3
0

ModalPopupHandler.h 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. #pragma once
  9. #if !defined(Q_MOC_RUN)
  10. #include <Tests/SystemComponentFixture.h>
  11. #include <AzQtComponents/Components/StyleManager.h>
  12. #include <AzQtComponents/Components/WindowDecorationWrapper.h>
  13. #include <QDialogButtonBox>
  14. #include <QtTest>
  15. #include <QPushButton>
  16. #include <QMessageBox>
  17. #endif
  18. QT_FORWARD_DECLARE_CLASS(QWidget)
  19. QT_FORWARD_DECLARE_CLASS(QAction)
  20. QT_FORWARD_DECLARE_CLASS(QTreeView)
  21. QT_FORWARD_DECLARE_CLASS(ReselectingTreeView)
  22. namespace EMotionFX
  23. {
  24. using MenuActiveCallback = AZStd::function<void(QMenu* menu)>;
  25. using WidgetActiveCallback = AZStd::function<void(QWidget* widget)>;
  26. using ActionCompletionCallback = AZStd::function<void(const QString& action)>;
  27. /// This class can be used to manipulate modal popups which cannot otherwise be interracted with, due to the thread being on hold until they are complete.
  28. /// To use it: set up an instance before the popup is triggered, with a callback that handles any interraction you require.
  29. /// Can also be used with modeless popups, by calling WaitForCompletion after the popup is triggered.
  30. class ModalPopupHandler
  31. : public QObject
  32. {
  33. Q_OBJECT
  34. public:
  35. explicit ModalPopupHandler(QObject* pParent = nullptr);
  36. /// Brings up a context menu on a widget, then triggers the named action.
  37. void ShowContextMenuAndTriggerAction(QWidget* widget, const QString& actionName, int timeout, ActionCompletionCallback callback);
  38. /// Wait for a modal widget (returned by activeModalWidget) which is of WidgetType to appear, then call callback.
  39. /// @param callback routine to call when a n active modal widget is detected.
  40. /// @param timeout maximum time to wait before giving up.
  41. template <class WidgetType>
  42. void WaitForPopup(AZStd::function<void(WidgetType menu)> callback, const int timeout = DefaultTimeout)
  43. {
  44. m_complete = false;
  45. ResetSeenTargetWidget();
  46. WidgetActiveCallback widgetCallback = [this, callback](QWidget* widget)
  47. {
  48. if (!widget)
  49. {
  50. QTimer::singleShot(WaitTickTime, this, &ModalPopupHandler::CheckForPopupWidget);
  51. return;
  52. }
  53. WidgetType popupWidget = nullptr;
  54. // If the style manager is active, the hierarchy will be different.
  55. if (AzQtComponents::StyleManager::isInstanced())
  56. {
  57. AzQtComponents::WindowDecorationWrapper* wrapper = qobject_cast<AzQtComponents::WindowDecorationWrapper*>(widget);
  58. if (wrapper)
  59. {
  60. popupWidget = wrapper->findChild<WidgetType>();
  61. }
  62. }
  63. else
  64. {
  65. popupWidget = qobject_cast<WidgetType>(widget);
  66. }
  67. if (!popupWidget)
  68. {
  69. QTimer::singleShot(WaitTickTime, this, &ModalPopupHandler::CheckForPopupWidget);
  70. return;
  71. }
  72. callback(popupWidget);
  73. m_complete = true;
  74. };
  75. m_totalTime = 0;
  76. m_widgetActiveCallback = widgetCallback;
  77. m_timeout = timeout;
  78. // Kick a timer off to check whether the menu is open.
  79. QTimer::singleShot(WaitTickTime, this, &ModalPopupHandler::CheckForPopupWidget);
  80. }
  81. /// Wait for a modal widget (returned by activeModalWidget) which is of WidgetType to appear, then press a button in a child QDialogButtonBox.
  82. /// @param buttonRole button to press.
  83. /// @param timeout maximum time to wait before giving up.
  84. template <class WidgetType>
  85. void WaitForPopupPressDialogButton(QDialogButtonBox::StandardButton buttonRole, const int timeout = DefaultTimeout)
  86. {
  87. WidgetActiveCallback pressButtonCallback = [buttonRole](QWidget* widget)
  88. {
  89. ASSERT_TRUE(widget);
  90. QDialogButtonBox* buttonBox = widget->findChild< QDialogButtonBox*>();
  91. ASSERT_TRUE(buttonBox) << "Unable to find button box in SaveDirtySettingsWindow";
  92. QPushButton* button = buttonBox->button(buttonRole);
  93. ASSERT_TRUE(button) << "Unable to find button in SaveDirtySettingsWindow";
  94. QTest::mouseClick(button, Qt::LeftButton);
  95. };
  96. WaitForPopup<WidgetType>(pressButtonCallback, timeout);
  97. }
  98. /// Wait for a modal widget (returned by activeModalWidget) which is of WidgetType to appear, then press a specific button in a child QDialogButtonBox.
  99. /// @param buttonRole button to press.
  100. /// @param timeout maximum time to wait before giving up.
  101. template <class WidgetType>
  102. void WaitForPopupPressSpecificButton(const AZStd::string buttonObjectName, const int timeout = DefaultTimeout)
  103. {
  104. WidgetActiveCallback pressButtonCallback = [buttonObjectName](QWidget* widget)
  105. {
  106. ASSERT_TRUE(widget);
  107. QDialogButtonBox* buttonBox = widget->findChild< QDialogButtonBox*>();
  108. ASSERT_TRUE(buttonBox) << "Unable to find button box in SaveDirtySettingsWindow";
  109. QAbstractButton* selectedButton = nullptr;
  110. for (QAbstractButton* button : buttonBox->buttons())
  111. {
  112. if (button->objectName().toStdString() == buttonObjectName.c_str())
  113. {
  114. selectedButton = button;
  115. break;
  116. }
  117. }
  118. ASSERT_TRUE(selectedButton) << "Unable to find button in SaveDirtySettingsWindow";
  119. QTest::mouseClick(selectedButton, Qt::LeftButton);
  120. };
  121. WaitForPopup<WidgetType>(pressButtonCallback, timeout);
  122. }
  123. /// @return true if the expected dialog was seen, false otherwise.
  124. bool GetSeenTargetWidget();
  125. /// Reset the seen target flag, to be used if you want to use the same handler twice.
  126. void ResetSeenTargetWidget();
  127. /// Returns whether or not the dialog has been completed (is closed). Only of use with modeless widgets.
  128. bool GetIsComplete();
  129. /// Wait until the dialog is complete, then return. Has no effect if the dialog is modal.
  130. void WaitForCompletion(const int timeout = DefaultTimeout);
  131. private Q_SLOTS:
  132. void CheckForContextMenu();
  133. void CheckForPopupWidget();
  134. private:
  135. MenuActiveCallback m_menuActiveCallback = nullptr;
  136. WidgetActiveCallback m_widgetActiveCallback = nullptr;
  137. ActionCompletionCallback m_actionCompletionCallback = nullptr;
  138. int m_totalTime = 0;
  139. int m_timeout = 0;
  140. bool m_seenTargetWidget = false;
  141. bool m_complete = false;
  142. static constexpr int WaitTickTime = 10;
  143. static constexpr int DefaultTimeout = 3000;
  144. };
  145. } // end namespace EMotionFX