/* * 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 #ifndef ACTIONMANAGER_H #define ACTIONMANAGER_H #include #include #include #include #include #include #include #include #include #include #include #include #include class QSignalMapper; class QPixmap; class QDockWidget; class DynamicMenu; class MainWindow; class QtViewPaneManager; class ShortcutDispatcher; class PatchedAction : public QAction { // PatchedAction is a workaround for the fact that Qt doesn't honour Qt::WindowShortcut context for floating dock widgets. Q_OBJECT public: explicit PatchedAction(const QString& name, QObject* parent = nullptr); bool event(QEvent*) override; }; class WidgetAction : public QWidgetAction { Q_OBJECT public: explicit WidgetAction(int actionId, MainWindow* mainWindow, const QString& name, QObject* parent); protected: QWidget* createWidget(QWidget* parent) override; private: const int m_actionId; MainWindow* const m_mainWindow; }; class ActionManager; // If the action handler puts up a modal dialog, the event queue will be pumped // and double clicks on menu items will go through, in some cases. // This class can be used to guard against that. class ActionManagerExecutionGuard { public: ActionManagerExecutionGuard(ActionManager* actionManager, QAction* action); ActionManagerExecutionGuard(ActionManager* actionManager, int actionId); ~ActionManagerExecutionGuard(); // Only execute the action of this returns true bool CanExecute() const { return m_canExecute; } private: QPointer m_actionManager; int m_actionId; bool m_canExecute; }; class ActionManager : public QObject , private AzToolsFramework::EditorActionRequestBus::Handler { Q_OBJECT public: class ActionWrapper { public: ActionWrapper& SetText(const QString& text) { m_action->setText(text); return *this; } ActionWrapper& SetIcon(const QIcon& icon) { m_action->setIcon(icon); return *this; } ActionWrapper& SetIcon(const QPixmap& icon) { m_action->setIcon(QIcon(icon)); return *this; } ActionWrapper& SetShortcut(const QString& shortcut) { m_action->setShortcut(shortcut); return *this; } ActionWrapper& SetShortcut(const QKeySequence& shortcut) { m_action->setShortcut(shortcut); return *this; } ActionWrapper& SetToolTip(const QString& toolTip) { m_action->setToolTip(toolTip); return *this; } ActionWrapper& SetStatusTip(const QString& statusTip) { m_action->setStatusTip(statusTip); return *this; } ActionWrapper& SetCheckable(bool value) { m_action->setCheckable(value); return *this; } ActionWrapper& SetReserved(); // if reserved, the action should not be allowed to be disabled or overridden //ActionWrapper &SetMenu(QMenu *menu) { m_action->setMenu(menu); return *this; } //ActionWrapper &SetMenu(QMenu *menu) { m_action->setMenu(menu); return *this; } template ActionWrapper& Connect(Func1 signal, Func2 slot) { // The ActionWrapper class is stack based usually so // the lambda below can't use the this pointer. // As a result, wrap the actionManager and the action in // a QPointer, and capture them in the lambda QPointer actionManager = m_actionManager; QPointer action = m_action; QObject::connect(m_action, signal, m_action, [action, actionManager, slot]() { ActionManagerExecutionGuard guard(actionManager.data(), action.data()); if (!GetIEditor()->IsInGameMode() && guard.CanExecute()) { slot(); } }); return *this; } template ActionWrapper& Connect(Func1 signal, Object* context, const Func2 slot, Qt::ConnectionType type = Qt::AutoConnection) { // The ActionWrapper class is stack based usually so // the lambda below can't use the this pointer. // As a result, wrap the actionManager and the action in // a QPointer, and capture them in the lambda QPointer actionManager = m_actionManager; QPointer action = m_action; QObject::connect(m_action, signal, context, [action, actionManager, context, slot]() { ActionManagerExecutionGuard guard(actionManager.data(), action.data()); if (!GetIEditor()->IsInGameMode() && guard.CanExecute()) { (context->*slot)(); } }, type); return *this; } ActionWrapper& SetMenu(DynamicMenu* menu); ActionWrapper& SetMetricsIdentifier(const QString& metricsIdentifier); ActionWrapper& SetMetricsIdentifier(const QString& group, const QString& metricsIdentifier); ActionWrapper& SetApplyHoverEffect(); operator QAction*() const { return m_action; } QAction* operator->() const { return m_action; } template ActionWrapper& RegisterUpdateCallback(T* object, void (T::* method)(QAction*)) { m_actionManager->RegisterUpdateCallback(m_action->data().toInt(), object, method); return *this; } private: friend ActionManager; friend DynamicMenu; ActionWrapper(QAction* action, ActionManager* am) : m_action(action) , m_actionManager(am) { Q_ASSERT(m_action); } QAction* m_action; ActionManager* m_actionManager; }; class MenuWrapper { public: MenuWrapper() = default; MenuWrapper(QMenu* menu, ActionManager* am) : m_menu(menu) , m_actionManager(am) {} MenuWrapper& SetTitle(const QString& text) { m_menu->setTitle(text); return *this; } MenuWrapper& SetIcon(const QIcon& icon) { m_menu->setIcon(icon); return *this; } QAction* AddAction(int id) { auto action = m_actionManager->GetAction(id); Q_ASSERT(action); m_menu->addAction(action); return action; } QAction* AddSeparator() { return m_menu->addSeparator(); } MenuWrapper AddMenu(const QString& name, const QString& menuId = QString()) { auto menu = m_actionManager->AddMenu(name, menuId); m_menu->addMenu(menu); return menu; } bool isNull() const { return !m_menu; } operator QMenu*() { return m_menu; } QMenu* operator->() { return m_menu; } QMenu* Get() { return m_menu; } private: friend ActionManager; QPointer m_menu = nullptr; ActionManager* m_actionManager = nullptr; }; class ToolBarWrapper { public: QAction* AddAction(int id) { auto action = m_actionManager->GetAction(id); Q_ASSERT(action); m_toolBar->addAction(action); return action; } QAction* AddSeparator() { QAction* action = m_toolBar->addSeparator(); // For the Dnd to work: action->setData(ID_TOOLBAR_SEPARATOR); QWidget* w = m_toolBar->widgetForAction(action); w->addAction(action); return action; } void AddWidget(QWidget* widget) { m_toolBar->addWidget(widget); } operator QToolBar*() const { return m_toolBar; } QToolBar* operator->() const { return m_toolBar; } private: friend ActionManager; ToolBarWrapper(QToolBar* toolBar, ActionManager* am) : m_toolBar(toolBar) , m_actionManager(am) { Q_ASSERT(m_toolBar); } QToolBar* m_toolBar; ActionManager* m_actionManager; }; public: explicit ActionManager( MainWindow* parent, QtViewPaneManager* qtViewPaneManager, ShortcutDispatcher* shortcutDispatcher); ~ActionManager(); void AddMenu(QMenu* menu); MenuWrapper AddMenu(const QString& title, const QString& menuId); MenuWrapper FindMenu(const QString& menuId); bool ActionIsWidget(int actionId) const; void AddToolBar(QToolBar* toolBar); ToolBarWrapper AddToolBar(int id); void AddAction(QAction* action); void AddAction(int id, QAction* action); void RemoveAction(QAction* action); ActionWrapper AddAction(int id, const QString& name); bool HasAction(QAction*) const; bool HasAction(int id) const; QAction* GetAction(int id) const; QList GetActions() const; // AzToolsFramework::EditorActionRequests void AddActionViaBus(int id, QAction* action) override; void RemoveActionViaBus(QAction* action) override; void EnableDefaultActions() override; void DisableDefaultActions() override; void AttachOverride(QWidget* object) override; void DetachOverride() override; template void RegisterUpdateCallback(int id, T* object, void (T::* method)(QAction*)) { Q_ASSERT(m_actions.contains(id)); auto f = std::bind(method, object, m_actions.value(id)); m_updateCallbacks[id] = f; } template void RegisterActionHandler(int id, T method) { m_actionHandlers[id] = method; } template void RegisterActionHandler(int id, T* object, void (T::* method)()) { m_actionHandlers[id] = std::bind(method, object); } template void RegisterActionHandler(int id, T* object, void (T::* method)(UINT)) { m_actionHandlers[id] = std::bind(method, object, id); } void SendMetricsEvent(int id); bool eventFilter(QObject* watched, QEvent* event); // returns false if the action was already inserted, indicating that the action should not be processed again bool InsertActionExecuting(int id); // returns true if the action was inserted with InsertActionExecuting previously bool RemoveActionExecuting(int id); Q_SIGNALS: void SendMetricsSignal(const char* viewPaneName, const char* openLocation); public slots: void ActionTriggered(int id); private slots: void UpdateMenu(); void UpdateActions(); private: QHash m_actions; QVector m_menus; QVector m_toolBars; QSignalMapper* m_actionMapper; QHash > m_updateCallbacks; QHash > m_actionHandlers; MainWindow* const m_mainWindow; QtViewPaneManager* m_qtViewPaneManager; ShortcutDispatcher* m_shortcutDispatcher = nullptr; // for sending shortcut metrics events bool m_isShortcutEvent = false; // for sending toolbar metrics events QSet m_editorToolbarIds; // for sending main menu metrics events QSet m_registeredViewPaneIds; // update the registered view pane Ids when the registered view pane list is modified void RebuildRegisteredViewPaneIds(); // Guard against recursive actions being triggered by double clicks on menu items // while modal dialogs are in the process of popping up QSet m_executingIds; // have all default actions (not including reserved actions) been suspended bool m_defaultActionsSuspended = false; }; class DynamicMenu : public QObject { Q_OBJECT public: explicit DynamicMenu(QObject* parent = nullptr); void SetAction(QAction* action, ActionManager* am); void SetParentMenu(QMenu* menu, ActionManager* am); protected: virtual void CreateMenu() = 0; virtual void OnMenuUpdate(int id, QAction* action) = 0; virtual void OnMenuChange(int id, QAction* action) = 0; void AddAction(int id, QAction* action); ActionManager::ActionWrapper AddAction(int id, const QString& name); void AddSeparator(); void UpdateAllActions(); ActionManager* m_actionManager; private slots: void ShowMenu(); void TriggerAction(int id); private: QAction* m_action; QMenu* m_menu; QSignalMapper* m_actionMapper; QHash m_actions; }; #endif // ACTIONMANAGER_H