ProjectsScreen.cpp 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  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 <ProjectsScreen.h>
  9. #include <ProjectManagerDefs.h>
  10. #include <ProjectButtonWidget.h>
  11. #include <PythonBindingsInterface.h>
  12. #include <PythonBindings.h>
  13. #include <ProjectUtils.h>
  14. #include <ProjectBuilderController.h>
  15. #include <ProjectExportController.h>
  16. #include <ScreensCtrl.h>
  17. #include <SettingsInterface.h>
  18. #include <AddRemoteProjectDialog.h>
  19. #include <AzCore/std/ranges/ranges_algorithm.h>
  20. #include <AzQtComponents/Components/FlowLayout.h>
  21. #include <AzCore/Platform.h>
  22. #include <AzCore/IO/SystemFile.h>
  23. #include <AzFramework/AzFramework_Traits_Platform.h>
  24. #include <AzFramework/Process/ProcessCommon.h>
  25. #include <AzFramework/Process/ProcessWatcher.h>
  26. #include <AzCore/Utils/Utils.h>
  27. #include <AzCore/std/sort.h>
  28. #include <AzCore/std/smart_ptr/unique_ptr.h>
  29. #include <QVBoxLayout>
  30. #include <QHBoxLayout>
  31. #include <QLabel>
  32. #include <QPushButton>
  33. #include <QFileDialog>
  34. #include <QMenu>
  35. #include <QListView>
  36. #include <QSpacerItem>
  37. #include <QListWidget>
  38. #include <QListWidgetItem>
  39. #include <QScrollArea>
  40. #include <QStackedWidget>
  41. #include <QFrame>
  42. #include <QIcon>
  43. #include <QPixmap>
  44. #include <QSettings>
  45. #include <QMessageBox>
  46. #include <QTimer>
  47. #include <QQueue>
  48. #include <QDir>
  49. #include <QGuiApplication>
  50. #include <QFileSystemWatcher>
  51. #include <QProcess>
  52. namespace O3DE::ProjectManager
  53. {
  54. ProjectsScreen::ProjectsScreen(DownloadController* downloadController, QWidget* parent)
  55. : ScreenWidget(parent)
  56. , m_downloadController(downloadController)
  57. {
  58. QVBoxLayout* vLayout = new QVBoxLayout();
  59. vLayout->setAlignment(Qt::AlignTop);
  60. vLayout->setContentsMargins(s_contentMargins, 0, s_contentMargins, 0);
  61. setLayout(vLayout);
  62. m_fileSystemWatcher = new QFileSystemWatcher(this);
  63. connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &ProjectsScreen::HandleProjectFilePathChanged);
  64. m_stack = new QStackedWidget(this);
  65. m_firstTimeContent = CreateFirstTimeContent();
  66. m_stack->addWidget(m_firstTimeContent);
  67. m_projectsContent = CreateProjectsContent();
  68. m_stack->addWidget(m_projectsContent);
  69. vLayout->addWidget(m_stack);
  70. connect(static_cast<ScreensCtrl*>(parent), &ScreensCtrl::NotifyBuildProject, this, &ProjectsScreen::SuggestBuildProject);
  71. connect(m_downloadController, &DownloadController::Done, this, &ProjectsScreen::HandleDownloadResult);
  72. connect(m_downloadController, &DownloadController::ObjectDownloadProgress, this, &ProjectsScreen::HandleDownloadProgress);
  73. }
  74. ProjectsScreen::~ProjectsScreen() = default;
  75. QFrame* ProjectsScreen::CreateFirstTimeContent()
  76. {
  77. QFrame* frame = new QFrame(this);
  78. frame->setObjectName("firstTimeContent");
  79. {
  80. QVBoxLayout* layout = new QVBoxLayout();
  81. layout->setContentsMargins(0, 0, 0, 0);
  82. layout->setAlignment(Qt::AlignTop);
  83. frame->setLayout(layout);
  84. QLabel* titleLabel = new QLabel(tr("Ready? Set. Create!"), this);
  85. titleLabel->setObjectName("titleLabel");
  86. layout->addWidget(titleLabel);
  87. QLabel* introLabel = new QLabel(this);
  88. introLabel->setObjectName("introLabel");
  89. introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project."));
  90. layout->addWidget(introLabel);
  91. QHBoxLayout* buttonLayout = new QHBoxLayout();
  92. buttonLayout->setAlignment(Qt::AlignLeft);
  93. buttonLayout->setSpacing(s_spacerSize);
  94. // use a newline to force the text up
  95. QPushButton* createProjectButton = new QPushButton(tr("Create a project\n"), this);
  96. createProjectButton->setObjectName("createProjectButton");
  97. buttonLayout->addWidget(createProjectButton);
  98. QPushButton* addProjectButton = new QPushButton(tr("Open a project\n"), this);
  99. addProjectButton->setObjectName("addProjectButton");
  100. buttonLayout->addWidget(addProjectButton);
  101. QPushButton* addRemoteProjectButton = new QPushButton(tr("Add a remote project\n"), this);
  102. addRemoteProjectButton->setObjectName("addRemoteProjectButton");
  103. buttonLayout->addWidget(addRemoteProjectButton);
  104. connect(createProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleNewProjectButton);
  105. connect(addProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleAddProjectButton);
  106. connect(addRemoteProjectButton, &QPushButton::clicked, this, &ProjectsScreen::HandleAddRemoteProjectButton);
  107. layout->addLayout(buttonLayout);
  108. }
  109. return frame;
  110. }
  111. QFrame* ProjectsScreen::CreateProjectsContent()
  112. {
  113. QFrame* frame = new QFrame(this);
  114. frame->setObjectName("projectsContent");
  115. {
  116. QVBoxLayout* layout = new QVBoxLayout();
  117. layout->setAlignment(Qt::AlignTop);
  118. layout->setContentsMargins(0, 0, 0, 0);
  119. frame->setLayout(layout);
  120. QFrame* header = new QFrame(frame);
  121. QHBoxLayout* headerLayout = new QHBoxLayout();
  122. {
  123. QLabel* titleLabel = new QLabel(tr("My Projects"), this);
  124. titleLabel->setObjectName("titleLabel");
  125. headerLayout->addWidget(titleLabel);
  126. QMenu* newProjectMenu = new QMenu(this);
  127. m_createNewProjectAction = newProjectMenu->addAction("Create New Project");
  128. m_addExistingProjectAction = newProjectMenu->addAction("Open Existing Project");
  129. m_addRemoteProjectAction = newProjectMenu->addAction("Add a Remote Project");
  130. connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton);
  131. connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton);
  132. connect(m_addRemoteProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddRemoteProjectButton);
  133. QPushButton* newProjectMenuButton = new QPushButton(tr("New Project..."), this);
  134. newProjectMenuButton->setObjectName("newProjectButton");
  135. newProjectMenuButton->setMenu(newProjectMenu);
  136. newProjectMenuButton->setDefault(true);
  137. headerLayout->addWidget(newProjectMenuButton);
  138. }
  139. header->setLayout(headerLayout);
  140. layout->addWidget(header);
  141. QScrollArea* projectsScrollArea = new QScrollArea(this);
  142. QWidget* scrollWidget = new QWidget();
  143. m_projectsFlowLayout = new FlowLayout(0, s_spacerSize, s_spacerSize);
  144. scrollWidget->setLayout(m_projectsFlowLayout);
  145. projectsScrollArea->setWidget(scrollWidget);
  146. projectsScrollArea->setWidgetResizable(true);
  147. layout->addWidget(projectsScrollArea);
  148. }
  149. return frame;
  150. }
  151. ProjectButton* ProjectsScreen::CreateProjectButton(const ProjectInfo& project, const EngineInfo& engine)
  152. {
  153. ProjectButton* projectButton = new ProjectButton(project, engine, this);
  154. m_projectButtons.insert({ project.m_path.toUtf8().constData(), projectButton });
  155. m_projectsFlowLayout->addWidget(projectButton);
  156. connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
  157. connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
  158. connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
  159. connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
  160. connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
  161. connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
  162. connect(projectButton, &ProjectButton::BuildProject, this, &ProjectsScreen::QueueBuildProject);
  163. connect(projectButton, &ProjectButton::ExportProject, this, &ProjectsScreen::QueueExportProject);
  164. connect(projectButton, &ProjectButton::OpenCMakeGUI, this,
  165. [this](const ProjectInfo& projectInfo)
  166. {
  167. AZ::Outcome result = ProjectUtils::OpenCMakeGUI(projectInfo.m_path);
  168. if (!result)
  169. {
  170. QMessageBox::critical(this, tr("Failed to open CMake GUI"), result.GetError(), QMessageBox::Ok);
  171. }
  172. });
  173. connect(projectButton, &ProjectButton::OpenAndroidProjectGenerator, this, &ProjectsScreen::HandleOpenAndroidProjectGenerator);
  174. connect(projectButton, &ProjectButton::OpenProjectExportSettings, this, &ProjectsScreen::HandleOpenProjectExportSettings);
  175. return projectButton;
  176. }
  177. void ProjectsScreen::RemoveProjectButtonsFromFlowLayout(const QVector<ProjectInfo>& projectsToKeep)
  178. {
  179. // If a project path is in this set then the button for it will be kept
  180. AZStd::unordered_set<AZ::IO::Path> keepProject;
  181. for (const ProjectInfo& project : projectsToKeep)
  182. {
  183. keepProject.insert(project.m_path.toUtf8().constData());
  184. }
  185. // Remove buttons from flow layout and delete buttons for removed projects
  186. auto projectButtonsIter = m_projectButtons.begin();
  187. while (projectButtonsIter != m_projectButtons.end())
  188. {
  189. const auto button = projectButtonsIter->second;
  190. m_projectsFlowLayout->removeWidget(button);
  191. if (!keepProject.contains(projectButtonsIter->first))
  192. {
  193. m_fileSystemWatcher->removePath(QDir::toNativeSeparators(button->GetProjectInfo().m_path + "/project.json"));
  194. button->deleteLater();
  195. projectButtonsIter = m_projectButtons.erase(projectButtonsIter);
  196. }
  197. else
  198. {
  199. ++projectButtonsIter;
  200. }
  201. }
  202. }
  203. void ProjectsScreen::UpdateIfCurrentScreen()
  204. {
  205. if (IsCurrentScreen())
  206. {
  207. UpdateWithProjects(GetAllProjects());
  208. }
  209. }
  210. void ProjectsScreen::UpdateWithProjects(const QVector<ProjectInfo>& projects)
  211. {
  212. PythonBindingsInterface::Get()->RemoveInvalidProjects();
  213. if(projects.isEmpty() && !m_projectButtons.empty())
  214. {
  215. RemoveProjectButtonsFromFlowLayout(projects);
  216. }
  217. if (!projects.isEmpty())
  218. {
  219. // Remove all existing buttons before adding them back in the correct order
  220. RemoveProjectButtonsFromFlowLayout(/*projectsToKeep*/ projects);
  221. // It's more efficient to update the project engine by loading engine infos once
  222. // instead of loading them all each time we want to know what project an engine uses
  223. auto engineInfoResult = PythonBindingsInterface::Get()->GetAllEngineInfos();
  224. // Add all project buttons, restoring buttons to default state
  225. for (const ProjectInfo& project : projects)
  226. {
  227. ProjectButton* currentButton = nullptr;
  228. const AZ::IO::Path projectPath { project.m_path.toUtf8().constData() };
  229. auto projectButtonIter = m_projectButtons.find(projectPath);
  230. EngineInfo engine{};
  231. if (engineInfoResult && !project.m_enginePath.isEmpty())
  232. {
  233. AZ::IO::FixedMaxPath projectEnginePath{ project.m_enginePath.toUtf8().constData() };
  234. for (const EngineInfo& engineInfo : engineInfoResult.GetValue())
  235. {
  236. AZ::IO::FixedMaxPath enginePath{ engineInfo.m_path.toUtf8().constData() };
  237. if (enginePath == projectEnginePath)
  238. {
  239. engine = engineInfo;
  240. break;
  241. }
  242. }
  243. }
  244. if (projectButtonIter == m_projectButtons.end())
  245. {
  246. currentButton = CreateProjectButton(project, engine);
  247. m_projectButtons.insert({ projectPath, currentButton });
  248. m_fileSystemWatcher->addPath(QDir::toNativeSeparators(project.m_path + "/project.json"));
  249. }
  250. else
  251. {
  252. currentButton = projectButtonIter->second;
  253. currentButton->SetEngine(engine);
  254. currentButton->SetProject(project);
  255. currentButton->SetState(ProjectButtonState::ReadyToLaunch);
  256. }
  257. // Check whether project manager has successfully built the project
  258. AZ_Assert(currentButton, "Invalid ProjectButton");
  259. m_projectsFlowLayout->addWidget(currentButton);
  260. bool projectBuiltSuccessfully = false;
  261. SettingsInterface::Get()->GetProjectBuiltSuccessfully(projectBuiltSuccessfully, project);
  262. if (!projectBuiltSuccessfully)
  263. {
  264. currentButton->SetState(ProjectButtonState::NeedsToBuild);
  265. }
  266. if (project.m_remote)
  267. {
  268. currentButton->SetState(ProjectButtonState::NotDownloaded);
  269. currentButton->SetProjectButtonAction(
  270. tr("Download Project"),
  271. [this, currentButton, project]
  272. {
  273. m_downloadController->AddObjectDownload(project.m_projectName, "", DownloadController::DownloadObjectType::Project);
  274. currentButton->SetState(ProjectButtonState::Downloading);
  275. });
  276. }
  277. }
  278. if (m_currentBuilder)
  279. {
  280. AZ::IO::Path buildProjectPath = AZ::IO::Path(m_currentBuilder->GetProjectInfo().m_path.toUtf8().constData());
  281. if (!buildProjectPath.empty())
  282. {
  283. // Setup building button again
  284. auto buildProjectIter = m_projectButtons.find(buildProjectPath);
  285. if (buildProjectIter != m_projectButtons.end())
  286. {
  287. m_currentBuilder->SetProjectButton(buildProjectIter->second);
  288. }
  289. }
  290. }
  291. if (m_currentExporter)
  292. {
  293. AZ::IO::Path exportProjectPath = AZ::IO::Path(m_currentExporter->GetProjectInfo().m_path.toUtf8().constData());
  294. if (!exportProjectPath.empty())
  295. {
  296. //Setup export button
  297. if (auto exportProjectIter = m_projectButtons.find(exportProjectPath);
  298. exportProjectIter != m_projectButtons.end())
  299. {
  300. m_currentExporter->SetProjectButton(exportProjectIter->second);
  301. }
  302. }
  303. }
  304. // Let the user cancel builds for projects in the build queue and in the export queue
  305. for (const ProjectInfo& project : m_buildQueue)
  306. {
  307. auto projectIter = m_projectButtons.find(project.m_path.toUtf8().constData());
  308. if (projectIter != m_projectButtons.end())
  309. {
  310. projectIter->second->SetProjectButtonAction(
  311. tr("Cancel queued build"),
  312. [this, project]
  313. {
  314. UnqueueBuildProject(project);
  315. SuggestBuildProjectMsg(project, false);
  316. });
  317. }
  318. }
  319. for (const ProjectInfo& project : m_exportQueue)
  320. {
  321. auto projectIter = m_projectButtons.find(project.m_path.toUtf8().constData());
  322. if (projectIter != m_projectButtons.end())
  323. {
  324. projectIter->second->SetProjectButtonAction(
  325. tr("Cancel queued export"),
  326. [this, project]
  327. {
  328. UnqueueExportProject(project);
  329. });
  330. }
  331. }
  332. // Update the project build status if it requires building
  333. for (const ProjectInfo& project : m_requiresBuild)
  334. {
  335. auto projectIter = m_projectButtons.find(project.m_path.toUtf8().constData());
  336. if (projectIter != m_projectButtons.end())
  337. {
  338. // If project is not currently or about to build
  339. if (!m_currentBuilder || m_currentBuilder->GetProjectInfo() != project)
  340. {
  341. if (project.m_buildFailed)
  342. {
  343. projectIter->second->SetBuildLogsLink(project.m_logUrl);
  344. projectIter->second->SetState(ProjectButtonState::BuildFailed);
  345. }
  346. else
  347. {
  348. projectIter->second->SetState(ProjectButtonState::NeedsToBuild);
  349. }
  350. }
  351. }
  352. }
  353. }
  354. if (m_projectsContent)
  355. {
  356. m_stack->setCurrentWidget(m_projectsContent);
  357. }
  358. m_projectsFlowLayout->update();
  359. // Will focus whatever button it finds so the Project tab is not focused on start-up
  360. QTimer::singleShot(0, this, [this]
  361. {
  362. QPushButton* foundButton = m_stack->currentWidget()->findChild<QPushButton*>();
  363. if (foundButton)
  364. {
  365. foundButton->setFocus();
  366. }
  367. });
  368. }
  369. void ProjectsScreen::HandleProjectFilePathChanged(const QString& /*path*/)
  370. {
  371. // QFileWatcher automatically stops watching the path if it was removed so we will just refresh our view
  372. UpdateIfCurrentScreen();
  373. }
  374. ProjectManagerScreen ProjectsScreen::GetScreenEnum()
  375. {
  376. return ProjectManagerScreen::Projects;
  377. }
  378. bool ProjectsScreen::IsTab()
  379. {
  380. return true;
  381. }
  382. QString ProjectsScreen::GetTabText()
  383. {
  384. return tr("Projects");
  385. }
  386. void ProjectsScreen::paintEvent([[maybe_unused]] QPaintEvent* event)
  387. {
  388. // we paint the background here because qss does not support background cover scaling
  389. QPainter painter(this);
  390. const QSize winSize = size();
  391. const float pixmapRatio = (float)m_background.width() / m_background.height();
  392. const float windowRatio = (float)winSize.width() / winSize.height();
  393. QRect backgroundRect;
  394. if (pixmapRatio > windowRatio)
  395. {
  396. const int newWidth = (int)(winSize.height() * pixmapRatio);
  397. const int offset = (newWidth - winSize.width()) / -2;
  398. backgroundRect = QRect(offset, 0, newWidth, winSize.height());
  399. }
  400. else
  401. {
  402. const int newHeight = (int)(winSize.width() / pixmapRatio);
  403. backgroundRect = QRect(0, 0, winSize.width(), newHeight);
  404. }
  405. // Draw the background image.
  406. painter.drawPixmap(backgroundRect, m_background);
  407. // Draw a semi-transparent overlay to darken down the colors.
  408. // Use SourceOver, DestinationIn will make background transparent on Mac
  409. painter.setCompositionMode (QPainter::CompositionMode_SourceOver);
  410. const float overlayTransparency = 0.3f;
  411. painter.fillRect(backgroundRect, QColor(0, 0, 0, static_cast<int>(255.0f * overlayTransparency)));
  412. }
  413. void ProjectsScreen::HandleNewProjectButton()
  414. {
  415. emit ResetScreenRequest(ProjectManagerScreen::CreateProject);
  416. emit ChangeScreenRequest(ProjectManagerScreen::CreateProject);
  417. }
  418. void ProjectsScreen::HandleAddProjectButton()
  419. {
  420. QString title{ QObject::tr("Select Project File") };
  421. QString defaultPath;
  422. // get the default path to look for new projects in
  423. AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
  424. if (engineInfoResult.IsSuccess())
  425. {
  426. defaultPath = engineInfoResult.GetValue().m_defaultProjectsFolder;
  427. }
  428. QString path = QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, title, defaultPath, ProjectUtils::ProjectJsonFilename.data()));
  429. if (!path.isEmpty())
  430. {
  431. // RegisterProject will check compatibility and prompt user to continue if issues found
  432. // it will also handle detailed error messaging
  433. path.remove(ProjectUtils::ProjectJsonFilename.data());
  434. if (ProjectUtils::RegisterProject(path, this))
  435. {
  436. // notify the user the project was added successfully
  437. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  438. QMessageBox::information(this, "Project added", "Project added successfully");
  439. }
  440. }
  441. }
  442. void ProjectsScreen::HandleAddRemoteProjectButton()
  443. {
  444. AddRemoteProjectDialog* addRemoteProjectDialog = new AddRemoteProjectDialog(this);
  445. connect(addRemoteProjectDialog, &AddRemoteProjectDialog::StartObjectDownload, this, &ProjectsScreen::StartProjectDownload);
  446. if (addRemoteProjectDialog->exec() == QDialog::DialogCode::Accepted)
  447. {
  448. QString repoUri = addRemoteProjectDialog->GetRepoPath();
  449. if (repoUri.isEmpty())
  450. {
  451. QMessageBox::warning(this, tr("No Input"), tr("Please provide a repo Uri."));
  452. return;
  453. }
  454. }
  455. }
  456. void ProjectsScreen::HandleOpenProject(const QString& projectPath)
  457. {
  458. if (!projectPath.isEmpty())
  459. {
  460. if (!WarnIfInBuildQueue(projectPath))
  461. {
  462. AZ::IO::FixedMaxPath fixedProjectPath = projectPath.toUtf8().constData();
  463. AZ::IO::FixedMaxPath editorExecutablePath = ProjectUtils::GetEditorExecutablePath(fixedProjectPath);
  464. if (editorExecutablePath.empty())
  465. {
  466. AZ_Error("ProjectManager", false, "Failed to locate editor");
  467. QMessageBox::critical(
  468. this, tr("Error"), tr("Failed to locate the Editor, please verify that it is built."));
  469. return;
  470. }
  471. AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
  472. processLaunchInfo.m_commandlineParameters = AZStd::vector<AZStd::string>{
  473. editorExecutablePath.String(),
  474. AZStd::string::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%s")", fixedProjectPath.c_str())
  475. };
  476. ;
  477. bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
  478. if (!launchSucceeded)
  479. {
  480. AZ_Error("ProjectManager", false, "Failed to launch editor");
  481. QMessageBox::critical(
  482. this, tr("Error"), tr("Failed to launch the Editor, please verify the project settings are valid."));
  483. }
  484. else
  485. {
  486. // prevent the user from accidentally pressing the button while the editor is launching
  487. // and let them know what's happening
  488. ProjectButton* button = qobject_cast<ProjectButton*>(sender());
  489. if (button)
  490. {
  491. button->SetState(ProjectButtonState::Launching);
  492. }
  493. // enable the button after 3 seconds
  494. constexpr int waitTimeInMs = 3000;
  495. QTimer::singleShot(
  496. waitTimeInMs, this,
  497. [button]
  498. {
  499. if (button)
  500. {
  501. button->SetState(ProjectButtonState::ReadyToLaunch);
  502. }
  503. });
  504. }
  505. }
  506. }
  507. else
  508. {
  509. AZ_Error("ProjectManager", false, "Cannot open editor because an empty project path was provided");
  510. QMessageBox::critical( this, tr("Error"), tr("Failed to launch the Editor because the project path is invalid."));
  511. }
  512. }
  513. void ProjectsScreen::HandleEditProject(const QString& projectPath)
  514. {
  515. if (!WarnIfInBuildQueue(projectPath))
  516. {
  517. emit NotifyCurrentProject(projectPath);
  518. emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
  519. }
  520. }
  521. void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
  522. {
  523. if (!WarnIfInBuildQueue(projectPath))
  524. {
  525. emit NotifyCurrentProject(projectPath);
  526. emit ChangeScreenRequest(ProjectManagerScreen::ProjectGemCatalog);
  527. }
  528. }
  529. void ProjectsScreen::HandleCopyProject(const ProjectInfo& projectInfo)
  530. {
  531. if (!WarnIfInBuildQueue(projectInfo.m_path))
  532. {
  533. ProjectInfo newProjectInfo(projectInfo);
  534. // Open file dialog and choose location for copied project then register copy with O3DE
  535. if (ProjectUtils::CopyProjectDialog(projectInfo.m_path, newProjectInfo, this))
  536. {
  537. emit NotifyBuildProject(newProjectInfo);
  538. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  539. }
  540. }
  541. }
  542. void ProjectsScreen::HandleRemoveProject(const QString& projectPath)
  543. {
  544. if (!WarnIfInBuildQueue(projectPath))
  545. {
  546. // Unregister Project from O3DE and reload projects
  547. if (ProjectUtils::UnregisterProject(projectPath))
  548. {
  549. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  550. emit NotifyProjectRemoved(projectPath);
  551. }
  552. }
  553. }
  554. void ProjectsScreen::HandleDeleteProject(const QString& projectPath)
  555. {
  556. if (!WarnIfInBuildQueue(projectPath))
  557. {
  558. QString projectName = tr("Project");
  559. auto getProjectResult = PythonBindingsInterface::Get()->GetProject(projectPath);
  560. if (getProjectResult)
  561. {
  562. projectName = getProjectResult.GetValue().m_displayName;
  563. }
  564. QMessageBox::StandardButton warningResult = QMessageBox::warning(this,
  565. tr("Delete %1").arg(projectName),
  566. tr("%1 will be unregistered from O3DE and the project directory '%2' will be deleted from your disk.\n\nAre you sure you want to delete %1?").arg(projectName, projectPath),
  567. QMessageBox::No | QMessageBox::Yes);
  568. if (warningResult == QMessageBox::Yes)
  569. {
  570. QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
  571. // Remove project from O3DE and delete from disk
  572. HandleRemoveProject(projectPath);
  573. ProjectUtils::DeleteProjectFiles(projectPath);
  574. QGuiApplication::restoreOverrideCursor();
  575. emit NotifyProjectRemoved(projectPath);
  576. }
  577. }
  578. }
  579. void ProjectsScreen::HandleOpenAndroidProjectGenerator(const QString& projectPath)
  580. {
  581. AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetProjectEngine(projectPath);
  582. AZ::Outcome projectBuildPathResult = ProjectUtils::GetProjectBuildPath(projectPath);
  583. auto engineInfo = engineInfoResult.TakeValue();
  584. auto buildPath = projectBuildPathResult.TakeValue();
  585. QString projectName = tr("Project");
  586. auto getProjectResult = PythonBindingsInterface::Get()->GetProject(projectPath);
  587. if (getProjectResult)
  588. {
  589. projectName = getProjectResult.GetValue().m_displayName;
  590. }
  591. const QString pythonPath = ProjectUtils::GetPythonExecutablePath(engineInfo.m_path);
  592. const QString apgPath = QString("%1/Code/Tools/Android/ProjectGenerator/main.py").arg(engineInfo.m_path);
  593. AZ_Printf("ProjectManager", "APG Info:\nProject Name: %s\nProject Path: %s\nEngine Path: %s\n3rdParty Path: %s\nBuild Path: %s\nPython Path: %s\nAPG path: %s\n",
  594. projectName.toUtf8().constData(),
  595. projectPath.toUtf8().constData(),
  596. engineInfo.m_path.toUtf8().constData(),
  597. engineInfo.m_thirdPartyPath.toUtf8().constData(),
  598. buildPath.toUtf8().constData(),
  599. pythonPath.toUtf8().constData(),
  600. apgPath.toUtf8().constData());
  601. // Let's start the python script.
  602. QProcess process;
  603. process.setProgram(pythonPath);
  604. const QStringList commandArgs { apgPath,
  605. "--e", engineInfo.m_path,
  606. "--p", projectPath,
  607. "--b", buildPath,
  608. "--t", engineInfo.m_thirdPartyPath };
  609. process.setArguments(commandArgs);
  610. // It's important to dump the command details in the application log so the user
  611. // would know how to spawn the Android Project Generator from the command terminal
  612. // in case of errors and debugging is required.
  613. const QString commandArgsStr = QString("%1 %2").arg(pythonPath, commandArgs.join(" "));
  614. AZ_Printf("ProjectManager", "Will start the Android Project Generator with the following command:\n%s\n", commandArgsStr.toUtf8().constData());
  615. if (!process.startDetached())
  616. {
  617. QMessageBox::warning(
  618. this,
  619. tr("Tool Error"),
  620. tr("Failed to start Android Project Generator from path %1").arg(apgPath),
  621. QMessageBox::Ok);
  622. }
  623. }
  624. void ProjectsScreen::HandleOpenProjectExportSettings(const QString& projectPath)
  625. {
  626. AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetProjectEngine(projectPath);
  627. AZ::Outcome projectBuildPathResult = ProjectUtils::GetProjectBuildPath(projectPath);
  628. auto engineInfo = engineInfoResult.TakeValue();
  629. auto buildPath = projectBuildPathResult.TakeValue();
  630. QString projectName = tr("Project");
  631. auto getProjectResult = PythonBindingsInterface::Get()->GetProject(projectPath);
  632. if (getProjectResult)
  633. {
  634. projectName = getProjectResult.GetValue().m_displayName;
  635. }
  636. else
  637. {
  638. QMessageBox::critical(this, tr("Tool Error"), tr("Failed to retrieve project information."), QMessageBox::Ok);
  639. return;
  640. }
  641. const QString pythonPath = ProjectUtils::GetPythonExecutablePath(engineInfo.m_path);
  642. const QString o3dePath = QString("%1/scripts/o3de.py").arg(engineInfo.m_path);
  643. // Let's start the python script.
  644. QProcess process;
  645. process.setProgram(pythonPath);
  646. const QStringList commandArgs{ o3dePath, "export-project", "-pp", projectPath, "--configure" };
  647. process.setArguments(commandArgs);
  648. // It's important to dump the command details in the application log so the user
  649. // would know how to spawn the Export Configuration Panel from the command terminal
  650. // in case of errors and debugging is required.
  651. const QString commandArgsStr = QString("%1 %2").arg(pythonPath, commandArgs.join(" "));
  652. AZ_Printf(
  653. "ProjectManager",
  654. "Will start the Export Configuration Panel with the following command:\n%s\n",
  655. commandArgsStr.toUtf8().constData());
  656. if (!process.startDetached())
  657. {
  658. QMessageBox::critical(this, tr("Tool Error"), tr("Failed to start o3de.py from path %1").arg(o3dePath), QMessageBox::Ok);
  659. }
  660. }
  661. void ProjectsScreen::SuggestBuildProjectMsg(const ProjectInfo& projectInfo, bool showMessage)
  662. {
  663. if (RequiresBuildProjectIterator(projectInfo.m_path) == m_requiresBuild.end() || projectInfo.m_buildFailed)
  664. {
  665. m_requiresBuild.append(projectInfo);
  666. }
  667. UpdateIfCurrentScreen();
  668. if (showMessage)
  669. {
  670. QMessageBox::information(this,
  671. tr("Project should be rebuilt."),
  672. projectInfo.GetProjectDisplayName() + tr(" project likely needs to be rebuilt."));
  673. }
  674. }
  675. void ProjectsScreen::SuggestBuildProject(const ProjectInfo& projectInfo)
  676. {
  677. SuggestBuildProjectMsg(projectInfo, true);
  678. }
  679. void ProjectsScreen::QueueBuildProject(const ProjectInfo& projectInfo, bool skipDialogBox)
  680. {
  681. auto requiredIter = RequiresBuildProjectIterator(projectInfo.m_path);
  682. if (requiredIter != m_requiresBuild.end())
  683. {
  684. m_requiresBuild.erase(requiredIter);
  685. }
  686. if (!BuildQueueContainsProject(projectInfo.m_path))
  687. {
  688. if (m_buildQueue.empty() && !m_currentBuilder)
  689. {
  690. StartProjectBuild(projectInfo, skipDialogBox);
  691. // Projects Content is already reset in function
  692. }
  693. else
  694. {
  695. m_buildQueue.append(projectInfo);
  696. UpdateIfCurrentScreen();
  697. }
  698. }
  699. }
  700. void ProjectsScreen::UnqueueBuildProject(const ProjectInfo& projectInfo)
  701. {
  702. m_buildQueue.removeAll(projectInfo);
  703. UpdateIfCurrentScreen();
  704. }
  705. void ProjectsScreen::QueueExportProject(const ProjectInfo& projectInfo, const QString& exportScript, bool skipDialogBox)
  706. {
  707. if (!ExportQueueContainsProject(projectInfo.m_path))
  708. {
  709. if (m_exportQueue.empty() && !m_currentExporter)
  710. {
  711. ProjectInfo info = projectInfo;
  712. info.m_currentExportScript = exportScript;
  713. StartProjectExport(info, skipDialogBox);
  714. //Projects Content should be reset in function
  715. }
  716. else
  717. {
  718. m_exportQueue.append(projectInfo);
  719. UpdateIfCurrentScreen();
  720. }
  721. }
  722. }
  723. void ProjectsScreen::UnqueueExportProject(const ProjectInfo& projectInfo)
  724. {
  725. m_exportQueue.removeAll(projectInfo);
  726. UpdateIfCurrentScreen();
  727. }
  728. void ProjectsScreen::StartProjectDownload(const QString& projectName, const QString& destinationPath, bool queueBuild)
  729. {
  730. m_downloadController->AddObjectDownload(projectName, destinationPath, DownloadController::DownloadObjectType::Project);
  731. UpdateIfCurrentScreen();
  732. auto foundButton = AZStd::ranges::find_if(m_projectButtons,
  733. [&projectName](const AZStd::unordered_map<AZ::IO::Path, ProjectButton*>::value_type& value)
  734. {
  735. return (value.second->GetProjectInfo().m_projectName == projectName);
  736. });
  737. if (foundButton != m_projectButtons.end())
  738. {
  739. (*foundButton).second->SetState(queueBuild ? ProjectButtonState::DownloadingBuildQueued : ProjectButtonState::Downloading);
  740. }
  741. }
  742. void ProjectsScreen::HandleDownloadResult(const QString& projectName, bool succeeded)
  743. {
  744. auto foundButton = AZStd::ranges::find_if(
  745. m_projectButtons,
  746. [&projectName](const AZStd::unordered_map<AZ::IO::Path, ProjectButton*>::value_type& value)
  747. {
  748. return (value.second->GetProjectInfo().m_projectName == projectName);
  749. });
  750. if (foundButton != m_projectButtons.end())
  751. {
  752. if (succeeded)
  753. {
  754. // Find the project info since it should now be local
  755. auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
  756. if (projectsResult.IsSuccess() && !projectsResult.GetValue().isEmpty())
  757. {
  758. for (const ProjectInfo& projectInfo : projectsResult.GetValue())
  759. {
  760. if (projectInfo.m_projectName == projectName)
  761. {
  762. (*foundButton).second->SetProject(projectInfo);
  763. if ((*foundButton).second->GetState() == ProjectButtonState::DownloadingBuildQueued)
  764. {
  765. QueueBuildProject(projectInfo, true);
  766. }
  767. else
  768. {
  769. (*foundButton).second->SetState(ProjectButtonState::NeedsToBuild);
  770. }
  771. }
  772. }
  773. }
  774. }
  775. else
  776. {
  777. (*foundButton).second->SetState(ProjectButtonState::NotDownloaded);
  778. }
  779. }
  780. else
  781. {
  782. UpdateIfCurrentScreen();
  783. }
  784. }
  785. void ProjectsScreen::HandleDownloadProgress(const QString& projectName, DownloadController::DownloadObjectType objectType, int bytesDownloaded, int totalBytes)
  786. {
  787. if (objectType != DownloadController::DownloadObjectType::Project)
  788. {
  789. return;
  790. }
  791. //Find button for project name
  792. auto foundButton = AZStd::ranges::find_if(m_projectButtons,
  793. [&projectName](const AZStd::unordered_map<AZ::IO::Path, ProjectButton*>::value_type& value)
  794. {
  795. return (value.second->GetProjectInfo().m_projectName == projectName);
  796. });
  797. if (foundButton != m_projectButtons.end())
  798. {
  799. float percentage = static_cast<float>(bytesDownloaded) / totalBytes;
  800. (*foundButton).second->SetProgressBarPercentage(percentage);
  801. }
  802. }
  803. QVector<ProjectInfo> ProjectsScreen::GetAllProjects()
  804. {
  805. QVector<ProjectInfo> projects;
  806. auto projectsResult = PythonBindingsInterface::Get()->GetProjects();
  807. if (projectsResult.IsSuccess() && !projectsResult.GetValue().isEmpty())
  808. {
  809. projects.append(projectsResult.GetValue());
  810. }
  811. auto remoteProjectsResult = PythonBindingsInterface::Get()->GetProjectsForAllRepos();
  812. if (remoteProjectsResult.IsSuccess() && !remoteProjectsResult.GetValue().isEmpty())
  813. {
  814. for (const ProjectInfo& remoteProject : remoteProjectsResult.TakeValue())
  815. {
  816. auto foundProject = AZStd::ranges::find_if( projects,
  817. [&remoteProject](const ProjectInfo& value)
  818. {
  819. return remoteProject.m_id == value.m_id;
  820. });
  821. if (foundProject == projects.end())
  822. {
  823. projects.append(remoteProject);
  824. }
  825. }
  826. }
  827. AZ::IO::Path buildProjectPath;
  828. if (m_currentBuilder)
  829. {
  830. buildProjectPath = AZ::IO::Path(m_currentBuilder->GetProjectInfo().m_path.toUtf8().constData());
  831. }
  832. // Sort the projects, putting currently building project in front, then queued projects, then sorts alphabetically
  833. AZStd::sort(projects.begin(), projects.end(), [buildProjectPath, this](const ProjectInfo& arg1, const ProjectInfo& arg2)
  834. {
  835. if (!buildProjectPath.empty())
  836. {
  837. if (AZ::IO::Path(arg1.m_path.toUtf8().constData()) == buildProjectPath)
  838. {
  839. return true;
  840. }
  841. else if (AZ::IO::Path(arg2.m_path.toUtf8().constData()) == buildProjectPath)
  842. {
  843. return false;
  844. }
  845. }
  846. bool arg1InBuildQueue = BuildQueueContainsProject(arg1.m_path);
  847. bool arg2InBuildQueue = BuildQueueContainsProject(arg2.m_path);
  848. if (arg1InBuildQueue && !arg2InBuildQueue)
  849. {
  850. return true;
  851. }
  852. else if (!arg1InBuildQueue && arg2InBuildQueue)
  853. {
  854. return false;
  855. }
  856. else if (arg1.m_displayName.compare(arg2.m_displayName, Qt::CaseInsensitive) == 0)
  857. {
  858. // handle case where names are the same
  859. return arg1.m_path.toLower() < arg2.m_path.toLower();
  860. }
  861. else
  862. {
  863. return arg1.m_displayName.toLower() < arg2.m_displayName.toLower();
  864. }
  865. });
  866. return projects;
  867. }
  868. void ProjectsScreen::NotifyCurrentScreen()
  869. {
  870. const QVector<ProjectInfo>& projects = GetAllProjects();
  871. const bool projectsFound = !projects.isEmpty();
  872. if (ShouldDisplayFirstTimeContent(projectsFound))
  873. {
  874. m_background.load(":/Backgrounds/FtueBackground.jpg");
  875. m_stack->setCurrentWidget(m_firstTimeContent);
  876. }
  877. else
  878. {
  879. m_background.load(":/Backgrounds/DefaultBackground.jpg");
  880. UpdateWithProjects(projects);
  881. }
  882. }
  883. bool ProjectsScreen::ShouldDisplayFirstTimeContent(bool projectsFound)
  884. {
  885. if (projectsFound)
  886. {
  887. return false;
  888. }
  889. // only show this screen once
  890. QSettings settings;
  891. bool displayFirstTimeContent = settings.value("displayFirstTimeContent", true).toBool();
  892. if (displayFirstTimeContent)
  893. {
  894. settings.setValue("displayFirstTimeContent", false);
  895. }
  896. return displayFirstTimeContent;
  897. }
  898. bool ProjectsScreen::StartProjectExport(const ProjectInfo& projectInfo, bool skipDialogBox)
  899. {
  900. bool proceedToExport = skipDialogBox;
  901. if (!proceedToExport)
  902. {
  903. QMessageBox::StandardButton buildProject = QMessageBox::information(
  904. this,
  905. tr("Exporting \"%1\"").arg(projectInfo.GetProjectDisplayName()),
  906. tr("Ready to export \"%1\"? Please ensure you have configured the export settings before proceeding.").arg(projectInfo.GetProjectDisplayName()),
  907. QMessageBox::No | QMessageBox::Yes);
  908. proceedToExport = buildProject == QMessageBox::Yes;
  909. }
  910. if (proceedToExport)
  911. {
  912. m_currentExporter = AZStd::make_unique<ProjectExportController>(projectInfo, nullptr, this);
  913. UpdateWithProjects(GetAllProjects());
  914. connect(m_currentExporter.get(), &ProjectExportController::Done, this, &ProjectsScreen::ProjectExportDone);
  915. m_currentExporter->Start();
  916. }
  917. else
  918. {
  919. return false;
  920. }
  921. return true;
  922. }
  923. bool ProjectsScreen::StartProjectBuild(const ProjectInfo& projectInfo, bool skipDialogBox)
  924. {
  925. if (ProjectUtils::FindSupportedCompiler(projectInfo, this))
  926. {
  927. bool proceedToBuild = skipDialogBox;
  928. if (!proceedToBuild)
  929. {
  930. QMessageBox::StandardButton buildProject = QMessageBox::information(
  931. this,
  932. tr("Building \"%1\"").arg(projectInfo.GetProjectDisplayName()),
  933. tr("Ready to build \"%1\"?").arg(projectInfo.GetProjectDisplayName()),
  934. QMessageBox::No | QMessageBox::Yes);
  935. proceedToBuild = buildProject == QMessageBox::Yes;
  936. }
  937. if (proceedToBuild)
  938. {
  939. m_currentBuilder = new ProjectBuilderController(projectInfo, nullptr, this);
  940. UpdateWithProjects(GetAllProjects());
  941. connect(m_currentBuilder, &ProjectBuilderController::Done, this, &ProjectsScreen::ProjectBuildDone);
  942. connect(m_currentBuilder, &ProjectBuilderController::NotifyBuildProject, this, &ProjectsScreen::SuggestBuildProject);
  943. m_currentBuilder->Start();
  944. }
  945. else
  946. {
  947. SuggestBuildProjectMsg(projectInfo, false);
  948. return false;
  949. }
  950. return true;
  951. }
  952. return false;
  953. }
  954. void ProjectsScreen::ProjectBuildDone(bool success)
  955. {
  956. ProjectInfo currentBuilderProject;
  957. if (!success)
  958. {
  959. currentBuilderProject = m_currentBuilder->GetProjectInfo();
  960. }
  961. delete m_currentBuilder;
  962. m_currentBuilder = nullptr;
  963. if (!success)
  964. {
  965. SuggestBuildProjectMsg(currentBuilderProject, false);
  966. }
  967. if (!m_buildQueue.empty())
  968. {
  969. while (!StartProjectBuild(m_buildQueue.front()) && m_buildQueue.size() > 1)
  970. {
  971. m_buildQueue.pop_front();
  972. }
  973. m_buildQueue.pop_front();
  974. }
  975. UpdateIfCurrentScreen();
  976. }
  977. void ProjectsScreen::ProjectExportDone(bool success)
  978. {
  979. ProjectInfo currentExportProject;
  980. if (!success)
  981. {
  982. currentExportProject = m_currentExporter->GetProjectInfo();
  983. }
  984. m_currentExporter.reset();
  985. if (!m_exportQueue.empty())
  986. {
  987. while (!StartProjectExport(m_exportQueue.front()) && m_exportQueue.size() > 1)
  988. {
  989. m_exportQueue.pop_front();
  990. }
  991. m_exportQueue.pop_front();
  992. }
  993. UpdateIfCurrentScreen();
  994. }
  995. QList<ProjectInfo>::iterator ProjectsScreen::RequiresBuildProjectIterator(const QString& projectPath)
  996. {
  997. QString nativeProjPath(QDir::toNativeSeparators(projectPath));
  998. auto projectIter = m_requiresBuild.begin();
  999. for (; projectIter != m_requiresBuild.end(); ++projectIter)
  1000. {
  1001. if (QDir::toNativeSeparators(projectIter->m_path) == nativeProjPath)
  1002. {
  1003. break;
  1004. }
  1005. }
  1006. return projectIter;
  1007. }
  1008. bool ProjectsScreen::BuildQueueContainsProject(const QString& projectPath)
  1009. {
  1010. const AZ::IO::PathView path { projectPath.toUtf8().constData() };
  1011. for (const ProjectInfo& project : m_buildQueue)
  1012. {
  1013. if (AZ::IO::PathView(project.m_path.toUtf8().constData()) == path)
  1014. {
  1015. return true;
  1016. }
  1017. }
  1018. return false;
  1019. }
  1020. bool ProjectsScreen::ExportQueueContainsProject(const QString& projectPath)
  1021. {
  1022. const AZ::IO::PathView path { projectPath.toUtf8().constData() };
  1023. for (const ProjectInfo& project : m_exportQueue)
  1024. {
  1025. if (AZ::IO::PathView(project.m_path.toUtf8().constData()) == path)
  1026. {
  1027. return true;
  1028. }
  1029. }
  1030. return false;
  1031. }
  1032. bool ProjectsScreen::WarnIfInBuildQueue(const QString& projectPath)
  1033. {
  1034. if (BuildQueueContainsProject(projectPath))
  1035. {
  1036. QMessageBox::warning(
  1037. this,
  1038. tr("Action Temporarily Disabled!"),
  1039. tr("Action not allowed on projects in build queue."));
  1040. return true;
  1041. }
  1042. return false;
  1043. }
  1044. } // namespace O3DE::ProjectManager