/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #pragma once #include #include #include #include #include #include QT_FORWARD_DECLARE_CLASS(QWidget) QT_FORWARD_DECLARE_CLASS(QAction) QT_FORWARD_DECLARE_CLASS(QTreeView) QT_FORWARD_DECLARE_CLASS(ReselectingTreeView) namespace EMotionFX { using MenuActiveCallback = AZStd::function; using WidgetActiveCallback = AZStd::function; using ActionCompletionCallback = AZStd::function; static const int DefaultTimeout = 3000; /// 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. /// To use it: set up an instance before the popup is triggered, with a callback that handles any interraction you require. /// Can also be used with modeless popups, by calling WaitForCompletion after the popup is triggered. class ModalPopupHandler : public QObject { Q_OBJECT public: explicit ModalPopupHandler(QObject* pParent = nullptr); /// Brings up a context menu on a widget, then triggers the named action. void ShowContextMenuAndTriggerAction(QWidget* widget, const QString& actionName, int timeout, ActionCompletionCallback callback); /// Wait for a modal widget (returned by activeModalWidget) which is of WidgetType to appear, then call callback. /// @param callback routine to call when a n active modal widget is detected. /// @param timeout maximum time to wait before giving up. template void WaitForPopup(AZStd::function callback, const int timeout = DefaultTimeout) { m_complete = false; ResetSeenTargetWidget(); WidgetActiveCallback widgetCallback = [this, callback](QWidget* widget) { if (!widget) { QTimer::singleShot(10, this, SLOT(CheckForPopupWidget())); return; } WidgetType popupWidget = nullptr; // If the style manager is active, the hierarchy will be different. if (AzQtComponents::StyleManager::isInstanced()) { AzQtComponents::WindowDecorationWrapper* wrapper = qobject_cast(widget); if (wrapper) { popupWidget = wrapper->findChild(); } } else { popupWidget = qobject_cast(widget); } if (!popupWidget) { QTimer::singleShot(10, this, SLOT(CheckForPopupWidget())); return; } callback(popupWidget); m_complete = true; }; m_totalTime = 0; m_widgetActiveCallback = widgetCallback; m_timeout = timeout; // Kick a timer off to check whether the menu is open. QTimer::singleShot(10, this, SLOT(CheckForPopupWidget())); } /// Wait for a modal widget (returned by activeModalWidget) which is of WidgetType to appear, then press a button in a child QDialogButtonBox. /// @param buttonRole button to press. /// @param timeout maximum time to wait before giving up. template void WaitForPopupPressDialogButton(QDialogButtonBox::StandardButton buttonRole, const int timeout = DefaultTimeout) { WidgetActiveCallback pressButtonCallback = [buttonRole](QWidget* widget) { ASSERT_TRUE(widget); QDialogButtonBox* buttonBox = widget->findChild< QDialogButtonBox*>(); ASSERT_TRUE(buttonBox) << "Unable to find button box in SaveDirtySettingsWindow"; QPushButton* button = buttonBox->button(buttonRole); ASSERT_TRUE(button) << "Unable to find button in SaveDirtySettingsWindow"; QTest::mouseClick(button, Qt::LeftButton); }; WaitForPopup(pressButtonCallback, timeout); } /// Wait for a modal widget (returned by activeModalWidget) which is of WidgetType to appear, then press a specific button in a child QDialogButtonBox. /// @param buttonRole button to press. /// @param timeout maximum time to wait before giving up. template void WaitForPopupPressSpecificButton(const AZStd::string buttonObjectName, const int timeout = DefaultTimeout) { WidgetActiveCallback pressButtonCallback = [buttonObjectName](QWidget* widget) { ASSERT_TRUE(widget); QDialogButtonBox* buttonBox = widget->findChild< QDialogButtonBox*>(); ASSERT_TRUE(buttonBox) << "Unable to find button box in SaveDirtySettingsWindow"; QAbstractButton* selectedButton = nullptr; for (QAbstractButton* button : buttonBox->buttons()) { if (button->objectName().toStdString() == buttonObjectName.c_str()) { selectedButton = button; break; } } ASSERT_TRUE(selectedButton) << "Unable to find button in SaveDirtySettingsWindow"; QTest::mouseClick(selectedButton, Qt::LeftButton); }; WaitForPopup(pressButtonCallback, timeout); } /// @return true if the expected dialog was seen, false otherwise. bool GetSeenTargetWidget(); /// Reset the seen target flag, to be used if you want to use the same handler twice. void ResetSeenTargetWidget(); /// Returns whether or not the dialog has been completed (is closed). Only of use with modeless widgets. bool GetIsComplete(); /// Wait until the dialog is complete, then return. Has no effect if the dialog is modal. void WaitForCompletion(const int timeout = DefaultTimeout); private Q_SLOTS: void CheckForContextMenu(); void CheckForPopupWidget(); private: MenuActiveCallback m_MenuActiveCallback = nullptr; WidgetActiveCallback m_widgetActiveCallback = nullptr; ActionCompletionCallback m_actionCompletionCallback = nullptr;; int m_totalTime = 0; int m_timeout = 0; bool m_seenTargetWidget = false; bool m_complete = false; }; } // end namespace EMotionFX