Prechádzať zdrojové kódy

Remove legacy ShortcutDispatcher class

Signed-off-by: Danilo Aimini <[email protected]>
Danilo Aimini 2 rokov pred
rodič
commit
82ccf4f20f

+ 0 - 1
Code/Editor/MainWindow.cpp

@@ -62,7 +62,6 @@
 
 // Editor
 #include "Resource.h"
-#include "ShortcutDispatcher.h"
 #include "LayoutWnd.h"
 #include "AssetImporter/AssetImporterManager/AssetImporterManager.h"
 #include "AssetImporter/AssetImporterManager/AssetImporterDragAndDropHandler.h"

+ 0 - 2
Code/Editor/QtViewPaneManager.cpp

@@ -47,8 +47,6 @@
 #include <AzQtComponents/Components/StyleManager.h>
 #include <AzCore/UserSettings/UserSettingsComponent.h>
 
-#include "ShortcutDispatcher.h"
-
 // Helper for EditorComponentModeNotifications to be used
 // as a member instead of inheriting from EBus directly.
 class ViewportEditorModeNotificationsBusImpl

+ 0 - 491
Code/Editor/ShortcutDispatcher.cpp

@@ -1,491 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#include "EditorDefs.h"
-
-#include "ShortcutDispatcher.h"
-
-#include <assert.h>
-
-
-// Qt
-#include <QScopedValueRollback>
-#include <QAction>
-#include <QDockWidget>
-#include <QDebug>
-#include <QMainWindow>
-#include <QMenuBar>
-
-// AzQtComponents
-#include <AzQtComponents/Buses/ShortcutDispatch.h>
-
-// Editor
-#include "QtViewPaneManager.h"
-
-const char* FOCUSED_VIEW_PANE_EVENT_NAME = "FocusedViewPaneEvent";    //Sent when view panes are focused
-const char* FOCUSED_VIEW_PANE_ATTRIBUTE_NAME = "FocusedViewPaneName"; //Name of the current focused view pane
-
-// Build sets _DEBUG when we're in debug mode; define SHOW_ACTION_INFO_IN_DEBUGGER so that when stepping through the debugger,
-// we can see what the actions are and what their keyboard shortcuts are
-#define SHOW_ACTION_INFO_IN_DEBUGGER _DEBUG
-
-static QPointer<QWidget> s_lastFocus;
-
-#if AZ_TRAIT_OS_PLATFORM_APPLE
-class MacNativeShortcutFilter : public QObject
-{
-    /* mac's native toolbar doesn't generate shortcut events, it calls the action directly.
-     * It doesn't even honour shortcut contexts.
-     *
-     * To remedy this, we catch the QMetaCallEvent that triggers the menu item activation
-     * and suppress it if it was triggered via key combination, and send a QShortcutEvent.
-     *
-     * The tricky part is to find out if the menu item was triggered via mouse or shortcut.
-     * If the previous event was a ShortcutOverride then it means key press.
-     */
-
-public:
-    explicit MacNativeShortcutFilter(QObject* parent)
-        : QObject(parent)
-        , m_lastShortcutOverride(QEvent::KeyPress, 0, Qt::NoModifier) // dummy init
-    {
-        qApp->installEventFilter(this);
-    }
-
-    bool eventFilter(QObject* watched, QEvent* event) override
-    {
-
-        switch (event->type())
-        {
-            case QEvent::ShortcutOverride:
-            {
-                auto ke = static_cast<QKeyEvent*>(event);
-                m_lastEventWasShortcutOverride = true;
-                m_lastShortcutOverride = QKeyEvent(*ke);
-                break;
-            }
-            case QEvent::MetaCall:
-            {
-                if (m_lastEventWasShortcutOverride)
-                {
-                    m_lastEventWasShortcutOverride = false;
-                    const QMetaObject* mo = watched->metaObject();
-                    const bool isMenuItem = mo && qstrcmp(mo->className(), "QPlatformMenuItem") == 0;
-                    if (isMenuItem)
-                    {
-                        QWidget* focusWidget = ShortcutDispatcher::focusWidget();
-                        if (focusWidget)
-                        {
-                            QShortcutEvent se(QKeySequence(m_lastShortcutOverride.key() + m_lastShortcutOverride.modifiers()), /*ambiguous=*/false);
-                            se.setAccepted(false);
-                            QApplication::sendEvent(focusWidget, &se);
-                            return se.isAccepted();
-                        }
-                    }
-                }
-
-                break;
-            }
-            case QEvent::MouseButtonDblClick:
-            case QEvent::MouseButtonPress:
-            case QEvent::MouseButtonRelease:
-            case QEvent::KeyPress:
-            case QEvent::KeyRelease:
-                m_lastEventWasShortcutOverride = false;
-                break;
-            default:
-                break;
-        }
-
-        return false;
-    }
-
-    bool m_lastEventWasShortcutOverride = false;
-    QKeyEvent m_lastShortcutOverride;
-};
-#endif
-
-// Returns either a top-level or a dock widget (regardless of floating)
-// This way when docking a main window Qt::WindowShortcut still works
-QWidget* ShortcutDispatcher::FindParentScopeRoot(QWidget* widget)
-{
-    QWidget* w = widget;
-    // if the current scope root is a QDockWidget, we want to bubble out
-    // so we move to the parent immediately
-    if (qobject_cast<QDockWidget*>(w) != nullptr || qobject_cast<QMainWindow*>(w) != nullptr)
-    {
-        w = w->parentWidget();
-    }
-
-    QWidget* newScopeRoot = w;
-    while (newScopeRoot && newScopeRoot->parent() && !qobject_cast<QDockWidget*>(newScopeRoot) && !qobject_cast<QMainWindow*>(newScopeRoot))
-    {
-        newScopeRoot = newScopeRoot->parentWidget();
-    }
-
-    // This method should always return a parent scope root; if it can't find one
-    // it returns null
-    if (newScopeRoot == widget)
-    {
-        newScopeRoot = nullptr;
-
-        if (w != nullptr)
-        {
-            // we couldn't find a valid parent; broadcast a message to see if something else wants to tell us about one
-            AzQtComponents::ShortcutDispatchBus::EventResult(newScopeRoot, w, &AzQtComponents::ShortcutDispatchBus::Events::GetShortcutDispatchScopeRoot, w);
-        }
-    }
-
-    return newScopeRoot;
-}
-
-// Returns true if a widget is ancestor of another widget
-bool ShortcutDispatcher::IsAContainerForB(QWidget* a, QWidget* b)
-{
-    if (!a || !b)
-    {
-        return false;
-    }
-
-    while (b && (a != b))
-    {
-        b = b->parentWidget();
-    }
-
-    return (a == b);
-}
-
-// Returns the list of QActions which have this specific key shortcut
-// Only QActions under scopeRoot are considered.
-QList<QAction*> ShortcutDispatcher::FindCandidateActions(QObject* scopeRoot, const QKeySequence& sequence, QSet<QObject*>& previouslyVisited, bool checkVisibility)
-{
-    QList<QAction*> actions;
-    if (!scopeRoot)
-    {
-        return actions;
-    }
-
-    if (previouslyVisited.contains(scopeRoot))
-    {
-        return actions;
-    }
-    previouslyVisited.insert(scopeRoot);
-
-    QWidget* scopeRootWidget = qobject_cast<QWidget*>(scopeRoot);
-    if (scopeRootWidget && ((checkVisibility && !scopeRootWidget->isVisible()) || !scopeRootWidget->isEnabled()))
-    {
-        return actions;
-    }
-
-#ifdef SHOW_ACTION_INFO_IN_DEBUGGER
-    QString matchingAgainst = sequence.toString();
-    (void) matchingAgainst; // avoid an unused variable warning; want this for debugging
-#endif
-
-    // Don't just call scopeRoot->actions()! It doesn't always return the proper list, especially with the dock widgets
-    for (QAction* action : scopeRoot->findChildren<QAction*>(QString(), Qt::FindDirectChildrenOnly))   // Width first
-    {
-#ifdef SHOW_ACTION_INFO_IN_DEBUGGER
-        QString actionName = action->text();
-        (void)actionName; // avoid an unused variable warning; want this for debugging
-        QString shortcut = action->shortcut().toString();
-        (void)shortcut; // avoid an unused variable warning; want this for debugging
-#endif
-
-        if (action->shortcut() == sequence)
-        {
-            actions << action;
-        }
-    }
-
-    // also have to check the actions on the object directly, without looking at children
-    // specifically for the base Editor MainWindow
-    if (scopeRootWidget)
-    {
-        for (QAction* action : scopeRootWidget->actions())
-        {
-#ifdef SHOW_ACTION_INFO_IN_DEBUGGER
-            QString actionName = action->text();
-            (void)actionName; // avoid an unused variable warning; want this for debugging
-            QString shortcut = action->shortcut().toString();
-            (void)shortcut; // avoid an unused variable warning; want this for debugging
-#endif
-
-            if (action->shortcut() == sequence)
-            {
-                actions << action;
-            }
-        }
-    }
-
-    // Menubars have child widgets that have actions
-    // But menu bar child widgets (menu items) are only visible when they've been clicked on
-    // so we don't want to test visibility for child widgets of menubars
-    if (qobject_cast<QMenuBar*>(scopeRoot))
-    {
-        checkVisibility = false;
-    }
-
-    // check the dock's central widget and the main window's
-    // In some cases, they aren't in the scopeRoot's children, despite having the scopeRoot as their parent
-    QDockWidget* dockWidget = qobject_cast<QDockWidget*>(scopeRoot);
-    if (dockWidget)
-    {
-        actions << FindCandidateActions(dockWidget->widget(), sequence, previouslyVisited, checkVisibility);
-    }
-
-    QMainWindow* mainWindow = qobject_cast<QMainWindow*>(scopeRoot);
-    if (mainWindow)
-    {
-        actions << FindCandidateActions(mainWindow->centralWidget(), sequence, previouslyVisited, checkVisibility);
-    }
-
-    for (QWidget* child : scopeRoot->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly))
-    {
-        bool isMenu = (qobject_cast<QMenu*>(child) != nullptr);
-
-        if ((child->windowFlags() & Qt::Window) && !isMenu)
-        {
-            // When going down the hierarchy stop at window boundaries, to not accidentally trigger
-            // shortcuts from unfocused windows. Windows might be parented to this scope for purposes
-            // "centering within parent" or lifetime.
-            // Don't stop at menus though, as they are flagged as Qt::Window but are often the only thing that
-            // actions are attached to.
-            continue;
-        }
-
-        bool isDockWidget = (qobject_cast<QDockWidget*>(child) != nullptr);
-        if ((isDockWidget && !actions.isEmpty()) || IsShortcutSearchBreak(child))
-        {
-            // If we already found a candidate, don't go into dock widgets, they have lower priority
-            // since they are not focused.
-            // Also never go into viewpanes; viewpanes are their own separate shortcut context and they never take shortcuts from the main window
-            continue;
-        }
-
-        actions << FindCandidateActions(child, sequence, previouslyVisited, checkVisibility);
-    }
-
-    return actions;
-}
-
-bool ShortcutDispatcher::FindCandidateActionAndFire(QObject* focusWidget, QShortcutEvent* shortcutEvent, QList<QAction*>& candidates, QSet<QObject*>& previouslyVisited)
-{
-    candidates = FindCandidateActions(focusWidget, shortcutEvent->key(), previouslyVisited);
-    QSet<QAction*> candidateSet = QSet<QAction*>(candidates.begin(), candidates.end());
-    QAction* chosenAction = nullptr;
-    chosenAction = candidates.first();
-
-    if (chosenAction)
-    {
-        if (chosenAction->isEnabled())
-        {
-            // has to be send, not post, or the dispatcher will get the event again and won't know that it was the one that queued it
-            bool isAmbiguous = false;
-            QShortcutEvent newEvent(shortcutEvent->key(), isAmbiguous);
-            QApplication::sendEvent(chosenAction, &newEvent);
-        }
-        shortcutEvent->accept();
-        return true;
-    }
-
-    return false;
-}
-
-ShortcutDispatcher::ShortcutDispatcher(QObject* parent)
-    : QObject(parent)
-    , m_currentlyHandlingShortcut(false)
-{
-    qApp->installEventFilter(this);
-#if AZ_TRAIT_OS_PLATFORM_APPLE
-    new MacNativeShortcutFilter(this);
-#endif
-}
-
-ShortcutDispatcher::~ShortcutDispatcher()
-{
-}
-
-bool ShortcutDispatcher::eventFilter(QObject* obj, QEvent* ev)
-{
-    switch (ev->type())
-    {
-    case QEvent::ShortcutOverride:
-        // QActions default "autoRepeat" to true, which is not an ideal user experience
-        // We globally disable that behavior here - in the unlikely event a shortcut needs to
-        // replicate it, its owner can instead implement a keyEvent handler
-        if (static_cast<QKeyEvent*>(ev)->isAutoRepeat())
-        {
-            ev->accept();
-            return true;
-        }
-        break;
-
-    case QEvent::Shortcut:
-        return shortcutFilter(obj, static_cast<QShortcutEvent*>(ev));
-
-    case QEvent::MouseButtonPress:
-        if (!s_lastFocus || !IsAContainerForB(qobject_cast<QWidget*>(obj), s_lastFocus))
-        {
-            setNewFocus(obj);
-        }
-        break;
-
-    case QEvent::FocusIn:
-        setNewFocus(obj);
-        break;
-
-        // we don't really care about focus out, because something should always have the focus
-        // but I'm leaving this here, so that it's clear that this is intentional
-        //case QEvent::FocusOut:
-        //    break;
-    }
-
-    return false;
-}
-
-QWidget* ShortcutDispatcher::focusWidget()
-{
-    QWidget* focusWidget = s_lastFocus; // check the widget we tracked last
-    if (!focusWidget)
-    {
-        // we don't have anything, so fall back to using the focus object
-        focusWidget = qobject_cast<QWidget*>(qApp->focusObject()); // QApplication::focusWidget() doesn't always work
-    }
-
-    return focusWidget;
-}
-
-bool ShortcutDispatcher::shortcutFilter(QObject* obj, QShortcutEvent* shortcutEvent)
-{
-    if (m_currentlyHandlingShortcut)
-    {
-        return false;
-    }
-
-    QScopedValueRollback<bool> recursiveCheck(m_currentlyHandlingShortcut, true);
-
-    // prioritize m_actionOverrideObject if active
-    if (m_actionOverrideObject != nullptr)
-    {
-        QList<QAction*> childActions =
-            m_actionOverrideObject->findChildren<QAction*>(QString(), Qt::FindDirectChildrenOnly);
-
-        // attempt to find shortcut in override
-        const auto childActionIt = AZStd::find_if(
-            childActions.begin(), childActions.end(), [shortcutEvent](QAction* child)
-        {
-            return child->shortcut() == shortcutEvent->key();
-        });
-
-        // trigger shortcut
-        if (childActionIt != childActions.end())
-        {
-            // has to be send, not post, or the dispatcher will get the event again
-            // and won't know that it was the one that queued it
-            const bool isAmbiguous = false;
-            QShortcutEvent newEvent(shortcutEvent->key(), isAmbiguous);
-
-            QAction* action = *childActionIt;
-            QApplication::sendEvent(action, &newEvent);
-
-            shortcutEvent->accept();
-            return true;
-        }
-    }
-
-    QWidget* currentFocusWidget = focusWidget(); // check the widget we tracked last
-    if (!currentFocusWidget)
-    {
-        qWarning() << Q_FUNC_INFO << "No focus widget"; // Defensive. Doesn't happen.
-        return false;
-    }
-
-    // Shortcut is ambiguous, lets resolve ambiguity and give preference to QActions in the most inner scope
-
-    // Try below the focusWidget first:
-    QSet<QObject*> previouslyVisited;
-    QList<QAction*> candidates;
-    if (FindCandidateActionAndFire(currentFocusWidget, shortcutEvent, candidates, previouslyVisited))    {
-        return true;
-    }
-
-    // Now incrementally try bigger scopes. This handles complex cases several levels docking nesting
-
-    QWidget* correctedTopLevel = nullptr;
-    QWidget* p = currentFocusWidget;
-    correctedTopLevel = FindParentScopeRoot(p);
-    while (correctedTopLevel)
-    {
-        if (FindCandidateActionAndFire(correctedTopLevel, shortcutEvent, candidates, previouslyVisited))
-        {
-            return true;
-        }
-
-        p = correctedTopLevel;
-        correctedTopLevel = FindParentScopeRoot(p);
-    }
-
-
-    // Nothing else to do... shortcut is really ambiguous, or there's no actions, something for the developer to fix.
-    // Here's some debug info :
-
-    if (candidates.isEmpty())
-    {
-        qWarning() << Q_FUNC_INFO << "No candidate QActions found";
-    }
-    else
-    {
-        qWarning() << Q_FUNC_INFO << "Ambiguous shortcut:" << shortcutEvent->key() << "; focusWidget="
-            << qApp->focusWidget() << "Candidates=" << candidates << "; obj = " << obj
-            << "Focused top-level=" << currentFocusWidget;
-        for (auto ambiguousAction : candidates)
-        {
-            qWarning() << "action=" << ambiguousAction << "; action->parentWidget=" << ambiguousAction->parentWidget()
-                << "; associatedWidgets=" << ambiguousAction->associatedWidgets()
-                << "; shortcut=" << ambiguousAction->shortcut();
-        }
-    }
-
-    return false;
-}
-
-void ShortcutDispatcher::setNewFocus(QObject* obj)
-{
-    // Unless every widget has strong focus, mouse clicks don't change the current focus widget
-    // which is a little unintuitive, compared to how we expect focus to work, right now.
-    // So instead of putting strong focus on everything, we detect focus change and mouse clicks
-
-    QWidget* widget = qobject_cast<QWidget*>(obj);
-
-    // we only watch widgets
-    if (widget == nullptr)
-    {
-        return;
-    }
-
-    // track it for later
-    s_lastFocus = widget;
-}
-
-bool ShortcutDispatcher::IsShortcutSearchBreak(QWidget* widget)
-{
-    return widget->property(AzQtComponents::SHORTCUT_DISPATCHER_CONTEXT_BREAK_PROPERTY).toBool();
-}
-
-void ShortcutDispatcher::AttachOverride(QWidget* object)
-{
-    m_actionOverrideObject = object;
-}
-
-void ShortcutDispatcher::DetachOverride()
-{
-    m_actionOverrideObject = nullptr;
-}
-
-#include <moc_ShortcutDispatcher.cpp>

