From c7b763e02a337dce5f7bb0ead594fbce57c21a36 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Tue, 2 Nov 2021 12:09:47 +0100 Subject: Start dispatching events to QQuickWindow virtual functions again Users sometimes override those. Amends 68c103225f4e8bd6c1b18ef547108fd60f398c0f : we no longer bypass the "big switch" in QWindow::event() completely, but let it dispatch pointer events to any of the window virtual functions that may have been overridden, while using a guard to prevent them from calling back to QQuickWindow::event(). Fixes: QTBUG-97859 Change-Id: I2bb5258db9682a82a804fa5deca9eb8477cb38cc Reviewed-by: Volker Hilsheimer (cherry picked from commit 13a0da5bd2cf59aeb343fe9345b9bac2cfbb5e6f) Reviewed-by: Qt Cherry-pick Bot --- src/quick/items/qquickwindow.cpp | 54 ++++++++++- src/quick/items/qquickwindow_p.h | 1 + tests/auto/quick/qquickwindow/tst_qquickwindow.cpp | 108 +++++++++++++++++++-- 3 files changed, 153 insertions(+), 10 deletions(-) diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 9dbb61509c..fd65319f7b 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -1337,6 +1337,38 @@ bool QQuickWindow::event(QEvent *e) // bypass QWindow::event dispatching of input events: deliveryAgent takes care of it QQuickDeliveryAgent *da = d->deliveryAgent; if (e->isPointerEvent()) { + /* + We can't bypass the virtual functions like mousePressEvent() tabletEvent() etc., + for the sake of code that subclasses QQuickWindow and overrides them, even though + we no longer need them as entry points for Qt Quick event delivery. + So dispatch to them now, ahead of normal delivery, and stop them from calling + back into this function if they were called from here (avoid recursion). + It could also be that user code expects them to work as entry points, too; + in that case, windowEventDispatch _won't_ be set, so the event comes here and + we'll dispatch it further below. + */ + if (d->windowEventDispatch) + return false; + { + const bool wasAccepted = e->isAccepted(); + QBoolBlocker windowEventDispatchGuard(d->windowEventDispatch, true); + qCDebug(lcPtr) << "dispatching to window functions in case of override" << e; + QWindow::event(e); + if (e->isAccepted() && !wasAccepted) + return true; + } + /* + QQuickWindow does not override touchEvent(). If the application has a subclass + of QQuickWindow which allows the event to remain accepted, it means they want + to stop propagation here, so return early (below). But otherwise we will call + QWindow::touchEvent(), which will ignore(); in that case, we need to continue + with the usual delivery below, so we need to undo the ignore(). + */ + auto pe = static_cast(e); + if (QQuickDeliveryAgentPrivate::isTouchEvent(pe)) + e->accept(); + // end of dispatch to user-overridden virtual window functions + /* When delivering update and release events to existing grabbers, use the subscene delivery agent, if any. A possible scenario: @@ -1350,7 +1382,6 @@ bool QQuickWindow::event(QEvent *e) With single-point events (mouse, or only one finger) it's simplified: there can only be one subscene of interest; for (pt : pe->points()) would only iterate once, so we might as well skip that logic. */ - auto pe = static_cast(e); if (pe->pointCount()) { if (QQuickDeliveryAgentPrivate::subsceneAgentsExist) { bool ret = false; @@ -1521,13 +1552,18 @@ bool QQuickWindow::event(QEvent *e) else if (e->type() == QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure)) d->windowManager->handleContextCreationFailure(this); - return QWindow::event(e); + if (e->isPointerEvent()) + return true; + else + return QWindow::event(e); } /*! \reimp */ void QQuickWindow::keyPressEvent(QKeyEvent *e) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->deliverKeyEvent(e); @@ -1537,6 +1573,8 @@ void QQuickWindow::keyPressEvent(QKeyEvent *e) void QQuickWindow::keyReleaseEvent(QKeyEvent *e) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->deliverKeyEvent(e); @@ -1547,6 +1585,8 @@ void QQuickWindow::keyReleaseEvent(QKeyEvent *e) void QQuickWindow::wheelEvent(QWheelEvent *event) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->deliverSinglePointEventUntilAccepted(event); @@ -1558,6 +1598,8 @@ void QQuickWindow::wheelEvent(QWheelEvent *event) void QQuickWindow::tabletEvent(QTabletEvent *event) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->deliverPointerEvent(event); @@ -1568,6 +1610,8 @@ void QQuickWindow::tabletEvent(QTabletEvent *event) void QQuickWindow::mousePressEvent(QMouseEvent *event) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->handleMouseEvent(event); @@ -1576,6 +1620,8 @@ void QQuickWindow::mousePressEvent(QMouseEvent *event) void QQuickWindow::mouseMoveEvent(QMouseEvent *event) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->handleMouseEvent(event); @@ -1584,6 +1630,8 @@ void QQuickWindow::mouseMoveEvent(QMouseEvent *event) void QQuickWindow::mouseDoubleClickEvent(QMouseEvent *event) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->handleMouseEvent(event); @@ -1592,6 +1640,8 @@ void QQuickWindow::mouseDoubleClickEvent(QMouseEvent *event) void QQuickWindow::mouseReleaseEvent(QMouseEvent *event) { Q_D(QQuickWindow); + if (d->windowEventDispatch) + return; auto da = d->deliveryAgentPrivate(); Q_ASSERT(da); da->handleMouseEvent(event); diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 1a3ff31539..04c33afc74 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -287,6 +287,7 @@ public: uint hasActiveSwapchain : 1; uint hasRenderableSwapchain : 1; uint swapchainJustBecameRenderable : 1; + bool windowEventDispatch = false; private: static void cleanupNodesOnShutdown(QQuickItem *); diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index f88ce43c9b..6f08714fba 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -327,30 +327,68 @@ protected: } }; -class MouseRecordingWindow : public QQuickWindow +class PointerRecordingWindow : public QQuickWindow { public: - explicit MouseRecordingWindow(QWindow *parent = nullptr) : QQuickWindow(parent) { } + explicit PointerRecordingWindow(QWindow *parent = nullptr) : QQuickWindow(parent) { } protected: + bool event(QEvent *event) override { + if (event->isPointerEvent()) { + qCDebug(lcTests) << event; + m_events << PointerEvent { event->type(), static_cast(event)->pointingDevice() }; + } + return QQuickWindow::event(event); + } + void mousePressEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; - m_mouseEvents << event->source(); + m_mouseEvents << PointerEvent { event->type(), event->pointingDevice() }; QQuickWindow::mousePressEvent(event); } void mouseMoveEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; - m_mouseEvents << event->source(); + m_mouseEvents << PointerEvent { event->type(), event->pointingDevice() }; QQuickWindow::mouseMoveEvent(event); } void mouseReleaseEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; - m_mouseEvents << event->source(); + m_mouseEvents << PointerEvent { event->type(), event->pointingDevice() }; QQuickWindow::mouseReleaseEvent(event); } + void touchEvent(QTouchEvent * event) override { + qCDebug(lcTests) << event; + m_touchEvents << PointerEvent { event->type(), event->pointingDevice() }; + QQuickWindow::touchEvent(event); + } + +#if QT_CONFIG(tabletevent) + void tabletEvent(QTabletEvent * event) override { + qCDebug(lcTests) << event; + m_tabletEvents << PointerEvent { event->type(), event->pointingDevice() }; + QQuickWindow::tabletEvent(event); + } +#endif + +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent * event) override { + qCDebug(lcTests) << event; + m_tabletEvents << PointerEvent { event->type(), event->pointingDevice() }; + QQuickWindow::wheelEvent(event); + } +#endif + public: - QList m_mouseEvents; + struct PointerEvent + { + QEvent::Type type; + const QPointingDevice *device; + }; + QList m_events; + QList m_mouseEvents; + QList m_touchEvents; + QList m_tabletEvents; }; class MouseRecordingItem : public QQuickItem @@ -511,6 +549,9 @@ private slots: void testChildMouseEventFilter_data(); void cleanupGrabsOnRelease(); + void subclassWithPointerEventVirtualOverrides_data(); + void subclassWithPointerEventVirtualOverrides(); + #if QT_CONFIG(shortcut) void testShortCut(); #endif @@ -1250,7 +1291,7 @@ void tst_qquickwindow::synthMouseFromTouch() QFETCH(bool, acceptTouch); QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, synthMouse); - QScopedPointer window(new MouseRecordingWindow); + QScopedPointer window(new PointerRecordingWindow); QScopedPointer item(new MouseRecordingItem(acceptTouch, nullptr)); item->setParentItem(window->contentItem()); window->resize(250, 250); @@ -1295,7 +1336,7 @@ void tst_qquickwindow::synthMouseDoubleClickFromTouch() QFETCH(bool, expectedSynthesizedDoubleClickEvent); QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true); - QScopedPointer window(new MouseRecordingWindow); + QScopedPointer window(new PointerRecordingWindow); QScopedPointer item(new MouseRecordingItem(false, nullptr)); item->setParentItem(window->contentItem()); window->resize(250, 250); @@ -3578,6 +3619,57 @@ void tst_qquickwindow::cleanupGrabsOnRelease() QCOMPARE(parent->mouseUngrabEventCount, 1); } +void tst_qquickwindow::subclassWithPointerEventVirtualOverrides_data() +{ + QTest::addColumn("deviceType"); + + QTest::newRow("mouse click") << QPointingDevice::DeviceType::Mouse; + QTest::newRow("touch tap") << QPointingDevice::DeviceType::TouchScreen; + QTest::newRow("stylus tap") << QPointingDevice::DeviceType::Stylus; +} + +void tst_qquickwindow::subclassWithPointerEventVirtualOverrides() // QTBUG-97859 +{ + QFETCH(QPointingDevice::DeviceType, deviceType); + + PointerRecordingWindow window; + window.resize(250, 250); + window.setPosition(100, 100); + window.setTitle(QTest::currentTestFunction()); + window.show(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + const qint64 stylusId = 1234567890; + + const QPoint pos(120, 120); + switch (static_cast(deviceType)) { + case QPointingDevice::DeviceType::Mouse: + QTest::mouseClick(&window, Qt::LeftButton, Qt::NoModifier, pos); + QTRY_COMPARE(window.m_mouseEvents.count(), 3); // separate move before press + QCOMPARE(window.m_events.count(), 3); + break; + case QPointingDevice::DeviceType::TouchScreen: + QTest::touchEvent(&window, touchDevice).press(0, pos, &window); + QTest::touchEvent(&window, touchDevice).release(0, pos, &window); + QTRY_COMPARE(window.m_touchEvents.count(), 2); + QCOMPARE(window.m_events.count(), 2); + break; + case QPointingDevice::DeviceType::Stylus: + // press (pressure is 0.8) + QWindowSystemInterface::handleTabletEvent(&window, pos, window.mapToGlobal(pos), + int(QInputDevice::DeviceType::Stylus), int(QPointingDevice::PointerType::Pen), + Qt::LeftButton, 0.8, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); + // release (pressure is 0) + QWindowSystemInterface::handleTabletEvent(&window, pos, window.mapToGlobal(pos), + int(QInputDevice::DeviceType::Stylus), int(QPointingDevice::PointerType::Pen), + Qt::NoButton, 0, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); + QTRY_COMPARE(window.m_tabletEvents.count(), 2); + QVERIFY(window.m_events.count() >= window.m_tabletEvents.count()); // tablet + synth-mouse events + break; + default: + break; + } +} + #if QT_CONFIG(shortcut) void tst_qquickwindow::testShortCut() { -- cgit v1.2.3