PythonScriptsDialog.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. using AZ::SettingsRegistryInterface::Visitor::Visit;
  65. void Visit(AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type,
  66. AZStd::string_view value) override
  67. {
  68. AZStd::string_view jsonSourcePathPointer{ path };
  69. // Remove the array index from the path and check if the JSON path ends with "/SourcePaths"
  70. AZ::StringFunc::TokenizeLast(jsonSourcePathPointer, "/");
  71. if (jsonSourcePathPointer.ends_with("/SourcePaths"))
  72. {
  73. AZ::IO::Path newSourcePath = jsonSourcePathPointer;
  74. // Resolve any file aliases first - Do not use ResolvePath() as that assumes
  75. // any relative path is underneath the @products@ alias
  76. if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
  77. {
  78. AZ::IO::FixedMaxPath replacedAliasPath;
  79. if (fileIoBase->ReplaceAlias(replacedAliasPath, value))
  80. {
  81. newSourcePath = AZ::IO::PathView(replacedAliasPath);
  82. }
  83. }
  84. // The current assumption is that the gem source path is the relative to the engine root
  85. AZ::IO::Path engineRootPath;
  86. m_settingsRegistry.Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  87. newSourcePath = (engineRootPath / newSourcePath).LexicallyNormal();
  88. if (auto gemSourcePathIter = AZStd::find(m_gemSourcePaths.begin(), m_gemSourcePaths.end(), newSourcePath);
  89. gemSourcePathIter == m_gemSourcePaths.end())
  90. {
  91. m_gemSourcePaths.emplace_back(AZStd::move(newSourcePath));
  92. }
  93. }
  94. }
  95. AZStd::vector<AZ::IO::Path> m_gemSourcePaths;
  96. private:
  97. AZ::SettingsRegistryInterface& m_settingsRegistry;
  98. };
  99. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  100. {
  101. GetGemSourcePathsVisitor visitor{ *settingsRegistry };
  102. constexpr auto gemListKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::OrganizationRootKey)
  103. + "/Gems";
  104. settingsRegistry->Visit(visitor, gemListKey);
  105. for (const AZ::IO::Path& gemSourcePath : visitor.m_gemSourcePaths)
  106. {
  107. ScanFolderForScripts(QString("%1/Editor/Scripts").arg(gemSourcePath.c_str()), scriptFolders);
  108. }
  109. }
  110. ui->treeView->init(scriptFolders, s_kPythonFileNameSpec, s_kRootElementName, false, false);
  111. QObject::connect(ui->treeView, &CFolderTreeCtrl::ItemDoubleClicked, this, &CPythonScriptsDialog::OnExecute);
  112. QObject::connect(ui->executeButton, &QPushButton::clicked, this, &CPythonScriptsDialog::OnExecute);
  113. QObject::connect(ui->searchField, &QLineEdit::textChanged, ui->treeView, &CFolderTreeCtrl::SetSearchFilter);
  114. }
  115. //////////////////////////////////////////////////////////////////////////
  116. void CPythonScriptsDialog::ScanFolderForScripts(QString path, QStringList& scriptFolders) const
  117. {
  118. char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
  119. if (AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(path.toLocal8Bit().constData(), resolvedPath, AZ_MAX_PATH_LEN))
  120. {
  121. if (AZ::IO::SystemFile::Exists(resolvedPath))
  122. {
  123. scriptFolders.push_back(path);
  124. }
  125. }
  126. }
  127. //////////////////////////////////////////////////////////////////////////
  128. CPythonScriptsDialog::~CPythonScriptsDialog()
  129. {
  130. }
  131. //////////////////////////////////////////////////////////////////////////
  132. void CPythonScriptsDialog::OnExecute()
  133. {
  134. QList<QStandardItem*> selectedItems = ui->treeView->GetSelectedItems();
  135. QStandardItem* selectedItem = selectedItems.empty() ? nullptr : selectedItems.first();
  136. if (selectedItem == nullptr)
  137. {
  138. return;
  139. }
  140. if (ui->treeView->IsFile(selectedItem))
  141. {
  142. auto scriptPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / ui->treeView->GetPath(selectedItem).toUtf8().constData();
  143. using namespace AzToolsFramework;
  144. EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, scriptPath.Native());
  145. }
  146. }
  147. #include <Dialogs/moc_PythonScriptsDialog.cpp>