aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2024-05-06 13:10:03 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2024-05-30 21:18:56 -0700
commit1e325c5c8b98dd5ddf5ba2be8c017306474fe2b7 (patch)
treee54dc72bf62cf784f41b22d6af5ef760296529a8
parent823026646c62c7e3b42be1549fcb61debb468e60 (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.cpp3
-rw-r--r--src/quicktemplates/qquickpopup_p.h1
-rw-r--r--src/quicktemplates/qquickpopupwindow.cpp110
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);
}