aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2022-07-07 22:23:37 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-08-26 04:49:27 +0000
commit8358d6b2788b62e91b0a76b805ae4010eb932fb2 (patch)
tree30f04c817f75d8e269a05485172b47b27fa7db58 /tests
parent0dc6034f9153a6a188d86f6ae5b08a27ce08800a (diff)
Ensure that multiple HoverHandlers can react to multiple device types
1c44804600ad3dbeb60d1f5209ce9cf937d30ab3 had some known incompleteness in QQuickItemPrivate::effectiveCursorHandler because it couldn't be finished in Qt 5; but HoverHandlers with different acceptedDevices and acceptedPointerTypes were working together in Qt 6.0 and 6.1 to an extent. Perhaps for this case it helped that HoverHandlers got passive grabs, but we stopped that in bbcc2657fa0dbf715e6db7d675662e4be94a1e04. So now, as with mouse events, we need to ensure that when a HoverHandler detects a particular stylus device in a QTabletEvent and chooses a different cursor, it is applied to the window. At this time, since QQuickDeliveryAgentPrivate::deliverHoverEvent() sends a synth-mouse event, it's not suitable for tablet hover; so we depend on correct implementation of allPointsGrabbed() to ensure that QQuickDeliveryAgentPrivate::deliverUpdatedPoints() will visit all the HoverHandlers, in this case only. Fixes: QTBUG-101932 Change-Id: Ia8f31610e9252825afc7151be58765ac5217b0e8 Reviewed-by: Doris Verria <doris.verria@qt.io> (cherry picked from commit 79893e1773be9d04208243cb88c1daf793d830a4) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverDeviceCursors.qml49
-rw-r--r--tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp98
2 files changed, 147 insertions, 0 deletions
diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverDeviceCursors.qml b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverDeviceCursors.qml
new file mode 100644
index 0000000000..edb56ffdc6
--- /dev/null
+++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverDeviceCursors.qml
@@ -0,0 +1,49 @@
+import QtQuick
+
+Item {
+ width: 200; height: 200
+
+ HoverHandler {
+ objectName: "stylus"
+ acceptedDevices: PointerDevice.Stylus
+ acceptedPointerTypes: PointerDevice.Pen
+ cursorShape: Qt.CrossCursor
+ }
+
+ HoverHandler {
+ objectName: "stylus eraser"
+ acceptedDevices: PointerDevice.Stylus
+ acceptedPointerTypes: PointerDevice.Eraser
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ HoverHandler {
+ objectName: "airbrush"
+ acceptedDevices: PointerDevice.Airbrush
+ acceptedPointerTypes: PointerDevice.Pen
+ cursorShape: Qt.BusyCursor
+ }
+
+ HoverHandler {
+ objectName: "airbrush eraser"
+ acceptedDevices: PointerDevice.Airbrush
+ acceptedPointerTypes: PointerDevice.Eraser
+ cursorShape: Qt.OpenHandCursor
+ }
+
+ HoverHandler {
+ objectName: "mouse"
+ acceptedDevices: PointerDevice.Mouse
+ // acceptedPointerTypes can be omitted because Mouse is not ambiguous.
+ // When a genuine mouse move is sent, there's a conflict, and this one should win.
+ cursorShape: Qt.IBeamCursor
+ }
+
+ HoverHandler {
+ objectName: "conflictingMouse"
+ acceptedDevices: PointerDevice.Mouse
+ // acceptedPointerTypes can be omitted because Mouse is not ambiguous.
+ // When a genuine mouse move is sent, there's a conflict, and this one should lose.
+ cursorShape: Qt.ClosedHandCursor
+ }
+}
diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp
index fcd2c5c49b..513573311f 100644
--- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp
+++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp
@@ -6,6 +6,7 @@
#include <QtQuick/qquickview.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/private/qquickhoverhandler_p.h>
+#include <QtQuick/private/qquickpointerhandler_p_p.h>
#include <QtQuick/private/qquickmousearea_p.h>
#include <qpa/qwindowsysteminterface.h>
@@ -41,6 +42,8 @@ private slots:
void movingItemWithHoverHandler();
void margin();
void window();
+ void deviceCursor_data();
+ void deviceCursor();
private:
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
@@ -383,6 +386,101 @@ void tst_HoverHandler::window() // QTBUG-98717
#endif
}
+void tst_HoverHandler::deviceCursor_data()
+{
+ QTest::addColumn<bool>("synthMouseForTabletEvents");
+ QTest::addColumn<bool>("earlierTabletBeforeMouse");
+
+ QTest::newRow("nosynth, tablet wins") << false << false;
+ QTest::newRow("synth, tablet wins") << true << false;
+ QTest::newRow("synth, mouse wins") << true << true;
+}
+
+void tst_HoverHandler::deviceCursor()
+{
+ QFETCH(bool, synthMouseForTabletEvents);
+ QFETCH(bool, earlierTabletBeforeMouse);
+ qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents, synthMouseForTabletEvents);
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl("hoverDeviceCursors.qml")));
+ // Ensure that we don't get extra hover events delivered on the side
+ QQuickWindowPrivate::get(&window)->deliveryAgentPrivate()->frameSynchronousHoverEnabled = false;
+ // And flush out any mouse events that might be queued up in QPA, since QTest::mouseMove() calls processEvents.
+ qGuiApp->processEvents();
+ const QQuickItem *root = window.rootObject();
+ QQuickHoverHandler *stylusHandler = root->findChild<QQuickHoverHandler *>("stylus");
+ QVERIFY(stylusHandler);
+ QQuickHoverHandler *eraserHandler = root->findChild<QQuickHoverHandler *>("stylus eraser");
+ QVERIFY(eraserHandler);
+ QQuickHoverHandler *aibrushHandler = root->findChild<QQuickHoverHandler *>("airbrush");
+ QVERIFY(aibrushHandler);
+ QQuickHoverHandler *airbrushEraserHandler = root->findChild<QQuickHoverHandler *>("airbrush eraser");
+ QVERIFY(airbrushEraserHandler);
+ QQuickHoverHandler *mouseHandler = root->findChild<QQuickHoverHandler *>("mouse");
+ QVERIFY(mouseHandler);
+
+ QPoint point(100, 100);
+
+#if QT_CONFIG(tabletevent)
+ const qint64 stylusId = 1234567890;
+ QElapsedTimer timer;
+ timer.start();
+ auto testStylusDevice = [&](QInputDevice::DeviceType dt, QPointingDevice::PointerType pt,
+ Qt::CursorShape expectedCursor, QQuickHoverHandler* expectedActiveHandler) {
+ // We will follow up with a mouse event afterwards, and we want to simulate that the tablet events occur
+ // either slightly before (earlierTabletBeforeMouse == true) or some time before.
+ // It turns out that the first mouse move happens at timestamp 501 (simulated).
+ const ulong timestamp = (earlierTabletBeforeMouse ? 0 : 400) + timer.elapsed();
+ qCDebug(lcPointerTests) << "@" << timestamp << "sending" << dt << pt << "expecting" << expectedCursor << expectedActiveHandler->objectName();
+ QWindowSystemInterface::handleTabletEvent(&window, timestamp, point, window.mapToGlobal(point),
+ int(dt), int(pt), Qt::NoButton, 0, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier);
+ point += QPoint(1, 0);
+#if QT_CONFIG(cursor)
+ // QQuickItem::setCursor() doesn't get called: we only have HoverHandlers in this test
+ QCOMPARE(root->cursor().shape(), Qt::ArrowCursor);
+ QTRY_COMPARE(window.cursor().shape(), expectedCursor);
+#endif
+ QCOMPARE(stylusHandler->isHovered(), stylusHandler == expectedActiveHandler);
+ QCOMPARE(eraserHandler->isHovered(), eraserHandler == expectedActiveHandler);
+ QCOMPARE(aibrushHandler->isHovered(), aibrushHandler == expectedActiveHandler);
+ QCOMPARE(airbrushEraserHandler->isHovered(), airbrushEraserHandler == expectedActiveHandler);
+ };
+
+ // simulate move events from various tablet stylus types
+ testStylusDevice(QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen,
+ Qt::CrossCursor, stylusHandler);
+ testStylusDevice(QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Eraser,
+ Qt::PointingHandCursor, eraserHandler);
+ testStylusDevice(QInputDevice::DeviceType::Airbrush, QPointingDevice::PointerType::Pen,
+ Qt::BusyCursor, aibrushHandler);
+ testStylusDevice(QInputDevice::DeviceType::Airbrush, QPointingDevice::PointerType::Eraser,
+ Qt::OpenHandCursor, airbrushEraserHandler);
+
+ QTest::qWait(200);
+ qCDebug(lcPointerTests) << "---- no more tablet events, now we send a mouse move";
+#endif
+
+ // move the mouse: the mouse-specific HoverHandler gets to set the cursor only if
+ // more than kCursorOverrideTimeout ms have elapsed
+ QTest::mouseMove(&window, point);
+ QTRY_COMPARE(mouseHandler->isHovered(), true);
+ const bool afterTimeout =
+ QQuickPointerHandlerPrivate::get(airbrushEraserHandler)->lastEventTime + 100 <
+ QQuickPointerHandlerPrivate::get(mouseHandler)->lastEventTime;
+ qCDebug(lcPointerTests) << "airbrush handler reacted last time:" << QQuickPointerHandlerPrivate::get(airbrushEraserHandler)->lastEventTime
+ << "and the mouse handler reacted at time:" << QQuickPointerHandlerPrivate::get(mouseHandler)->lastEventTime
+ << "so > 100 ms have elapsed?" << afterTimeout;
+#if QT_CONFIG(cursor)
+ QCOMPARE(window.cursor().shape(), afterTimeout ? Qt::IBeamCursor : Qt::OpenHandCursor);
+#endif
+ QCOMPARE(stylusHandler->isHovered(), false);
+ QCOMPARE(eraserHandler->isHovered(), false);
+ QCOMPARE(aibrushHandler->isHovered(), false);
+#if QT_CONFIG(tabletevent)
+ QCOMPARE(airbrushEraserHandler->isHovered(), true); // there was no fresh QTabletEvent to tell it not to be hovered
+#endif
+}
+
QTEST_MAIN(tst_HoverHandler)
#include "tst_qquickhoverhandler.moc"