CanUseFileMenu.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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 <gtest/gtest.h>
  9. #include <QPushButton>
  10. #include <QAction>
  11. #include <QtTest>
  12. #include <QMessageBox>
  13. #include <QMenu>
  14. #include <QApplication>
  15. #include <QGridLayout>
  16. #include <QDir>
  17. #include <QCheckBox>
  18. #include <Tests/UI/MenuUIFixture.h>
  19. #include <Tests/UI/ModalPopupHandler.h>
  20. #include <EMotionFX/Exporters/ExporterLib/Exporter/Exporter.h>
  21. #include <Tests/TestAssetCode/SimpleActors.h>
  22. #include <Tests/TestAssetCode/ActorFactory.h>
  23. #include <Tests/TestAssetCode/TestActorAssets.h>
  24. #include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
  25. #include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.h>
  26. #include <EMotionFX/Source/AnimGraphReferenceNode.h>
  27. #include <EMotionFX/Source/ActorManager.h>
  28. #include <EMotionFX/CommandSystem/Source/CommandManager.h>
  29. #include <EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.h>
  30. #include <EMotionFX/CommandSystem/Source/MotionCommands.h>
  31. #include <EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphModel.h>
  32. #include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
  33. #include <EMotionStudio/EMStudioSDK/Source/MainWindow.h>
  34. #include <EMotionStudio/EMStudioSDK/Source/SaveChangedFilesManager.h>
  35. #include <EMotionStudio/EMStudioSDK/Source/ResetSettingsDialog.h>
  36. #include <EMotionStudio/EMStudioSDK/Source/FileManager.h>
  37. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphPlugin.h>
  38. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphViewWidget.h>
  39. #include <EMotionStudio/Plugins/StandardPlugins/Source/MotionSetsWindow/MotionSetsWindowPlugin.h>
  40. namespace EMotionFX
  41. {
  42. class CanUseFileMenuUIFixture
  43. : public MenuUIFixture
  44. {
  45. public:
  46. void SetUp() override
  47. {
  48. MenuUIFixture::SetUp();
  49. GetEMotionFX().InitAssetFolderPaths();
  50. m_animGraphPlugin = static_cast<EMStudio::AnimGraphPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::AnimGraphPlugin::CLASS_ID));
  51. ASSERT_TRUE(m_animGraphPlugin);
  52. EMStudio::GetManager()->SetSkipSourceControlCommands(true);
  53. }
  54. void TearDown() override
  55. {
  56. QDir(GetAssetSaveFolder()).removeRecursively();
  57. MenuUIFixture::TearDown();
  58. }
  59. QString GetAssetSaveFolder() const
  60. {
  61. auto testAssetsPath = AZ::IO::Path(GetEMotionFX().GetAssetCacheFolder()) / "tmptestassets";
  62. QString dataDir = QString::fromUtf8(testAssetsPath.c_str(), aznumeric_cast<int>(testAssetsPath.Native().size()));
  63. if (!QDir(dataDir).exists())
  64. {
  65. QDir().mkdir(dataDir);
  66. }
  67. return dataDir;
  68. }
  69. QString GenerateTempAssetFile(const QString& filenamebase, const QString& extension) const
  70. {
  71. bool foundFilename = false;
  72. int fileIndex = 0;
  73. QString baseDir = GetAssetSaveFolder();
  74. while (!foundFilename)
  75. {
  76. const QString filename = QString("%1_%2").arg(filenamebase).arg(fileIndex);
  77. const QString filepath = QString("%1/%2.%3").arg(baseDir, filename, extension);
  78. if (!QFile::exists(filepath))
  79. {
  80. return filepath;
  81. }
  82. fileIndex++;
  83. }
  84. return nullptr;
  85. }
  86. QString GenerateTempAnimGraphFilename() const
  87. {
  88. return GenerateTempAssetFile("tmpanimgraph", "animgraph");
  89. }
  90. QString GenerateTempWorkspaceFilename() const
  91. {
  92. return GenerateTempAssetFile("tmpworkspace", "emfxworkspace");
  93. }
  94. QString GenerateTempActorFilename() const
  95. {
  96. return GenerateTempAssetFile("tmpactor", "actor");
  97. }
  98. QString GenerateTempMotionSetFilename() const
  99. {
  100. return GenerateTempAssetFile("tmpmotionset", "motionset");
  101. }
  102. QString GenerateTempMotionFilename() const
  103. {
  104. return GenerateTempAssetFile("tmpmotion", "motion");
  105. }
  106. void CreateAnimGraph()
  107. {
  108. if (!AnimGraphExists())
  109. {
  110. m_animGraphPlugin->GetViewWidget()->OnCreateAnimGraph();
  111. ASSERT_TRUE(m_animGraphPlugin->GetActiveAnimGraph()) << "Failed to create AnimGraph.";
  112. }
  113. }
  114. void CreateActor()
  115. {
  116. if (EMotionFX::GetActorManager().GetNumActorInstances() == 0)
  117. {
  118. AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}");
  119. AZ::Data::Asset<Integration::ActorAsset> actorAsset =
  120. TestActorAssets::CreateActorAssetAndRegister<SimpleJointChainActor>(actorAssetId, 2, "CanAddSimulatedObjectWithJointsActor");
  121. ActorInstance::Create(actorAsset->GetActor());
  122. EXPECT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to create actor set for reset test.";
  123. }
  124. }
  125. void CreateAndSaveActor(const char* filename) const
  126. {
  127. if (EMotionFX::GetActorManager().GetNumActorInstances() == 0)
  128. {
  129. AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}");
  130. AZ::Data::Asset<Integration::ActorAsset> actorAsset = TestActorAssets::CreateActorAssetAndRegister<SimpleJointChainActor>(
  131. actorAssetId, 2, "CanAddSimulatedObjectWithJointsActor");
  132. ActorInstance::Create(actorAsset->GetActor());
  133. EXPECT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to create actor set for reset test.";
  134. actorAsset->GetActor()->SetFileName(filename);
  135. AZStd::string stringFilename = filename;
  136. ExporterLib::SaveActor(stringFilename, actorAsset->GetActor(), MCore::Endian::ENDIAN_LITTLE);
  137. }
  138. }
  139. void LoadActor(const char* filename, const bool replaceScene)
  140. {
  141. ModalPopupHandler saveDirtyPopupHandler;
  142. saveDirtyPopupHandler.WaitForPopupPressDialogButton<EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Ok);
  143. EMStudio::GetMainWindow()->LoadActor(filename, replaceScene);
  144. }
  145. void CreateMotion()
  146. {
  147. if (EMotionFX::GetMotionManager().GetNumMotions() == 0)
  148. {
  149. LoadTestMotion();
  150. ASSERT_EQ(EMotionFX::GetMotionManager().GetNumMotions(), 1) << "Failed to create motion for reset test.";
  151. }
  152. }
  153. void DeleteAnimGraph()
  154. {
  155. EMotionFX::AnimGraph* animGraph = m_animGraphPlugin->GetActiveAnimGraph();
  156. GetEventManager().OnDeleteAnimGraph(animGraph);
  157. }
  158. bool AnimGraphExists() const
  159. {
  160. EMotionFX::AnimGraph* animGraph = m_animGraphPlugin->GetActiveAnimGraph();
  161. return animGraph;
  162. }
  163. void SaveCurrentAnimGraph(const QString& filename)
  164. {
  165. // Set the save filename to avoid a file select dialog.
  166. EMotionFX::AnimGraph* animGraph = m_animGraphPlugin->GetActiveAnimGraph();
  167. animGraph->SetFileName(filename.toUtf8().constData());
  168. m_animGraphPlugin->OnFileSave();
  169. ASSERT_TRUE(QFile::exists(filename)) << "Failed to save AnimGraph.";
  170. }
  171. void TestActorMenus(QMenu* fileMenu)
  172. {
  173. // Open Actor
  174. // We can't use the Open Actor menu item as it would involve interacting with a system requestor, so just do what the menu option does internally.
  175. //Clear any existing actors before we start.
  176. QAction* resetAction = GetResetMenuAction(fileMenu);
  177. ASSERT_TRUE(resetAction) << "Reset menu item not found";
  178. TestResetMenuItem(resetAction, "EMFX.ResetSettingsDialog.Actors");
  179. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 0) << "Failed to reset Actors.";
  180. // Create an actor and save it so we can reload it for the merge step.
  181. const QString actorFilename = GenerateTempActorFilename();
  182. CreateAndSaveActor(actorFilename.toUtf8().data());
  183. // Clear out the existing actors so we can tell whether the load works.
  184. TestResetMenuItem(resetAction, "EMFX.ResetSettingsDialog.Actors");
  185. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 0) << "Failed to reset Actors.";
  186. // Load the actor we just saved, with replaceScene set to true to represent a load.
  187. LoadActor(actorFilename.toUtf8().data(), true);
  188. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to load Actor.";
  189. // Do it again to verify that number of actors stays the same when replaceScene is true.
  190. LoadActor(actorFilename.toUtf8().data(), true);
  191. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to load Actor.";
  192. // Now load again, but with replaceScene set to false, as the merge code does.
  193. LoadActor(actorFilename.toUtf8().data(), false);
  194. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 2) << "Failed to merge Actor.";
  195. // We can't test Save Selected Actor as we would it would involve mocking source scene handling.
  196. // Add the filename to the recent actorsa anyway, so we can test that functionality.
  197. EMStudio::GetMainWindow()->AddRecentActorFile(actorFilename);
  198. // Check for the file saved in the Save test to be listed in the recent actors submenu.
  199. const QList <QMenu*> recentMenus = fileMenu->findChildren<QMenu*>("EMFX.MainWindow.RecentFilesMenu");
  200. QMenu* recentActorsMenu = nullptr;
  201. for (QMenu* recentMenu : recentMenus)
  202. {
  203. if (recentMenu->title() == "Recent Actors")
  204. {
  205. recentActorsMenu = recentMenu;
  206. }
  207. }
  208. ASSERT_TRUE(recentActorsMenu) << "Unable to find recent actors menu.";
  209. const QList<QAction*> actions = recentActorsMenu->findChildren<QAction *>();
  210. QAction* recentAction = nullptr;
  211. for (QAction* action : actions)
  212. {
  213. if (IsActionRecentlySavedActor(action->text(), actorFilename))
  214. {
  215. recentAction = action;
  216. }
  217. }
  218. ASSERT_TRUE(recentAction) << "Recent action for last saved actor not found.";
  219. recentAction->trigger();
  220. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to load recent Actor.";
  221. QAction* resetRecentAction = recentActorsMenu->findChild<QAction *>("EMFX.RecentFiles.ResetRecentFilesAction");
  222. ASSERT_TRUE(resetRecentAction) << "Reset recent actors action not found.";
  223. resetRecentAction->trigger();
  224. const QList<QAction*> actionsAfterReset = recentActorsMenu->findChildren<QAction *>();
  225. ASSERT_EQ(actionsAfterReset.size(), 1) << "Failed to reset recent items menu.";
  226. }
  227. bool IsActionRecentlySavedActor(const QString& actionTitle, const QString& actorFilename) const
  228. {
  229. if (actionTitle.isEmpty())
  230. {
  231. return false;
  232. }
  233. QFileInfo fileInfo(actionTitle);
  234. QString fileName = fileInfo.fileName();
  235. // Remove the shortcut at the start.
  236. const int shortcutLen = fileName.indexOf(" ");
  237. fileName = fileName.mid(shortcutLen + 1);
  238. return actorFilename.endsWith(fileName);
  239. }
  240. void TestSaveWorkspaceMenuOption([[maybe_unused]] QMenu* fileMenu, const QString& workspaceFilename)
  241. {
  242. EMStudio::Workspace* workspace = EMStudio::GetManager()->GetWorkspace();
  243. ASSERT_TRUE(workspace) << "Current workspace not found";
  244. // Create an anim graph so that there is unsaved data
  245. CreateAnimGraph();
  246. // The workspace needs to have a file to save to, as we can't interact with the SaveAs dialog.
  247. workspace->SetFilename(workspaceFilename.toUtf8().constData());
  248. // If we try to save now, we'll be asked to select a save file for the anim graph, we need to provide one to avoid that.
  249. const QString animGraphFilename = GenerateTempAnimGraphFilename();
  250. SaveCurrentAnimGraph(animGraphFilename);
  251. // Pretend editing the anim graph
  252. EMotionFX::AnimGraph* animGraph = m_animGraphPlugin->GetActiveAnimGraph();
  253. animGraph->SetDirtyFlag(true);
  254. // Skip the motion set.
  255. GetMotionManager().GetMotionSet(0)->SetDirtyFlag(false);
  256. // Prepare a watcher to press the ok button when the SaveDirtySettingsWindow appears.
  257. ModalPopupHandler saveDirtyPopupHandler;
  258. saveDirtyPopupHandler.WaitForPopupPressDialogButton<EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Ok);
  259. EMStudio::GetMainWindow()->OnFileSaveWorkspace();
  260. ASSERT_TRUE(saveDirtyPopupHandler.GetSeenTargetWidget()) << "Expected SaveDirtySettingsWindow not found.";
  261. ASSERT_TRUE(QFile::exists(workspaceFilename)) << "Workspace save failed.";
  262. }
  263. bool IsActionRecentlySavedWorkspace(const QString& actionTitle) const
  264. {
  265. if (actionTitle.isEmpty())
  266. {
  267. return false;
  268. }
  269. QFileInfo fileInfo(actionTitle);
  270. QString fileName = fileInfo.fileName();
  271. // Remove the shortcut at the start.
  272. const int shortcutLen = fileName.indexOf(" ");
  273. fileName = fileName.mid(shortcutLen + 1);
  274. return m_lastSavedWorkspaceFilename.endsWith(fileName);
  275. }
  276. void TestNewWorkspaceMenuOption(QMenu* fileMenu)
  277. {
  278. // Create an anim graph so that there is unsaved data
  279. CreateAnimGraph();
  280. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  281. // Test 1. Select New Workspace, press cancel in the SaveDirtySettingsWindow.
  282. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  283. // Start waiting for the "do you want to save" dialog, then press Cancel.
  284. ModalPopupHandler saveDirtyPopupHandler;
  285. saveDirtyPopupHandler.WaitForPopupPressDialogButton< EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Cancel);
  286. // Trigger the new workspace menu option
  287. QAction* newWorkspaceAction = MenuUIFixture::FindMenuActionWithObjectName(fileMenu, "EMFX.MainWindow.NewWorkspaceAction", "EMFX.MainWindow.FileMenu");
  288. ASSERT_TRUE(newWorkspaceAction);
  289. newWorkspaceAction->trigger();
  290. // If the dialog does not appear, we might get here before the callback is triggered, so check that it was.
  291. ASSERT_TRUE(saveDirtyPopupHandler.GetSeenTargetWidget()) << "Expected SaveDirtySettingsWindow not found.";
  292. // The dialog should now be gone but the anim graph we made should still exist.
  293. ASSERT_FALSE(QApplication::activeModalWidget()) << "SaveDirtySettingsWindow failed to close.";
  294. ASSERT_TRUE(AnimGraphExists()) << "AnimGraph not found.";
  295. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  296. // Test 2. Select New Workspace, press Discard in the SaveDirtySettingsWindow.
  297. // Then press No in the new workspace confirmation dialog.
  298. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  299. // Start waiting for the "do you want to save" dialog, then press Discard.
  300. saveDirtyPopupHandler.WaitForPopupPressDialogButton< EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Discard);
  301. // Start waiting for the "really make new workspace?" confirmation popup, then press No.
  302. ModalPopupHandler messageBoxPopupHandler;
  303. messageBoxPopupHandler.WaitForPopupPressDialogButton<QMessageBox*>(QDialogButtonBox::No);
  304. newWorkspaceAction->trigger();
  305. saveDirtyPopupHandler.WaitForCompletion();
  306. messageBoxPopupHandler.WaitForCompletion();
  307. // If the dialog does not appear, we might get here before the callback is triggered, so check that it was.
  308. ASSERT_TRUE(saveDirtyPopupHandler.GetSeenTargetWidget()) << "Expected SaveDirtySettingsWindow not found.";
  309. ASSERT_TRUE(messageBoxPopupHandler.GetSeenTargetWidget()) << "Expected QMessageBox not found.";
  310. // The dialog should now be gone and the anim graph we made should still be there.
  311. ASSERT_FALSE(QApplication::activeModalWidget()) << "SaveDirtySettingsWindow failed to close.";
  312. ASSERT_TRUE(AnimGraphExists()) << "AnimGraph not found.";
  313. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  314. // Test 3. Select New Workspace, press discard in the SaveDirtySettingsWindow.
  315. // Then press Yes in the new workspace confirmation dialog.
  316. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  317. // Start waiting for the "do you want to save" dialog, then press Discard.
  318. saveDirtyPopupHandler.WaitForPopupPressDialogButton< EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Discard);
  319. // Start waiting for the "really make new workspace?" confirmation popup, then press Yes.
  320. messageBoxPopupHandler.WaitForPopupPressDialogButton<QMessageBox*>(QDialogButtonBox::Yes);
  321. newWorkspaceAction->trigger();
  322. // If the dialog does not appear, we might get here before the callback is triggered, so check that it did.
  323. ASSERT_TRUE(saveDirtyPopupHandler.GetSeenTargetWidget()) << "Expected SaveDirtySettingsWindow not found.";
  324. ASSERT_TRUE(messageBoxPopupHandler.GetSeenTargetWidget()) << "Expected QMessageBox not found.";
  325. // There should be no active popup.
  326. ASSERT_FALSE(QApplication::activeModalWidget()) << "SaveDirtySettingsWindow failed to close.";
  327. // The AnimGraph should be removed.
  328. ASSERT_FALSE(AnimGraphExists()) << "AnimGraph not removed.";
  329. }
  330. void TestRecentWorkspacesMenuOption(QMenu* fileMenu)
  331. {
  332. // Remove the AnimGraph so that we can tell the workspace has been reloaded correctly.
  333. DeleteAnimGraph();
  334. ASSERT_FALSE(AnimGraphExists()) << "AnimGraph not removed.";
  335. // Check for the file saved in the Save test to be listed.
  336. const QList <QMenu*> recentMenus = fileMenu->findChildren<QMenu*>("EMFX.MainWindow.RecentFilesMenu");
  337. QMenu* recentWorkspacesMenu = nullptr;
  338. for (QMenu* recentMenu : recentMenus)
  339. {
  340. if (recentMenu->title() == "Recent Workspaces")
  341. {
  342. recentWorkspacesMenu = recentMenu;
  343. }
  344. }
  345. ASSERT_TRUE(recentWorkspacesMenu) << "Unable to find recent workspaces menu.";
  346. const QList<QAction*> actions = recentWorkspacesMenu->findChildren<QAction *>();
  347. QAction* recentAction = nullptr;
  348. for (QAction* action : actions)
  349. {
  350. if (IsActionRecentlySavedWorkspace(action->text()))
  351. {
  352. recentAction = action;
  353. }
  354. }
  355. ASSERT_TRUE(recentAction) << "Recent action for last saved workspace not found.";
  356. // Now try to use the action to reload the workspace.
  357. // As we've deleted the AnimGraph, we'll be asked about saving changes, discard them.
  358. ModalPopupHandler saveDirtyPopupHandler;
  359. saveDirtyPopupHandler.WaitForPopupPressDialogButton< EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Discard);
  360. recentAction->trigger();
  361. ASSERT_TRUE(AnimGraphExists()) << "AnimGraph not found after reloading recent workspace.";
  362. // Test the clear recent items action.
  363. QAction* resetRecentAction = recentWorkspacesMenu->findChild<QAction *>("EMFX.RecentFiles.ResetRecentFilesAction");
  364. ASSERT_TRUE(resetRecentAction) << "Reset recent workspaces action not found.";
  365. resetRecentAction->trigger();
  366. const QList<QAction*> actionsAfterReset = recentWorkspacesMenu->findChildren<QAction *>();
  367. ASSERT_EQ(actionsAfterReset.size(), 1) << "Failed to reset workspaces items menu.";
  368. }
  369. void CreateDataForResetTest()
  370. {
  371. CreateActor();
  372. CreateMotion();
  373. CreateAnimGraph();
  374. }
  375. void TestResetMenuItem(QAction* resetMenuAction, const QString& resetItemName)
  376. {
  377. // Set up a callback to set the correct checkbox states when the ResetSettings dialog appears.
  378. WidgetActiveCallback resetSettingsCallback = [resetItemName](QWidget* widget)
  379. {
  380. ASSERT_TRUE(widget) << "Failed to find Reset widget.";
  381. if (resetItemName == "*")
  382. {
  383. // Set all checkboxes
  384. QList<QCheckBox*>checkBoxes = widget->findChildren<QCheckBox*>();
  385. for (QCheckBox *checkBox : checkBoxes)
  386. {
  387. checkBox->setChecked(true);
  388. }
  389. }
  390. else
  391. {
  392. // Reset all checkboxes
  393. QList<QCheckBox*>checkBoxes = widget->findChildren<QCheckBox*>();
  394. for (QCheckBox *checkBox : checkBoxes)
  395. {
  396. QString on = checkBox->objectName();
  397. checkBox->setChecked(false);
  398. }
  399. // Just tick the one we want
  400. QCheckBox* checkBox = widget->findChild<QCheckBox*>(resetItemName);
  401. ASSERT_TRUE(checkBox) << "Failed to find reset item checkbox ";
  402. checkBox->setChecked(true);
  403. }
  404. // Press the Ok button
  405. QDialogButtonBox* buttonBox = widget->findChild< QDialogButtonBox*>();
  406. ASSERT_TRUE(buttonBox) << "Unable to find button box in ResetSettingsDialog";
  407. QAbstractButton* button = buttonBox->button(QDialogButtonBox::Ok);
  408. ASSERT_TRUE(button) << "Unable to find Ok button in ResetSettingsDialog";
  409. QTest::mouseClick(button, Qt::LeftButton);
  410. };
  411. // Setup a watcher to handle the save dirty settings dialog by pressing Discard.
  412. ModalPopupHandler saveDirtyPopupHandler;
  413. saveDirtyPopupHandler.WaitForPopupPressDialogButton< EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Discard);
  414. // Setup a handler to select the type to reset and press ok.
  415. ModalPopupHandler resetSettingsHandler;
  416. resetSettingsHandler.WaitForPopup<EMStudio::ResetSettingsDialog*>(resetSettingsCallback);
  417. resetMenuAction->trigger();
  418. saveDirtyPopupHandler.WaitForCompletion();
  419. resetSettingsHandler.WaitForCompletion();
  420. }
  421. QString GetTestMotionFileName() const
  422. {
  423. AZStd::string resolvedAssetPath = this->ResolvePath("@gemroot:EMotionFX@/Code/Tests/TestAssets/Rin/rin_idle.motion");
  424. return QString::fromUtf8(resolvedAssetPath.data(), aznumeric_cast<int>(resolvedAssetPath.size()));
  425. }
  426. void LoadTestMotion()
  427. {
  428. const QString testFile = GetTestMotionFileName();
  429. ASSERT_TRUE(QFile::exists(testFile)) << "Failed to find motion file asset.";
  430. AZStd::vector<AZStd::string> motionFilenames;
  431. motionFilenames.push_back(testFile.toUtf8().data());
  432. CommandSystem::LoadMotionsCommand(motionFilenames);
  433. }
  434. QAction* GetResetMenuAction(const QMenu* fileMenu) const
  435. {
  436. return MenuUIFixture::FindMenuActionWithObjectName(fileMenu, "EMFX.MainWindow.ResetAction", fileMenu->objectName());
  437. }
  438. void TestResetMenuItem(QMenu* fileMenu)
  439. {
  440. QAction* resetAction = GetResetMenuAction(fileMenu);
  441. ASSERT_TRUE(resetAction) << "Reset menu item not found";
  442. ASSERT_TRUE(resetAction->isEnabled()) << "Reset menu action is disabled.";
  443. // Make one of everything to reset
  444. CreateDataForResetTest();
  445. // Reset them one at a time:
  446. {
  447. // Actors
  448. TestResetMenuItem(resetAction, "EMFX.ResetSettingsDialog.Actors");
  449. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 0) << "Failed to reset Actors.";
  450. // Motions
  451. TestResetMenuItem(resetAction, "EMFX.ResetSettingsDialog.Motions");
  452. ASSERT_EQ(EMotionFX::GetMotionManager().GetNumMotions(), 0) << "Failed to reset Motions.";
  453. // Motion Sets
  454. TestResetMenuItem(resetAction, "EMFX.ResetSettingsDialog.MotionSets");
  455. ASSERT_EQ(EMotionFX::GetMotionManager().GetNumMotionSets(), 1) << "Failed to reset MotionSets. Default motion set should be present.";
  456. // AnimGraphs
  457. TestResetMenuItem(resetAction, "EMFX.ResetSettingsDialog.AnimGraphs");
  458. ASSERT_FALSE(m_animGraphPlugin->GetActiveAnimGraph()) << "Failed to reset AnimGraphs.";
  459. }
  460. // The reset menu item should be disabled as there is nothing to reset.
  461. ASSERT_FALSE(resetAction->isEnabled()) << "Reset menu action is enabled after resetting all items.";
  462. // Recreate test data
  463. CreateDataForResetTest();
  464. // Reset them all at once
  465. {
  466. TestResetMenuItem(resetAction, "*");
  467. ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 0) << "Failed to reset Actors.";
  468. ASSERT_EQ(EMotionFX::GetMotionManager().GetNumMotions(), 0) << "Failed to reset Motions.";
  469. ASSERT_EQ(EMotionFX::GetMotionManager().GetNumMotionSets(), 1) << "Failed to reset MotionSets. Default motion set should be present.";
  470. ASSERT_FALSE(m_animGraphPlugin->GetActiveAnimGraph()) << "Failed to reset AnimGraphs.";
  471. }
  472. }
  473. void TestSaveAllMenuItem(QMenu* fileMenu)
  474. {
  475. // Use the test reset menu item to ensure everything is cleared out.
  476. QAction* resetAction = GetResetMenuAction(fileMenu);
  477. ASSERT_TRUE(resetAction) << "Reset menu item not found";
  478. TestResetMenuItem(resetAction, "*");
  479. // Make new sets of all data types, give them a unique filename and set them as dirty.
  480. CreateAnimGraph();
  481. const QString animGraphFilename = GenerateTempAnimGraphFilename();
  482. EMotionFX::AnimGraph* animGraph = m_animGraphPlugin->GetActiveAnimGraph();
  483. animGraph->SetFileName(animGraphFilename.toUtf8().constData());
  484. animGraph->SetDirtyFlag(true);
  485. const QString motionsetFilename = GenerateTempMotionSetFilename();
  486. EMotionFX::MotionSet *motionSet = EMotionFX::GetMotionManager().GetMotionSet(0);
  487. motionSet->SetFilename(motionsetFilename.toUtf8().constData());
  488. motionSet->SetDirtyFlag(true);
  489. // Don't create an actor or motion as we can't save that due to source scene requirements.
  490. EMStudio::Workspace* workspace = EMStudio::GetManager()->GetWorkspace();
  491. const QString workspaceFilename = GenerateTempWorkspaceFilename();
  492. workspace->SetFilename(workspaceFilename.toUtf8().data());
  493. workspace->SetDirtyFlag(true);
  494. ModalPopupHandler saveDirtyPopupHandler;
  495. saveDirtyPopupHandler.WaitForPopupPressDialogButton< EMStudio::SaveDirtySettingsWindow*>(QDialogButtonBox::Ok);
  496. QAction* saveAllAction = MenuUIFixture::FindMenuActionWithObjectName(fileMenu, "EMFX.MainWindow.SaveAllAction", fileMenu->objectName());
  497. ASSERT_TRUE(saveAllAction) << "Save All menu item not found";
  498. ASSERT_TRUE(saveAllAction->isEnabled());
  499. saveAllAction->trigger();
  500. ASSERT_TRUE(QFile::exists(animGraphFilename)) << "Failed to save AnimGraph in SaveAll action.";
  501. ASSERT_TRUE(QFile::exists(motionsetFilename)) << "Failed to save MotionSet in SaveAll action.";
  502. ASSERT_TRUE(QFile::exists(workspaceFilename)) << "Failed to save Workspace in SaveAll action.";
  503. }
  504. void TestWorkspaceMenuItems(QMenu* fileMenu)
  505. {
  506. m_lastSavedWorkspaceFilename = GenerateTempWorkspaceFilename();
  507. TestNewWorkspaceMenuOption(fileMenu);
  508. // We can't test Open Workspace as it requires interacting with a system file dialog.
  509. TestSaveWorkspaceMenuOption(fileMenu, m_lastSavedWorkspaceFilename);
  510. // We can't test Save As as it requires interacting with a system file dialog.
  511. TestRecentWorkspacesMenuOption(fileMenu);
  512. }
  513. private:
  514. EMStudio::AnimGraphPlugin* m_animGraphPlugin = nullptr;
  515. QString m_lastSavedWorkspaceFilename;
  516. };
  517. TEST_F(CanUseFileMenuUIFixture, CanUseFileMenu)
  518. {
  519. RecordProperty("test_case_id", "C1698601");
  520. RecordProperty("test_case_id", "C16302183");
  521. RecordProperty("test_case_id", "C1698617");
  522. // Find the File menu.
  523. QMenu* fileMenu = MenuUIFixture::FindMainMenuWithName("EMFX.MainWindow.FileMenu");
  524. ASSERT_TRUE(fileMenu) << "Unable to find file menu.";
  525. TestWorkspaceMenuItems(fileMenu);
  526. TestResetMenuItem(fileMenu);
  527. // Temporarily disable loading actor test.
  528. // This is because the importer command now load actor asset instead of reading from disk. We do not want to add dependency to the asset processor
  529. // in this test.
  530. // TestActorMenus(fileMenu);
  531. TestSaveAllMenuItem(fileMenu);
  532. }
  533. } // namespace EMotionFX