PythonScriptsDialog.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "PythonScriptsDialog.h"
  10. // AzCore
  11. #include <AzCore/Module/Module.h> // for AZ::ModuleData
  12. #include <AzCore/Module/ModuleManagerBus.h> // for AZ::ModuleManagerRequestBus
  13. #include <AzCore/Module/DynamicModuleHandle.h> // for AZ::DynamicModuleHandle
  14. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  15. #include <AzCore/Utils/Utils.h>
  16. // AzToolsFramework
  17. #include <AzToolsFramework/API/ViewPaneOptions.h> // for AzToolsFramework::ViewPaneOptions
  18. #include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h> // for AzToolsFramework::EditorPythonRunnerRequestBus
  19. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  20. // AzQtComponents
  21. #include <AzQtComponents/Components/Widgets/LineEdit.h>
  22. // Editor
  23. #include "Settings.h"
  24. #include "LyViewPaneNames.h"
  25. AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
  26. #include <Dialogs/ui_PythonScriptsDialog.h>
  27. AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
  28. //////////////////////////////////////////////////////////////////////////
  29. namespace
  30. {
  31. // File name extension for python files
  32. const QString s_kPythonFileNameSpec = "*.py";
  33. // Tree root element name
  34. const QString s_kRootElementName = "Python Scripts";
  35. }
  36. //////////////////////////////////////////////////////////////////////////
  37. void CPythonScriptsDialog::RegisterViewClass()
  38. {
  39. if (AzToolsFramework::EditorPythonRunnerRequestBus::HasHandlers())
  40. {
  41. AzToolsFramework::ViewPaneOptions options;
  42. options.canHaveMultipleInstances = true;
  43. AzToolsFramework::RegisterViewPane<CPythonScriptsDialog>("Python Scripts", LyViewPane::CategoryOther, options);
  44. }
  45. }
  46. //////////////////////////////////////////////////////////////////////////
  47. CPythonScriptsDialog::CPythonScriptsDialog(QWidget* parent)
  48. : QWidget(parent)
  49. , ui(new Ui::CPythonScriptsDialog)
  50. {
  51. ui->setupUi(this);
  52. AzQtComponents::LineEdit::applySearchStyle(ui->searchField);
  53. QStringList scriptFolders;
  54. auto engineScriptPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / "Assets" / "Editor" / "Scripts";
  55. scriptFolders.push_back(engineScriptPath.c_str());
  56. AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
  57. ScanFolderForScripts(QString("%1/Editor/Scripts").arg(projectPath.c_str()), scriptFolders);
  58. struct GetGemSourcePathsVisitor
  59. : AZ::SettingsRegistryInterface::Visitor
  60. {
  61. GetGemSourcePathsVisitor(AZ::SettingsRegistryInterface& settingsRegistry)
  62. : m_settingsRegistry(settingsRegistry)
  63. {}
  64. void Visit(AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type,
  65. AZStd::string_view value) override
  66. {
  67. AZStd::string_view jsonSourcePathPointer{ path };
  68. // Remove the array index from the path and check if the JSON path ends with "/SourcePaths"
  69. AZ::StringFunc::TokenizeLast(jsonSourcePathPointer, "/");
  70. if (jsonSourcePathPointer.ends_with("/SourcePaths"))
  71. {
  72. AZ::IO::Path newSourcePath = jsonSourcePathPointer;
  73. // Resolve any file aliases first - Do not use ResolvePath() as that assumes
  74. // any relative path is underneath the @assets@ alias
  75. if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
  76. {
  77. AZ::IO::FixedMaxPath replacedAliasPath;
  78. if (fileIoBase->ReplaceAlias(replacedAliasPath, value))
  79. {
  80. newSourcePath = AZ::IO::PathView(replacedAliasPath);
  81. }
  82. }
  83. // The current assumption is that the gem source path is the relative to the engine root
  84. AZ::IO::Path engineRootPath;
  85. m_settingsRegistry.Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  86. newSourcePath = (engineRootPath / newSourcePath).LexicallyNormal();
  87. if (auto gemSourcePathIter = AZStd::find(m_gemSourcePaths.begin(), m_gemSourcePaths.end(), newSourcePath);
  88. gemSourcePathIter == m_gemSourcePaths.end())
  89. {
  90. m_gemSourcePaths.emplace_back(AZStd::move(newSourcePath));
  91. }
  92. }
  93. }
  94. AZStd::vector<AZ::IO::Path> m_gemSourcePaths;
  95. private:
  96. AZ::SettingsRegistryInterface& m_settingsRegistry;
  97. };
  98. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  99. {
  100. GetGemSourcePathsVisitor visitor{ *settingsRegistry };
  101. constexpr auto gemListKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::OrganizationRootKey)
  102. + "/Gems";
  103. settingsRegistry->Visit(visitor, gemListKey);
  104. for (const AZ::IO::Path& gemSourcePath : visitor.m_gemSourcePaths)
  105. {
  106. ScanFolderForScripts(QString("%1/Editor/Scripts").arg(gemSourcePath.c_str()), scriptFolders);
  107. }
  108. }
  109. ui->treeView->init(scriptFolders, s_kPythonFileNameSpec, s_kRootElementName, false, false);
  110. QObject::connect(ui->treeView, &CFolderTreeCtrl::ItemDoubleClicked, this, &CPythonScriptsDialog::OnExecute);
  111. QObject::connect(ui->executeButton, &QPushButton::clicked, this, &CPythonScriptsDialog::OnExecute);
  112. QObject::connect(ui->searchField, &QLineEdit::textChanged, ui->treeView, &CFolderTreeCtrl::SetSearchFilter);
  113. }
  114. //////////////////////////////////////////////////////////////////////////
  115. void CPythonScriptsDialog::ScanFolderForScripts(QString path, QStringList& scriptFolders) const
  116. {
  117. char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
  118. if (AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(path.toLocal8Bit().constData(), resolvedPath, AZ_MAX_PATH_LEN))
  119. {
  120. if (AZ::IO::SystemFile::Exists(resolvedPath))
  121. {
  122. scriptFolders.push_back(path);
  123. }
  124. }
  125. }
  126. //////////////////////////////////////////////////////////////////////////
  127. CPythonScriptsDialog::~CPythonScriptsDialog()
  128. {
  129. }
  130. //////////////////////////////////////////////////////////////////////////
  131. void CPythonScriptsDialog::OnExecute()
  132. {
  133. QList<QStandardItem*> selectedItems = ui->treeView->GetSelectedItems();
  134. QStandardItem* selectedItem = selectedItems.empty() ? nullptr : selectedItems.first();
  135. if (selectedItem == nullptr)
  136. {
  137. return;
  138. }
  139. if (ui->treeView->IsFile(selectedItem))
  140. {
  141. auto scriptPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / ui->treeView->GetPath(selectedItem).toUtf8().constData();
  142. using namespace AzToolsFramework;
  143. EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, scriptPath.Native());
  144. }
  145. }
  146. #include <Dialogs/moc_PythonScriptsDialog.cpp>