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 <shawn.rutledge@qt.io>
(cherry picked from commit fb09c82a2c7c44d41a0a36d8fe6d6d22e792668a)
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Volker Hilsheimer 2023-02-17 09:35:50 +01:00
parent 33eeba416f
commit 3edc2cb543
2 changed files with 65 additions and 42 deletions

View File

@ -38,43 +38,19 @@ static QMouseEvent *copyMouseEvent(QEvent *e)
case QEvent::MouseButtonPress: case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease: case QEvent::MouseButtonRelease:
case QEvent::MouseMove: { case QEvent::MouseMove: {
QMouseEvent *me = static_cast<QMouseEvent *>(e); return static_cast<QMouseEvent *>(e->clone());
QMouseEvent *cme = new QMouseEvent(me->type(), QPoint(0, 0), me->scenePosition(), me->globalPosition(),
me->button(), me->buttons(), me->modifiers(), me->source());
return cme;
} }
#if QT_CONFIG(graphicsview) #if QT_CONFIG(graphicsview)
case QEvent::GraphicsSceneMousePress: case QEvent::GraphicsSceneMousePress:
case QEvent::GraphicsSceneMouseRelease: case QEvent::GraphicsSceneMouseRelease:
case QEvent::GraphicsSceneMouseMove: { case QEvent::GraphicsSceneMouseMove: {
QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(e); QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(e);
#if 1
QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress : QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress :
(me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove); (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove);
QMouseEvent *cme = new QMouseEvent(met, QPoint(0, 0), QPoint(0, 0), me->screenPos(), QMouseEvent *cme = new QMouseEvent(met, QPoint(0, 0), QPoint(0, 0), me->screenPos(),
me->button(), me->buttons(), me->modifiers(), me->source()); me->button(), me->buttons(), me->modifiers(), me->source());
cme->setTimestamp(me->timestamp());
return cme; 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) #endif // QT_CONFIG(graphicsview)
default: default:
@ -190,22 +166,6 @@ public:
mouseTarget = nullptr; mouseTarget = nullptr;
} else if (mouseTarget) { } else if (mouseTarget) {
// we did send a press, so we need to fake a release now // 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<Qt::MouseButton>(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); QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX);
qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; 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(), mouseTarget->topLevelWidget()->mapFromGlobal(me->globalPosition()), me->globalPosition(),
me->button(), me->buttons(), me->modifiers(), me->button(), me->buttons(), me->modifiers(),
me->source(), me->pointingDevice()); me->source(), me->pointingDevice());
copy.setTimestamp(me->timestamp());
qt_sendSpontaneousEvent(mouseTarget, &copy); qt_sendSpontaneousEvent(mouseTarget, &copy);
} }

View File

@ -103,6 +103,7 @@ private slots:
void scroll(); void scroll();
void overshoot(); void overshoot();
void multipleWindows(); void multipleWindows();
void mouseEventTimestamp();
private: private:
QPointingDevice *m_touchScreen = QTest::createTouchDevice(); QPointingDevice *m_touchScreen = QTest::createTouchDevice();
@ -516,6 +517,67 @@ void tst_QScroller::multipleWindows()
#endif #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<tst_QScrollerWidget> 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<int> timestamps;
protected:
bool eventFilter(QObject *o, QEvent *e) override
{
if (e->isInputEvent())
timestamps << static_cast<QInputEvent *>(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) QTEST_MAIN(tst_QScroller)
#include "tst_qscroller.moc" #include "tst_qscroller.moc"