diff options
author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2024-05-06 13:10:03 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2024-05-30 21:18:56 -0700 |
commit | 1e325c5c8b98dd5ddf5ba2be8c017306474fe2b7 (patch) | |
tree | e54dc72bf62cf784f41b22d6af5ef760296529a8 | |
parent | 823026646c62c7e3b42be1549fcb61debb468e60 (diff) |
QQuickPopupWindow: forward pointer events to the menu under the mousewip/nativemenus
We want to handle menus special compared to other popups.
For menus, we want all open parent menus and sub menus that
belong together to almost act as a single popup WRT event
delivery. This will allow the user to hover and highlight
MenuItems inside all of them, and not just the leaf menu.
This patch will therefore forward all relevant pointer events
that goes to a leaf menu to the actual menu, or menu bar,
under the mouse. But note that this forwarding will happen
in parallel with normal event delivery (we don't eat the
events), as we don't want to break the delivery to e.g
grabbers, and other DA logic in general.
Change-Id: If87756e59c08ff73cb75a21fafab6cf39e4c94b6
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r-- | src/quicktemplates/qquickmenu.cpp | 3 | ||||
-rw-r--r-- | src/quicktemplates/qquickpopup_p.h | 1 | ||||
-rw-r--r-- | src/quicktemplates/qquickpopupwindow.cpp | 110 |
3 files changed, 104 insertions, 10 deletions
diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp index d5f1a72f5b..3ab99ecd87 100644 --- a/src/quicktemplates/qquickmenu.cpp +++ b/src/quicktemplates/qquickmenu.cpp @@ -952,6 +952,9 @@ bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const */ bool QQuickMenuPrivate::handleReleaseWithoutGrab(const QEventPoint &eventPoint) { + if (!contains(eventPoint.scenePosition())) + return false; + QQuickMenuItem *menuItem = nullptr; // Usually, hover events have occurred, and currentIndex is set. // If not, use eventPoint.position() for picking. diff --git a/src/quicktemplates/qquickpopup_p.h b/src/quicktemplates/qquickpopup_p.h index 56287d4a03..1ca4a18621 100644 --- a/src/quicktemplates/qquickpopup_p.h +++ b/src/quicktemplates/qquickpopup_p.h @@ -451,6 +451,7 @@ private: Q_DISABLE_COPY(QQuickPopup) Q_DECLARE_PRIVATE(QQuickPopup) + friend class QQuickPopupWindow; friend class QQuickPopupItem; friend class QQuickOverlay; friend class QQuickOverlayPrivate; diff --git a/src/quicktemplates/qquickpopupwindow.cpp b/src/quicktemplates/qquickpopupwindow.cpp index 03d7472bf1..d65be0ebb4 100644 --- a/src/quicktemplates/qquickpopupwindow.cpp +++ b/src/quicktemplates/qquickpopupwindow.cpp @@ -5,9 +5,13 @@ #include "qquickcombobox_p.h" #include "qquickpopup_p.h" #include "qquickpopup_p_p.h" +#include "qquickmenu_p_p.h" +#include "qquickmenubar_p_p.h" #include "qquickpopupitem_p_p.h" +#include <QtGui/private/qguiapplication_p.h> #include <QtCore/qloggingcategory.h> +#include <QtGui/private/qeventpoint_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickwindowmodule_p.h> #include <QtQuick/private/qquickwindowmodule_p_p.h> @@ -24,6 +28,8 @@ class QQuickPopupWindowPrivate : public QQuickWindowQmlImplPrivate public: QPointer<QQuickItem> m_popupItem; QPointer<QQuickPopup> m_popup; + + void forwardEventToParentMenuOrMenuBar(QEvent *event); }; QQuickPopupWindow::QQuickPopupWindow(QQuickPopup *popup, QWindow *parent) @@ -99,19 +105,103 @@ void QQuickPopupWindow::resizeEvent(QResizeEvent *e) d->m_popupItem->setHeight(e->size().height()); } +/*! \internal + We want to handle menus specially, compared to other popups. For menus, we + want all open parent menus and sub menus that belong together to almost + act as a single popup WRT hover event delivery. This will allow the user to + hover and highlight MenuItems inside all of them, not just the leaf menu. + This function will therefore find the menu, or menu bar, under the event's + position, and forward the event to it. But note that this forwarding will + happen in parallel with normal event delivery (we don't eat the events), as + we don't want to break the delivery to e.g grabbers. + */ +void QQuickPopupWindowPrivate::forwardEventToParentMenuOrMenuBar(QEvent *event) +{ + Q_Q(QQuickPopupWindow); + + if (!event->isPointerEvent()) + return; + auto menu = qobject_cast<QQuickMenu *>(q->popup()); + if (!menu) + return; + + auto *pe = static_cast<QPointerEvent *>(event); + const QPointF globalPos = pe->points().first().globalPosition(); + + // If there is a Menu or a MenuBar under the mouse, resolve its window. + QQuickPopupWindow *targetPopupWindow = nullptr; + QQuickWindow *targetWindow = nullptr; + + QObject *menuParent = menu; + while (menuParent) { + if (auto parentMenu = qobject_cast<QQuickMenu *>(menuParent)) { + QQuickPopupWindow *popupWindow = QQuickMenuPrivate::get(parentMenu)->popupWindow; + auto popup_d = QQuickPopupPrivate::get(popupWindow->popup()); + QPointF scenePos = popupWindow->contentItem()->mapFromGlobal(globalPos); + if (popup_d->contains(scenePos)) { + targetPopupWindow = popupWindow; + targetWindow = popupWindow; + break; + } + } else if (auto menuBar = qobject_cast<QQuickMenuBar *>(menuParent)) { + const QPointF menuBarPos = menuBar->mapFromGlobal(globalPos); + if (menuBar->contains(menuBarPos)) + targetWindow = menuBar->window(); + break; + } + + menuParent = menuParent->parent(); + } + + if (!targetPopupWindow) { + if (pe->isBeginEvent()) { + // A QQuickPopupWindow can be bigger than the Popup itself, to make room + // for a drop-shadow. Close all popups if the user clicks either on the + // shadow or outside the window. + // QGuiApplicationPrivate::closeAllPopups(); // TODO as soon as dependency update is done + return; + } + } + + if (!targetWindow) + return; + + if (pe->isUpdateEvent()){ + // Forward move events to the target window + const auto scenePos = pe->point(0).scenePosition(); + const auto translatedScenePos = targetWindow->mapFromGlobal(globalPos); + QMutableEventPoint::setScenePosition(pe->point(0), translatedScenePos); + auto *grabber = pe->exclusiveGrabber(pe->point(0)); + + if (grabber) { + // Temporarily disable the grabber, to stop the delivery agent inside + // targetWindow from forwarding the event to an item outside the menu + // or menubar. This is especially important to support a press on e.g + // a MenuBarItem, followed by a drag-and-release on top of a MenuItem. + pe->setExclusiveGrabber(pe->point(0), nullptr); + } + + qCDebug(lcPopupWindow) << "forwarding" << pe << "to popup menu:" << targetWindow; + QQuickWindowPrivate::get(targetWindow)->deliveryAgent->event(pe); + + // Restore the event before we return + QMutableEventPoint::setScenePosition(pe->point(0), scenePos); + if (grabber) + pe->setExclusiveGrabber(pe->point(0), grabber); + } else if (pe->isEndEvent()) { + // To support opening a Menu on press (e.g on a MenuBarItem), followed by + // a drag and release on a MenuItem inside the Menu, we ask the Menu to + // perform a click on the active MenuItem, if any. + if (targetPopupWindow) + QQuickPopupPrivate::get(targetPopupWindow->popup())->handleReleaseWithoutGrab(pe->point(0)); + } +} + bool QQuickPopupWindow::event(QEvent *e) { Q_D(QQuickPopupWindow); - if (e->isPointerEvent()) { - auto *pe = static_cast<QPointerEvent *>(e); - const auto &eventPoint = pe->points().first(); - const auto *grabber = qmlobject_cast<QQuickItem *>(pe->exclusiveGrabber(eventPoint)); - if (pe->isEndEvent() && (!grabber || grabber->window() != this)) - QQuickPopupPrivate::get(d->m_popup)->handleReleaseWithoutGrab(eventPoint); - // handleReleaseWithoutGrab() returns true if the popup handled eventPoint. - // Nevertheless, the component that opened the menu needs to see the release too, - // to avoid getting stuck in "pressed" state; so we don't return early. - } + d->forwardEventToParentMenuOrMenuBar(e); + return QQuickWindowQmlImpl::event(e); } |