UpdateProjectCtrl.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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 <ProjectGemCatalogScreen.h>
  9. #include <GemRepo/GemRepoScreen.h>
  10. #include <CreateAGemScreen.h>
  11. #include <ProjectManagerDefs.h>
  12. #include <PythonBindingsInterface.h>
  13. #include <ScreenHeaderWidget.h>
  14. #include <ScreensCtrl.h>
  15. #include <UpdateProjectCtrl.h>
  16. #include <UpdateProjectSettingsScreen.h>
  17. #include <ProjectUtils.h>
  18. #include <DownloadController.h>
  19. #include <SettingsInterface.h>
  20. #include <QDialogButtonBox>
  21. #include <QMessageBox>
  22. #include <QPushButton>
  23. #include <QStackedWidget>
  24. #include <QTabWidget>
  25. #include <QVBoxLayout>
  26. #include <QDir>
  27. namespace O3DE::ProjectManager
  28. {
  29. UpdateProjectCtrl::UpdateProjectCtrl(DownloadController* downloadController, QWidget* parent)
  30. : ScreenWidget(parent)
  31. {
  32. QVBoxLayout* vLayout = new QVBoxLayout();
  33. vLayout->setContentsMargins(0, 0, 0, 0);
  34. m_header = new ScreenHeader(this);
  35. m_header->setTitle(tr(""));
  36. m_header->setSubTitle(tr("Edit Project Settings:"));
  37. connect(m_header->backButton(), &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton);
  38. vLayout->addWidget(m_header);
  39. m_updateSettingsScreen = new UpdateProjectSettingsScreen();
  40. m_projectGemCatalogScreen = new ProjectGemCatalogScreen(downloadController, this);
  41. m_gemRepoScreen = new GemRepoScreen(this);
  42. connect(m_projectGemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &UpdateProjectCtrl::OnChangeScreenRequest);
  43. connect(static_cast<ScreensCtrl*>(parent), &ScreensCtrl::NotifyProjectRemoved, m_projectGemCatalogScreen, &GemCatalogScreen::NotifyProjectRemoved);
  44. m_stack = new QStackedWidget(this);
  45. m_stack->setObjectName("body");
  46. m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
  47. vLayout->addWidget(m_stack);
  48. QFrame* topBarFrameWidget = new QFrame(this);
  49. topBarFrameWidget->setObjectName("projectSettingsTopFrame");
  50. QHBoxLayout* topBarHLayout = new QHBoxLayout();
  51. topBarHLayout->setContentsMargins(0, 0, 0, 0);
  52. topBarFrameWidget->setLayout(topBarHLayout);
  53. QTabWidget* tabWidget = new QTabWidget();
  54. tabWidget->setObjectName("projectSettingsTab");
  55. tabWidget->tabBar()->setObjectName("projectSettingsTabBar");
  56. tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
  57. tabWidget->addTab(m_updateSettingsScreen, tr("General"));
  58. QPushButton* gemsButton = new QPushButton(tr("Configure Gems"), this);
  59. gemsButton->setProperty("secondary", true);
  60. topBarHLayout->addWidget(gemsButton);
  61. tabWidget->setCornerWidget(gemsButton);
  62. topBarHLayout->addWidget(tabWidget);
  63. m_stack->addWidget(topBarFrameWidget);
  64. m_stack->addWidget(m_projectGemCatalogScreen);
  65. m_stack->addWidget(m_gemRepoScreen);
  66. QDialogButtonBox* backNextButtons = new QDialogButtonBox();
  67. backNextButtons->setObjectName("footer");
  68. vLayout->addWidget(backNextButtons);
  69. m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole);
  70. m_backButton->setProperty("secondary", true);
  71. m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole);
  72. m_nextButton->setProperty("primary", true);
  73. connect(gemsButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleGemsButton);
  74. connect(m_backButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton);
  75. connect(m_nextButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleNextButton);
  76. connect(static_cast<ScreensCtrl*>(parent), &ScreensCtrl::NotifyCurrentProject, this, &UpdateProjectCtrl::UpdateCurrentProject);
  77. Update();
  78. setLayout(vLayout);
  79. }
  80. ProjectManagerScreen UpdateProjectCtrl::GetScreenEnum()
  81. {
  82. return ProjectManagerScreen::UpdateProject;
  83. }
  84. bool UpdateProjectCtrl::ContainsScreen(ProjectManagerScreen screen)
  85. {
  86. // Do not include GemRepos because we don't want to advertise jumping to it from all other screens here
  87. return screen == GetScreenEnum() || screen == ProjectManagerScreen::ProjectGemCatalog;
  88. }
  89. void UpdateProjectCtrl::GoToScreen(ProjectManagerScreen screen)
  90. {
  91. OnChangeScreenRequest(screen);
  92. }
  93. // Called when pressing "Edit Project Settings..."
  94. void UpdateProjectCtrl::NotifyCurrentScreen()
  95. {
  96. m_stack->setCurrentIndex(ScreenOrder::Settings);
  97. Update();
  98. }
  99. void UpdateProjectCtrl::OnChangeScreenRequest(ProjectManagerScreen screen)
  100. {
  101. if (screen == ProjectManagerScreen::GemRepos)
  102. {
  103. m_stack->setCurrentWidget(m_gemRepoScreen);
  104. m_gemRepoScreen->NotifyCurrentScreen();
  105. Update();
  106. }
  107. else if (screen == ProjectManagerScreen::ProjectGemCatalog)
  108. {
  109. m_projectGemCatalogScreen->ReinitForProject(m_projectInfo.m_path);
  110. m_projectGemCatalogScreen->NotifyCurrentScreen();
  111. m_stack->setCurrentWidget(m_projectGemCatalogScreen);
  112. Update();
  113. }
  114. else if (screen == ProjectManagerScreen::UpdateProjectSettings)
  115. {
  116. m_stack->setCurrentWidget(m_updateSettingsScreen);
  117. m_updateSettingsScreen->NotifyCurrentScreen();
  118. Update();
  119. }
  120. else
  121. {
  122. emit ChangeScreenRequest(screen);
  123. }
  124. }
  125. void UpdateProjectCtrl::HandleGemsButton()
  126. {
  127. if (UpdateProjectSettings(true))
  128. {
  129. m_projectGemCatalogScreen->ReinitForProject(m_projectInfo.m_path);
  130. m_projectGemCatalogScreen->NotifyCurrentScreen();
  131. m_stack->setCurrentWidget(m_projectGemCatalogScreen);
  132. Update();
  133. }
  134. }
  135. void UpdateProjectCtrl::HandleBackButton()
  136. {
  137. if (m_stack->currentIndex() > 0)
  138. {
  139. m_stack->setCurrentIndex(m_stack->currentIndex() - 1);
  140. if (ScreenWidget* screenWidget = qobject_cast<ScreenWidget*>(m_stack->currentWidget()); screenWidget)
  141. {
  142. screenWidget->NotifyCurrentScreen();
  143. }
  144. Update();
  145. }
  146. else
  147. {
  148. if (UpdateProjectSettings(true))
  149. {
  150. emit GoToPreviousScreenRequest();
  151. }
  152. }
  153. }
  154. void UpdateProjectCtrl::HandleNextButton()
  155. {
  156. bool shouldRebuild = false;
  157. if (m_stack->currentIndex() == ScreenOrder::Settings && m_updateSettingsScreen)
  158. {
  159. if (!UpdateProjectSettings())
  160. {
  161. return;
  162. }
  163. }
  164. else if (m_stack->currentIndex() == ScreenOrder::Gems && m_projectGemCatalogScreen)
  165. {
  166. if (!m_projectGemCatalogScreen->GetDownloadController()->IsDownloadQueueEmpty())
  167. {
  168. QMessageBox::critical(this, tr("Gems downloading"), tr("You must wait for gems to finish downloading before continuing."));
  169. return;
  170. }
  171. // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project.
  172. const ProjectGemCatalogScreen::ConfiguredGemsResult result = m_projectGemCatalogScreen->ConfigureGemsForProject(m_projectInfo.m_path);
  173. if (result == ProjectGemCatalogScreen::ConfiguredGemsResult::Failed)
  174. {
  175. QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for project."));
  176. }
  177. if (result != ProjectGemCatalogScreen::ConfiguredGemsResult::Success)
  178. {
  179. return;
  180. }
  181. shouldRebuild = true;
  182. }
  183. if (shouldRebuild)
  184. {
  185. emit NotifyBuildProject(m_projectInfo);
  186. }
  187. emit ChangeScreenRequest(ProjectManagerScreen::Projects);
  188. }
  189. void UpdateProjectCtrl::UpdateCurrentProject(const QString& projectPath)
  190. {
  191. auto projectResult = PythonBindingsInterface::Get()->GetProject(projectPath);
  192. if (projectResult.IsSuccess())
  193. {
  194. m_projectInfo = projectResult.GetValue();
  195. }
  196. Update();
  197. UpdateSettingsScreen();
  198. }
  199. void UpdateProjectCtrl::Update()
  200. {
  201. if (m_stack->currentIndex() == ScreenOrder::GemRepos)
  202. {
  203. m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName()));
  204. m_header->setSubTitle(QString(tr("Remote Sources")));
  205. m_nextButton->setVisible(false);
  206. }
  207. else if (m_stack->currentIndex() == ScreenOrder::Gems)
  208. {
  209. m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName()));
  210. m_header->setSubTitle(QString(tr("Configure Gems")));
  211. m_nextButton->setText(tr("Save"));
  212. m_nextButton->setVisible(true);
  213. }
  214. else
  215. {
  216. m_header->setTitle("");
  217. m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName()));
  218. m_nextButton->setText(tr("Save"));
  219. m_nextButton->setVisible(true);
  220. }
  221. }
  222. void UpdateProjectCtrl::UpdateSettingsScreen()
  223. {
  224. m_updateSettingsScreen->SetProjectInfo(m_projectInfo);
  225. }
  226. bool UpdateProjectCtrl::UpdateProjectSettings(bool shouldConfirm)
  227. {
  228. AZ_Assert(m_updateSettingsScreen, "Update settings screen is nullptr.")
  229. ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo();
  230. if (m_projectInfo != newProjectSettings)
  231. {
  232. if (shouldConfirm)
  233. {
  234. QMessageBox::StandardButton questionResult = QMessageBox::question(
  235. this,
  236. QObject::tr("Unsaved changes"),
  237. QObject::tr("Would you like to save your changes to project settings?"),
  238. QMessageBox::No | QMessageBox::Yes
  239. );
  240. if (questionResult == QMessageBox::No)
  241. {
  242. return true;
  243. }
  244. }
  245. if (!m_updateSettingsScreen->Validate())
  246. {
  247. QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings"));
  248. return false;
  249. }
  250. // Move project first to avoid trying to update settings at the new location before it has been moved there
  251. if (QDir(newProjectSettings.m_path) != QDir(m_projectInfo.m_path))
  252. {
  253. if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path, this))
  254. {
  255. QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project."));
  256. return false;
  257. }
  258. }
  259. // Check engine compatibility if a new engine was selected
  260. if (QDir(newProjectSettings.m_enginePath) != QDir(m_projectInfo.m_enginePath))
  261. {
  262. auto incompatibleObjectsResult = PythonBindingsInterface::Get()->GetProjectEngineIncompatibleObjects(newProjectSettings.m_path, newProjectSettings.m_enginePath);
  263. AZStd::string errorTitle, generalError, detailedError;
  264. if (!incompatibleObjectsResult)
  265. {
  266. errorTitle = "Failed to check project compatibility";
  267. generalError = incompatibleObjectsResult.GetError().first;
  268. generalError.append("\nDo you still want to save your changes to project settings?");
  269. detailedError = incompatibleObjectsResult.GetError().second;
  270. }
  271. else if (const auto& incompatibleObjects = incompatibleObjectsResult.GetValue(); !incompatibleObjects.isEmpty())
  272. {
  273. // provide a couple more user friendly error messages for uncommon cases
  274. if (incompatibleObjects.at(0).contains("engine.json", Qt::CaseInsensitive))
  275. {
  276. errorTitle = "Failed to read engine.json";
  277. generalError = "The projects compatibility with the new engine could not be checked because the engine.json could not be read";
  278. }
  279. else if (incompatibleObjects.at(0).contains("project.json", Qt::CaseInsensitive))
  280. {
  281. errorTitle = "Invalid project, failed to read project.json";
  282. generalError = "The projects compatibility with the new engine could not be checked because the project.json could not be read.";
  283. }
  284. else
  285. {
  286. // could be gems, apis or both
  287. errorTitle = "Project may not be compatible with new engine";
  288. generalError = incompatibleObjects.join("\n").toUtf8().constData();
  289. generalError.append("\nDo you still want to save your changes to project settings?");
  290. }
  291. }
  292. if (!generalError.empty())
  293. {
  294. QMessageBox warningDialog(QMessageBox::Warning, errorTitle.c_str(), generalError.c_str(), QMessageBox::Yes | QMessageBox::No, this);
  295. warningDialog.setDetailedText(detailedError.c_str());
  296. if(warningDialog.exec() == QMessageBox::No)
  297. {
  298. return false;
  299. }
  300. AZ_Warning("ProjectManager", false, "Proceeding with saving project settings after engine compatibility check failed.");
  301. }
  302. }
  303. if (auto result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings); !result.IsSuccess())
  304. {
  305. QMessageBox::critical(this, tr("Project update failed"), tr(result.GetError().c_str()));
  306. return false;
  307. }
  308. if (QDir(newProjectSettings.m_path) != QDir(m_projectInfo.m_path) ||
  309. newProjectSettings.m_projectName != m_projectInfo.m_projectName ||
  310. QDir(newProjectSettings.m_enginePath) != QDir(m_projectInfo.m_enginePath))
  311. {
  312. // Remove project build successfully paths for both old and new projects
  313. // because a full rebuild is required when moving projects
  314. auto settings = SettingsInterface::Get();
  315. settings->SetProjectBuiltSuccessfully(m_projectInfo, false);
  316. settings->SetProjectBuiltSuccessfully(newProjectSettings, false);
  317. emit NotifyBuildProject(newProjectSettings);
  318. }
  319. if (!newProjectSettings.m_newPreviewImagePath.isEmpty())
  320. {
  321. if (!ProjectUtils::ReplaceProjectFile(
  322. QDir(newProjectSettings.m_path).filePath(newProjectSettings.m_iconPath), newProjectSettings.m_newPreviewImagePath))
  323. {
  324. QMessageBox::critical(this, tr("File replace failed"), tr("Failed to replace project preview image."));
  325. return false;
  326. }
  327. m_updateSettingsScreen->ResetProjectPreviewPath();
  328. }
  329. m_projectInfo = newProjectSettings;
  330. }
  331. return true;
  332. }
  333. } // namespace O3DE::ProjectManager