From 3edc2cb5437c3829e975b59009b641d4abca5084 Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Fri, 17 Feb 2023 09:35:50 +0100 Subject: QGesture: make sure we copy timestamp value for event clones Otherwise, double-click recognition will fail. Use QEvent::clone when possible, or set the timestamp explicitly when not. As a drive-by, remove some long-dead code in affected code lines. Fixes: QTBUG-102010 Change-Id: I882bf6e8090bf6f182b7a0a3c62aa3a4c8db2e14 Reviewed-by: Shawn Rutledge (cherry picked from commit fb09c82a2c7c44d41a0a36d8fe6d6d22e792668a) Reviewed-by: Volker Hilsheimer --- src/widgets/util/qflickgesture.cpp | 45 ++-------------- .../auto/widgets/util/qscroller/tst_qscroller.cpp | 62 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/widgets/util/qflickgesture.cpp b/src/widgets/util/qflickgesture.cpp index 71a515a1cd..128013d0e1 100644 --- a/src/widgets/util/qflickgesture.cpp +++ b/src/widgets/util/qflickgesture.cpp @@ -38,43 +38,19 @@ static QMouseEvent *copyMouseEvent(QEvent *e) case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: { - QMouseEvent *me = static_cast(e); - QMouseEvent *cme = new QMouseEvent(me->type(), QPoint(0, 0), me->scenePosition(), me->globalPosition(), - me->button(), me->buttons(), me->modifiers(), me->source()); - return cme; + return static_cast(e->clone()); } #if QT_CONFIG(graphicsview) case QEvent::GraphicsSceneMousePress: case QEvent::GraphicsSceneMouseRelease: case QEvent::GraphicsSceneMouseMove: { QGraphicsSceneMouseEvent *me = static_cast(e); -#if 1 QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress : (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove); QMouseEvent *cme = new QMouseEvent(met, QPoint(0, 0), QPoint(0, 0), me->screenPos(), me->button(), me->buttons(), me->modifiers(), me->source()); + cme->setTimestamp(me->timestamp()); return cme; -#else - QGraphicsSceneMouseEvent *copy = new QGraphicsSceneMouseEvent(me->type()); - copy->setPos(me->pos()); - copy->setScenePos(me->scenePos()); - copy->setScreenPos(me->screenPos()); - for (int i = 0x1; i <= 0x10; i <<= 1) { - Qt::MouseButton button = Qt::MouseButton(i); - copy->setButtonDownPos(button, me->buttonDownPos(button)); - copy->setButtonDownScenePos(button, me->buttonDownScenePos(button)); - copy->setButtonDownScreenPos(button, me->buttonDownScreenPos(button)); - } - copy->setLastPos(me->lastPos()); - copy->setLastScenePos(me->lastScenePos()); - copy->setLastScreenPos(me->lastScreenPos()); - copy->setButtons(me->buttons()); - copy->setButton(me->button()); - copy->setModifiers(me->modifiers()); - copy->setSource(me->source()); - copy->setFlags(me->flags()); - return copy; -#endif } #endif // QT_CONFIG(graphicsview) default: @@ -190,22 +166,6 @@ public: mouseTarget = nullptr; } else if (mouseTarget) { // we did send a press, so we need to fake a release now - - // release all pressed mouse buttons - /* Qt::MouseButtons mouseButtons = QGuiApplication::mouseButtons(); - for (int i = 0; i < 32; ++i) { - if (mouseButtons & (1 << i)) { - Qt::MouseButton b = static_cast(1 << i); - mouseButtons &= ~b; - QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); - - qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; - QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, - b, mouseButtons, QGuiApplication::keyboardModifiers()); - sendMouseEvent(&re); - } - }*/ - QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; @@ -265,6 +225,7 @@ protected: mouseTarget->topLevelWidget()->mapFromGlobal(me->globalPosition()), me->globalPosition(), me->button(), me->buttons(), me->modifiers(), me->source(), me->pointingDevice()); + copy.setTimestamp(me->timestamp()); qt_sendSpontaneousEvent(mouseTarget, ©); } diff --git a/tests/auto/widgets/util/qscroller/tst_qscroller.cpp b/tests/auto/widgets/util/qscroller/tst_qscroller.cpp index 491d5d48e3..1b64a34593 100644 --- a/tests/auto/widgets/util/qscroller/tst_qscroller.cpp +++ b/tests/auto/widgets/util/qscroller/tst_qscroller.cpp @@ -103,6 +103,7 @@ private slots: void scroll(); void overshoot(); void multipleWindows(); + void mouseEventTimestamp(); private: QPointingDevice *m_touchScreen = QTest::createTouchDevice(); @@ -516,6 +517,67 @@ void tst_QScroller::multipleWindows() #endif } +/*! + This test verifies that mouse events arrive at the target widget + with valid timestamp, even if there is a gesture filtering (and then + replaying a copy of) the event. QTBUG-102010 + + We cannot truly simulate the double click here, as simulated events don't + go through the exact same event machinery as real events, so double clicks + don't get generated by Qt here. But we can verify that the timestamps of + the eventually delivered events are maintained. +*/ +void tst_QScroller::mouseEventTimestamp() +{ +#if QT_CONFIG(gestures) && QT_CONFIG(scroller) + QScopedPointer sw(new tst_QScrollerWidget()); + sw->scrollArea = QRectF(0, 0, 1000, 1000); + QScroller::grabGesture(sw.data(), QScroller::LeftMouseButtonGesture); + sw->setGeometry(100, 100, 400, 300); + sw->show(); + QApplication::setActiveWindow(sw.data()); + if (!QTest::qWaitForWindowExposed(sw.data()) || !QTest::qWaitForWindowActive(sw.data())) + QSKIP("Failed to show and activate window"); + + QScroller *s1 = QScroller::scroller(sw.data()); + + struct EventFilter : QObject + { + QList timestamps; + protected: + bool eventFilter(QObject *o, QEvent *e) override + { + if (e->isInputEvent()) + timestamps << static_cast(e)->timestamp(); + return QObject::eventFilter(o, e); + } + + } eventFilter; + sw->installEventFilter(&eventFilter); + + const int interval = QGuiApplication::styleHints()->mouseDoubleClickInterval() / 10; + const QPoint point = sw->geometry().center(); + // Simulate double by pressing twice within the double click interval. + // Presses are filtered and then delayed by the scroller/gesture machinery, + // so we first record all events, and then make sure that the relative timestamps + // are maintained also for the replayed or synthesized events. + QTest::mousePress(sw->windowHandle(), Qt::LeftButton, {}, point); + QCOMPARE(s1->state(), QScroller::Pressed); + QTest::mouseRelease(sw->windowHandle(), Qt::LeftButton, {}, point, interval); + QCOMPARE(s1->state(), QScroller::Inactive); + QTest::mousePress(sw->windowHandle(), Qt::LeftButton, {}, point, interval); + QCOMPARE(s1->state(), QScroller::Pressed); + // also filtered and delayed by the scroller + QTest::mouseRelease(sw->windowHandle(), Qt::LeftButton, {}, point, interval); + QCOMPARE(s1->state(), QScroller::Inactive); + int lastTimestamp = -1; + for (int timestamp : std::as_const(eventFilter.timestamps)) { + QCOMPARE_GE(timestamp, lastTimestamp); + lastTimestamp = timestamp + interval; + } +#endif +} + QTEST_MAIN(tst_QScroller) #include "tst_qscroller.moc" -- cgit v1.2.3