3
0

UIFixture.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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 <Tests/UI/UIFixture.h>
  9. #include <Tests/UI/ModalPopupHandler.h>
  10. #include <Tests/Mocks/AtomRenderPlugin.h>
  11. #include <Tests/Mocks/PhysicsSystem.h>
  12. #include <Tests/D6JointLimitConfiguration.h>
  13. #include <Integration/System/SystemCommon.h>
  14. #include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
  15. #include <EMotionStudio/EMStudioSDK/Source/PluginManager.h>
  16. #include <EMotionStudio/Plugins/StandardPlugins/Source/MotionSetsWindow/MotionSetsWindowPlugin.h>
  17. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphPlugin.h>
  18. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/BlendGraphWidget.h>
  19. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.h>
  20. #include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterCreateEditWidget.h>
  21. #include <AzCore/IO/Path/Path.h>
  22. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  23. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  24. #include <AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx>
  25. #include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
  26. #include <AzQtComponents/Components/Titlebar.h>
  27. #include <AzQtComponents/Components/DockBarButton.h>
  28. #include <AzQtComponents/Components/WindowDecorationWrapper.h>
  29. #include <AzQtComponents/Components/StyleManager.h>
  30. #include <GraphCanvas/Widgets/NodePalette/NodePaletteTreeView.h>
  31. #include <Editor/Plugins/ColliderWidgets/SimulatedObjectColliderWidget.h>
  32. #include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
  33. #include <Editor/Plugins/SimulatedObject/SimulatedJointWidget.h>
  34. #include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.h>
  35. #include <Editor/ReselectingTreeView.h>
  36. #include <QtTest>
  37. #include <QAbstractItemModel>
  38. #include <QApplication>
  39. #include <QWidget>
  40. #include <QToolBar>
  41. #include <QPushButton>
  42. namespace EMotionFX
  43. {
  44. void MakeQtApplicationBase::SetUp()
  45. {
  46. m_uiApp = new QApplication(s_argc, nullptr);
  47. AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::NotifyRegisterViews);
  48. AZ::IO::FixedMaxPath engineRootPath;
  49. if (auto settingsRegistry = AZ::SettingsRegistry::Get())
  50. {
  51. settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  52. }
  53. (new AzQtComponents::StyleManager(m_uiApp))->initialize(m_uiApp, engineRootPath);
  54. }
  55. MakeQtApplicationBase::~MakeQtApplicationBase()
  56. {
  57. delete m_uiApp;
  58. }
  59. void UIFixture::SetupQtAndFixtureBase()
  60. {
  61. UIFixtureBase::SetUp();
  62. MakeQtApplicationBase::SetUp();
  63. // Set ignore visibilty so that the visibility check can be ignored in plugins
  64. EMStudio::GetManager()->SetIgnoreVisibility(true);
  65. }
  66. void UIFixture::SetupPluginWindows()
  67. {
  68. // Plugins have to be created after both the QApplication object and after the SystemComponent
  69. const EMStudio::PluginManager::PluginVector& registeredPlugins = EMStudio::GetPluginManager()->GetRegisteredPlugins();
  70. for (EMStudio::EMStudioPlugin* plugin : registeredPlugins)
  71. {
  72. EMStudio::GetPluginManager()->CreateWindowOfType(plugin->GetName());
  73. }
  74. m_skeletonOutlinerPlugin = EMStudio::GetPluginManager()->FindActivePlugin<EMotionFX::SkeletonOutlinerPlugin>();
  75. m_simulatedObjectPlugin = EMStudio::GetPluginManager()->FindActivePlugin<EMotionFX::SimulatedObjectWidget>();
  76. m_animGraphPlugin = EMStudio::GetPluginManager()->FindActivePlugin<EMStudio::AnimGraphPlugin>();
  77. }
  78. void UIFixture::ReflectMockedSystems()
  79. {
  80. if (ShouldReflectPhysicSystem())
  81. {
  82. AZ::SerializeContext* serializeContext = GetSerializeContext();
  83. Physics::MockPhysicsSystem::Reflect(serializeContext); // Required by Ragdoll plugin to fake PhysX Gem is available
  84. D6JointLimitConfiguration::Reflect(serializeContext);
  85. }
  86. }
  87. void UIFixture::OnRegisterPlugin()
  88. {
  89. EMStudio::PluginManager* pluginManager = EMStudio::EMStudioManager::GetInstance()->GetPluginManager();
  90. pluginManager->RegisterPlugin(new EMStudio::MockAtomRenderPlugin());
  91. }
  92. void UIFixture::SetUp()
  93. {
  94. Integration::SystemNotificationBus::Handler::BusConnect();
  95. using namespace testing;
  96. SetupQtAndFixtureBase();
  97. ReflectMockedSystems();
  98. SetupPluginWindows();
  99. ON_CALL(m_assetSystemRequestMock, GetFullSourcePathFromRelativeProductPath(_, _))
  100. .WillByDefault(Return(true));
  101. m_assetSystemRequestMock.BusConnect();
  102. }
  103. void UIFixture::TearDown()
  104. {
  105. m_assetSystemRequestMock.BusDisconnect();
  106. Integration::SystemNotificationBus::Handler::BusDisconnect();
  107. CloseAllNotificationWindows();
  108. DeselectAllAnimGraphNodes();
  109. // Restore visibility
  110. EMStudio::GetManager()->SetIgnoreVisibility(false);
  111. UIFixtureBase::TearDown();
  112. }
  113. QWidget* UIFixture::FindTopLevelWidget(const QString& objectName)
  114. {
  115. //const QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); // TODO: Check why QDialogs are no windows anymore and thus the topLevelWidgets() does not include them.
  116. const QWidgetList topLevelWidgets = QApplication::allWidgets();
  117. auto iterator = AZStd::find_if(topLevelWidgets.begin(), topLevelWidgets.end(),
  118. [objectName](const QWidget* widget)
  119. {
  120. return (widget->objectName() == objectName);
  121. });
  122. if (iterator != topLevelWidgets.end())
  123. {
  124. return *iterator;
  125. }
  126. return nullptr;
  127. }
  128. QWidget* UIFixture::GetWidgetFromToolbar(const QToolBar* toolbar, const QString &widgetText)
  129. {
  130. /*
  131. Searches a Toolbar for an action whose text exactly matches the widgetText parameter.
  132. Returns the widget by pointer if found, nullptr otherwise.
  133. */
  134. for (QAction* action : toolbar->actions())
  135. {
  136. if (action->text() == widgetText)
  137. {
  138. return toolbar->widgetForAction(action);
  139. }
  140. }
  141. return nullptr;
  142. }
  143. QWidget* UIFixture::GetWidgetFromToolbarWithObjectName(const QToolBar* toolbar, const QString &objectName)
  144. {
  145. for (QAction* action : toolbar->actions())
  146. {
  147. if (action->objectName() == objectName)
  148. {
  149. return toolbar->widgetForAction(action);
  150. }
  151. }
  152. return nullptr;
  153. }
  154. QWidget* UIFixture::GetWidgetWithNameFromNamedToolbar(const QWidget* widget, const QString &toolBarName, const QString &objectName)
  155. {
  156. auto toolBar = widget->findChild<QToolBar*>(toolBarName);
  157. if (!toolBar)
  158. {
  159. return nullptr;
  160. }
  161. return UIFixture::GetWidgetFromToolbarWithObjectName(toolBar, objectName);
  162. }
  163. QAction* UIFixture::GetNamedAction(const QWidget* widget, const QString& actionText)
  164. {
  165. const QList<QAction*> actions = widget->findChildren<QAction*>();
  166. for (QAction* action : actions)
  167. {
  168. if (action->text() == actionText)
  169. {
  170. return action;
  171. }
  172. }
  173. return nullptr;
  174. }
  175. QModelIndex UIFixture::GetIndexFromName(const GraphCanvas::NodePaletteTreeView* tree, const QString& name)
  176. {
  177. const QAbstractItemModel* model = tree->model();
  178. const QModelIndexList matches = model->match(model->index(0,0), Qt::DisplayRole, name, 1, Qt::MatchRecursive);
  179. if (!matches.empty())
  180. {
  181. return matches[0];
  182. }
  183. return {};
  184. }
  185. void UIFixture::ExecuteCommands(std::vector<std::string> commands)
  186. {
  187. AZStd::string result;
  188. for (const auto& commandStr : commands)
  189. {
  190. if (commandStr == "UNDO")
  191. {
  192. EXPECT_TRUE(CommandSystem::GetCommandManager()->Undo(result)) << "Undo: " << result.c_str();
  193. }
  194. else if (commandStr == "REDO")
  195. {
  196. EXPECT_TRUE(CommandSystem::GetCommandManager()->Redo(result)) << "Redo: " << result.c_str();
  197. }
  198. else
  199. {
  200. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(commandStr.c_str(), result)) << commandStr.c_str() << ": " << result.c_str();
  201. }
  202. }
  203. }
  204. bool UIFixture::GetActionFromContextMenu(QAction*& action, const QMenu* contextMenu, const QString& actionName)
  205. {
  206. const auto contextMenuActions = contextMenu->actions();
  207. auto contextAction = AZStd::find_if(contextMenuActions.begin(), contextMenuActions.end(), [actionName](const QAction* action) {
  208. return action->text() == actionName;
  209. });
  210. action = *contextAction;
  211. return contextAction != contextMenuActions.end();
  212. }
  213. void UIFixture::CloseAllPlugins()
  214. {
  215. m_skeletonOutlinerPlugin = nullptr;
  216. const EMStudio::PluginManager::PluginVector plugins = EMStudio::GetPluginManager()->GetActivePlugins();
  217. for (EMStudio::EMStudioPlugin* plugin : plugins)
  218. {
  219. EMStudio::GetPluginManager()->RemoveActivePlugin(plugin);
  220. }
  221. }
  222. void UIFixture::CloseAllNotificationWindows()
  223. {
  224. while (EMStudio::GetManager()->GetNotificationWindowManager()->GetNumNotificationWindow() > 0)
  225. {
  226. EMStudio::NotificationWindow* window = EMStudio::GetManager()->GetNotificationWindowManager()->GetNotificationWindow(0);
  227. delete window;
  228. }
  229. }
  230. void UIFixture::DeselectAllAnimGraphNodes()
  231. {
  232. // Unselectany selected anim graph nodes.
  233. EMStudio::AnimGraphPlugin*animGraphPlugin = static_cast<EMStudio::AnimGraphPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::AnimGraphPlugin::CLASS_ID));
  234. if (!animGraphPlugin)
  235. {
  236. return;
  237. }
  238. EMStudio::BlendGraphWidget* graphWidget = animGraphPlugin->GetGraphWidget();
  239. if (!graphWidget)
  240. {
  241. return;
  242. }
  243. EMStudio::NodeGraph* nodeGraph = graphWidget->GetActiveGraph();
  244. if (!nodeGraph)
  245. {
  246. return;
  247. }
  248. nodeGraph->UnselectAllNodes();
  249. ASSERT_EQ(nodeGraph->GetSelectedAnimGraphNodes().size(), 0) << "No node is selected";
  250. }
  251. void UIFixture::BringUpContextMenu(QObject* widget, const QPoint& pos, const QPoint& globalPos)
  252. {
  253. QContextMenuEvent cme(QContextMenuEvent::Mouse, pos, globalPos);
  254. QSpontaneKeyEvent::setSpontaneous(&cme);
  255. QApplication::instance()->notify(
  256. widget,
  257. &cme
  258. );
  259. }
  260. void UIFixture::BringUpContextMenu(const QTreeView* treeView, const QRect& rect)
  261. {
  262. BringUpContextMenu(treeView->viewport(), rect.center(), treeView->viewport()->mapTo(treeView->window(), rect.center()));
  263. }
  264. void UIFixture::BringUpContextMenu(const QTreeWidget* treeWidget, const QRect& rect)
  265. {
  266. QContextMenuEvent cme(QContextMenuEvent::Mouse, rect.center(), treeWidget->viewport()->mapTo(treeWidget->window(), rect.center()));
  267. QSpontaneKeyEvent::setSpontaneous(&cme);
  268. QApplication::instance()->notify(
  269. treeWidget->viewport(),
  270. &cme
  271. );
  272. }
  273. void UIFixture::SelectIndexes(const QModelIndexList& indexList, QTreeView* treeView, const int start, const int end)
  274. {
  275. QItemSelection selection;
  276. for (int i = start; i <= end; ++i)
  277. {
  278. const QModelIndex index = indexList[i];
  279. EXPECT_TRUE(index.isValid()) << "Unable to find a model index for the joint of the actor";
  280. selection.select(index, index);
  281. }
  282. treeView->selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
  283. treeView->scrollTo(indexList[end]);
  284. }
  285. AzToolsFramework::PropertyRowWidget* UIFixture::GetNamedPropertyRowWidgetFromReflectedPropertyEditor(AzToolsFramework::ReflectedPropertyEditor* rpe, const QString& name)
  286. {
  287. // Search through the RPE's widgets to find the matching widget
  288. const AzToolsFramework::ReflectedPropertyEditor::WidgetList& widgets = rpe->GetWidgets();
  289. const auto foundWidget = AZStd::find_if(widgets.begin(), widgets.end(), [name](const auto& widgetIter)
  290. {
  291. return name == widgetIter.second->label();
  292. });
  293. return foundWidget != widgets.end() ? foundWidget->second : nullptr;
  294. }
  295. void UIFixture::TriggerContextMenuAction(QWidget* widget, const QString& actionname)
  296. {
  297. BringUpContextMenu(widget, QPoint(10, 10), widget->mapToGlobal(QPoint(10, 10)));
  298. QMenu* menu = widget->findChild<QMenu*>();
  299. ASSERT_TRUE(menu) << "Unable to find context menu.";
  300. QAction* action = menu->findChild<QAction*>(actionname);
  301. ASSERT_TRUE(action) << "Unable to find context menu action " << actionname.toUtf8().data();
  302. action->trigger();
  303. menu->close();
  304. }
  305. void UIFixture::TriggerModalContextMenuAction(QWidget* widget, const QString& actionname)
  306. {
  307. ModalPopupHandler modalPopupHandler;
  308. bool actionComplete = false;
  309. // Set up an action to be called when the menu is either triggered or a timeout occurs.
  310. ActionCompletionCallback completionCallback = [&actionComplete, actionname](const QString& menu)
  311. {
  312. ASSERT_STREQ(menu.toUtf8().constData(), actionname.toUtf8().constData());
  313. actionComplete = true;
  314. };
  315. modalPopupHandler.ShowContextMenuAndTriggerAction(widget, actionname, 3000, completionCallback);
  316. // Shouldn't get here without the action being complete, but just to be safe...
  317. // Cast to void to avoid nodiscard compiler warning.
  318. static_cast<void>(QTest::qWaitFor([actionComplete]() {
  319. return actionComplete;
  320. }, 10000));
  321. }
  322. AzQtComponents::WindowDecorationWrapper* UIFixture::GetDecorationWrapperForMainWindow() const
  323. {
  324. return static_cast<AzQtComponents::WindowDecorationWrapper*>(EMStudio::GetMainWindow()->parent());
  325. }
  326. AzQtComponents::TitleBar* UIFixture::GetTitleBarForMainWindow() const
  327. {
  328. return GetDecorationWrapperForMainWindow()->findChild<AzQtComponents::TitleBar*>(QString(), Qt::FindDirectChildrenOnly);
  329. }
  330. AzQtComponents::DockBarButton* UIFixture::GetDockBarButtonForMainWindow(AzQtComponents::DockBarButton::WindowDecorationButton buttonType) const
  331. {
  332. const QList<AzQtComponents::DockBarButton*>buttons = GetTitleBarForMainWindow()->findChildren<AzQtComponents::DockBarButton*>();
  333. for (AzQtComponents::DockBarButton* button : buttons)
  334. {
  335. if (button->buttonType() == buttonType)
  336. {
  337. return button;
  338. }
  339. }
  340. return nullptr;
  341. }
  342. void UIFixture::SelectActor(Actor* actor)
  343. {
  344. AZStd::string result;
  345. AZStd::string cmd;
  346. cmd = AZStd::string::format("Select actor %d", actor->GetID());
  347. EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(cmd.c_str(), result)) << result.c_str();
  348. }
  349. void UIFixture::SelectActorInstance(ActorInstance* actorInstance)
  350. {
  351. SelectActor(actorInstance->GetActor());
  352. }
  353. EMStudio::MotionSetsWindowPlugin* UIFixture::GetMotionSetsWindowPlugin()
  354. {
  355. return static_cast<EMStudio::MotionSetsWindowPlugin*>(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::MotionSetsWindowPlugin::CLASS_ID));
  356. }
  357. EMStudio::MotionSetManagementWindow* UIFixture::GetMotionSetManagementWindow()
  358. {
  359. EMStudio::MotionSetsWindowPlugin* plugin = GetMotionSetsWindowPlugin();
  360. EXPECT_TRUE(plugin);
  361. return plugin->GetManagementWindow();
  362. }
  363. void UIFixture::CreateAnimGraphParameter(const AZStd::string& name)
  364. {
  365. EMStudio::ParameterWindow* parameterWindow = m_animGraphPlugin->GetParameterWindow();
  366. parameterWindow->OnAddParameter();
  367. auto paramWidget = qobject_cast<EMStudio::ParameterCreateEditWidget*>(FindTopLevelWidget("ParameterCreateEditWidget"));
  368. ASSERT_TRUE(paramWidget);
  369. const AZStd::unique_ptr<EMotionFX::Parameter>& param = paramWidget->GetParameter();
  370. param->SetName(name);
  371. const size_t numParams = m_animGraphPlugin->GetActiveAnimGraph()->GetNumParameters();
  372. QPushButton* createButton = paramWidget->findChild<QPushButton*>("EMFX.ParameterCreateEditWidget.CreateApplyButton");
  373. QTest::mouseClick(createButton, Qt::LeftButton);
  374. ASSERT_EQ(m_animGraphPlugin->GetActiveAnimGraph()->GetNumParameters(), numParams + 1);
  375. }
  376. SimulatedObjectColliderWidget* UIFixture::GetSimulatedObjectColliderWidget() const
  377. {
  378. const EMotionFX::SimulatedObjectWidget* simulatedObjectWidget = static_cast<EMotionFX::SimulatedObjectWidget*>(EMStudio::GetPluginManager()->FindActivePlugin(EMotionFX::SimulatedObjectWidget::CLASS_ID));
  379. EXPECT_TRUE(simulatedObjectWidget) << "Simulated Object plugin not found!";
  380. if (!simulatedObjectWidget)
  381. {
  382. return nullptr;
  383. }
  384. const SimulatedJointWidget* simulatedJointWidget = simulatedObjectWidget->GetSimulatedJointWidget();
  385. EXPECT_TRUE(simulatedJointWidget) << "SimulatedJointWidget not found.";
  386. if (!simulatedJointWidget)
  387. {
  388. return nullptr;
  389. }
  390. SimulatedObjectColliderWidget* simulatedObjectColliderWidget = simulatedJointWidget->findChild<SimulatedObjectColliderWidget*>();
  391. EXPECT_TRUE(simulatedObjectColliderWidget) << "SimulatedJointWidget not found.";
  392. return simulatedObjectColliderWidget;
  393. }
  394. } // namespace EMotionFX