+ 0 - 129
Code/Editor/ShortcutDispatcher.h

@@ -1,129 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#ifndef SHORTCUT_DISPATCHER_H
-#define SHORTCUT_DISPATCHER_H
-
-#if !defined(Q_MOC_RUN)
-#include <QObject>
-#endif
-
-template<typename T> class QSet;
-template<typename T> class QList;
-template<typename T> class QPointer;
-
-class QWidget;
-class QShortcutEvent;
-class QKeyEvent;
-
-/* This class provides a workaround against Qt's buggy implementation of shortcut contexts when using dock widgets.
-
-   There are several problems with Qt's implementation:
-
-   - Qt::WindowShortcut doesn't work well with floating docking widgets.
-     Qt gives a warning about ambiguous shortcut even though both actions are in different windows.
-   - A Qt::WindowShortcut in a docked widget will conflict when the widget is embedded (not floating).
-   - Qt::WidgetWithChildrenShortcut doesn't work well on menus since they can't have focus
-     Implement our own conflict resolution and shortcut dispatch.
-     Deliver to the action in the most inner scope, for our purposes a scope is either a window or a dockwidget (regardless of floating)
-
-     Beware that shortcut handling is complex and try not to change this class too much, as it's hard to test
-     and hard to verify all edge cases.
-*/
-
-
-/*
-  More documentation on Qt shortcuts
-  -------------------------------------------
-
-  Here's some more detailed info regarding shortcuts in Qt. Not specific Open 3D Engine but
-  useful as not explained in Qt docs much.
-
-  P.S.: The following text details the strategy used in an earlier Open 3D Engine version. Not sure which
-        shortcut context type it uses nowadays, but eitherway, the following text is educational,
-        and all the traps still exist in current Qt (5.11).
-
-     Some applications have a QMainWindow and also secondary main windows which can dock into the main QMainWindow.
-     All these main windows have menu bars containing actions with shortcuts.
-
-     So, which shortcut context should be used ?
-
-     - Qt::ApplicationShortcut
-       Obviously not, would create conflicting shortcuts and you only want local shortcuts anyway.
-
-     - Qt::WindowShortcut ?
-       This is supposed to only work if the shortcut's parent is in the focused window. However,
-       there a bug with floating dock widgets: doing a key sequence in the dock widget
-       triggers the main QMainWindow's shortcut.
-
-     - Qt::WidgetShortcut
-       docs say: "The shortcut is active when its parent widget has focus"
-       The QAction's parent is a QMenuBar, which doesn't get focus, so this is useless.
-
-     - Qt::WidgetWithChildrenShortcut ?
-       docs say: "The shortcut is active when its parent widget, or any of its children has focus"
-       The QAction's parent is a QMenuBar, which doesn't get focus or has any focused children. Useless.
-
-     - Qt::WidgetWithChildrenShortcut (Round2!)
-       Actually it's not the QAction's parent that counts but the associated widget! (Misleading docs)
-       So if you also add the action to the window, it works:
-       QAction *action = menu->addAction("Del");
-       myWindow->addAction(action); // success!
-
-       Are we happy ?
-       Not yet.
-
-       Qt::WidgetWithChildrenShortcut is working pretty well, but as soon as you dock your secondary QMainWindow into your main QMainWindow we get:
-       "QAction::eventFilter: Ambiguous shortcut overload: Ctrl+O". Some widget inside main window 2 is focused, but it's also a child of main window 1 further up the hierarchy, so both QActions would apply.
-
-       So now what we need is simply a global event filter, catch QEvent::Shortcut, check if shortcut->isAmbiguous(), and if yes dispatch the shortcut
-       manually (sendEvent), otherwise Qt would just bail out. To which QAction you send it to is up to you. I chose to imagine each dock widget was a scope and dispatch the shortcut to the most
-       inner scope that contains the focused widget.
-
-       If you've read this far you can now press 'Ctrl+Q' and hope it closes your editor ;)
- */
-
-class ShortcutDispatcher
-    : public QObject
-{
-    Q_OBJECT
-
-public:
-    explicit ShortcutDispatcher(QObject* parent = nullptr);
-    ~ShortcutDispatcher();
-    bool eventFilter(QObject* obj, QEvent* ev) override;
-
-    static QWidget* focusWidget();
-
-    /// Assign the widget responsible for getting first attempt
-    /// at every shortcut routed through the ShortcutDispatcher.
-    void AttachOverride(QWidget* object);
-    /// Detach the widget responsible for intercepting Actions
-    /// routed through the ShortcutDispatcher.
-    void DetachOverride();
-
-private:
-    bool shortcutFilter(QObject* obj, QShortcutEvent* ev);
-    void setNewFocus(QObject* obj);
-
-    QWidget* FindParentScopeRoot(QWidget* w);
-    bool IsAContainerForB(QWidget* a, QWidget* b);
-    QList<QAction*> FindCandidateActions(QObject* scopeRoot, const QKeySequence& sequence, QSet<QObject*>& previouslyVisited, bool checkVisibility = true);
-    bool FindCandidateActionAndFire(QObject* focusWidget, QShortcutEvent* shortcutEvent, QList<QAction*>& candidates, QSet<QObject*>& previouslyVisited);
-
-    static bool IsShortcutSearchBreak(QWidget* widget);
-
-    bool m_currentlyHandlingShortcut;
-
-    QString m_previousWidgetName;
-
-    QWidget* m_actionOverrideObject = nullptr; /**< (if set/not null) The widget responsible for getting first attempt
-                                                 *   at every shortcut routed through the ShortcutDispatcher. */
-};
-
-#endif

+ 0 - 2
Code/Editor/editor_lib_files.cmake

@@ -351,8 +351,6 @@ set(FILES
     LogFile.cpp
     LogFile.h
     Resource.h
-    ShortcutDispatcher.cpp
-    ShortcutDispatcher.h
     CheckOutDialog.cpp
     CheckOutDialog.h
     CheckOutDialog.ui