aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <[email protected]>2024-06-17 20:44:04 -0700
committerShawn Rutledge <[email protected]>2024-06-28 17:12:14 -0700
commit0debcf3ed3f62416be1c172454f420119df0587e (patch)
treecfb3bb664151bd87d07f34d19f98160ff0f058ba
parent3b6355fbaab2559dab301c803780768295089637 (diff)
Call QQuickItem::mouseUngrabEvent() on ungrab from QTabletEvent
As long as we rely on QGuiApplicationPrivate::processTabletEvent() to synthesize mouse events from tablet events (we aren't doing it on the fly as with touch events in 468626e99a90d6ac21cb311cde05c658ccb3b781), and as long as most QQuickItems are not handling tablet events, QQuickDeliveryAgentPrivate::onGrabChanged() almost always sees a QMouseEvent losing its grab when e.g. ListView takes over from one of its delegates while the user is trying to scroll with a stylus device. This event's device is the stylus, though. Whenever we see an EventPoint being canceled or ungrabbed, we must call either mouseUngrabEvent() on the item, or touchUngrabEvent() if all the points are released or cancelled, to avoid items getting "stuck" in pressed state. It must not be skipped. So call mouseUngrabEvent() whenever the event is a QSinglePointEvent, and touchUngrabEvent() otherwise (since only touchscreens send multi-point events). Make QQuickTest::pointerPress/Move/Release functions more correct for stylus devices: - we need the timestamp to monotonically increase, even though QTest::defaultMouseDelay() is usually 0 (which isn't sensible) - QTest::mouseEvent() calls qt_handleMouseEvent which converts logical coordinates to native positions; but for tablet events, do it here for now, since there are no QTest methods to generate them. This helps QQuickFlickablePrivate::handleMoveEvent() to calculate deltas correctly. Fixes: QTBUG-118903 Pick-to: 6.8 6.7 6.6 6.5 Change-Id: I5ec54c5181f5b9137fe16248884010aea94f671a Reviewed-by: Doris Verria <[email protected]>
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp33
-rw-r--r--src/quick/util/qquickdeliveryagent_p_p.h1
-rw-r--r--src/quicktestutils/quick/viewtestutils.cpp35
-rw-r--r--tests/auto/quick/qquicklistview/data/delegateWithMouseArea.qml3
-rw-r--r--tests/auto/quick/qquicklistview/tst_qquicklistview.cpp65
5 files changed, 122 insertions, 15 deletions
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp
index 1b2a888a22..9c63cf6b19 100644
--- a/src/quick/util/qquickdeliveryagent.cpp
+++ b/src/quick/util/qquickdeliveryagent.cpp
@@ -1538,6 +1538,28 @@ bool QQuickDeliveryAgentPrivate::isSynthMouse(const QPointerEvent *ev)
return (!isEventFromMouseOrTouchpad(ev) && isMouseEvent(ev));
}
+/*!
+ Returns \c true if \a dev is a type of device that only sends
+ QSinglePointEvents.
+*/
+bool QQuickDeliveryAgentPrivate::isSinglePointDevice(const QInputDevice *dev)
+{
+ switch (dev->type()) {
+ case QInputDevice::DeviceType::Mouse:
+ case QInputDevice::DeviceType::TouchPad:
+ case QInputDevice::DeviceType::Puck:
+ case QInputDevice::DeviceType::Stylus:
+ case QInputDevice::DeviceType::Airbrush:
+ return true;
+ case QInputDevice::DeviceType::TouchScreen:
+ case QInputDevice::DeviceType::Keyboard:
+ case QInputDevice::DeviceType::Unknown:
+ case QInputDevice::DeviceType::AllDevices:
+ return false;
+ }
+ return false;
+}
+
QQuickPointingDeviceExtra *QQuickDeliveryAgentPrivate::deviceExtra(const QInputDevice *device)
{
QInputDevicePrivate *devPriv = QInputDevicePrivate::get(const_cast<QInputDevice *>(device));
@@ -1843,17 +1865,18 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice
switch (transition) {
case QPointingDevice::CancelGrabExclusive:
case QPointingDevice::UngrabExclusive:
- if (isDeliveringTouchAsMouse()
- || point.device()->type() == QInputDevice::DeviceType::Mouse
- || point.device()->type() == QInputDevice::DeviceType::TouchPad) {
+ if (isDeliveringTouchAsMouse() || isSinglePointDevice(point.device())) {
+ // If an EventPoint from the mouse or the synth-mouse or from any
+ // mouse-like device is ungrabbed, call QQuickItem::mouseUngrabEvent().
QMutableSinglePointEvent e(QEvent::UngrabMouse, point.device(), point);
hasFiltered.clear();
if (!sendFilteredMouseEvent(&e, grabberItem, grabberItem->parentItem())) {
lastUngrabbed = grabberItem;
grabberItem->mouseUngrabEvent();
}
- }
- if (point.device()->type() == QInputDevice::DeviceType::TouchScreen) {
+ } else {
+ // Multi-point event: call QQuickItem::touchUngrabEvent() only if
+ // all eventpoints are released or cancelled.
bool allReleasedOrCancelled = true;
if (transition == QPointingDevice::UngrabExclusive && event) {
for (const auto &pt : event->points()) {
diff --git a/src/quick/util/qquickdeliveryagent_p_p.h b/src/quick/util/qquickdeliveryagent_p_p.h
index 878561bc12..3dd60db98f 100644
--- a/src/quick/util/qquickdeliveryagent_p_p.h
+++ b/src/quick/util/qquickdeliveryagent_p_p.h
@@ -151,6 +151,7 @@ public:
static bool isTabletEvent(const QPointerEvent *ev);
static bool isEventFromMouseOrTouchpad(const QPointerEvent *ev);
static bool isSynthMouse(const QPointerEvent *ev);
+ static bool isSinglePointDevice(const QInputDevice *dev);
static QQuickPointingDeviceExtra *deviceExtra(const QInputDevice *device);
// delivery of pointer events:
diff --git a/src/quicktestutils/quick/viewtestutils.cpp b/src/quicktestutils/quick/viewtestutils.cpp
index 053e864660..40a67a305e 100644
--- a/src/quicktestutils/quick/viewtestutils.cpp
+++ b/src/quicktestutils/quick/viewtestutils.cpp
@@ -9,6 +9,7 @@
#include <QtQuick/QQuickView>
#include <QtGui/QScreen>
#include <QtGui/qpa/qwindowsysteminterface.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtTest/QTest>
@@ -537,13 +538,17 @@ namespace QQuickTest {
break;
case QPointingDevice::DeviceType::Puck:
case QPointingDevice::DeviceType::Stylus:
- case QPointingDevice::DeviceType::Airbrush:
- QTest::lastMouseTimestamp += QTest::defaultMouseDelay();
+ case QPointingDevice::DeviceType::Airbrush:{
+ const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
+ const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(p), window);
+ const auto delay = QTest::defaultMouseDelay();
+ QTest::lastMouseTimestamp += delay ? delay : 1;
pressedTabletButton = button;
pressedTabletModifiers = modifiers;
- QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p),
+ QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
button, 0.8, 0, 0, 0, 0, 0, modifiers);
break;
+ }
default:
qWarning() << "can't send a press event from" << dev;
break;
@@ -563,11 +568,17 @@ namespace QQuickTest {
break;
case QPointingDevice::DeviceType::Puck:
case QPointingDevice::DeviceType::Stylus:
- case QPointingDevice::DeviceType::Airbrush:
- QTest::lastMouseTimestamp += QTest::defaultMouseDelay();
- QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p),
- pressedTabletButton, 0, 0, 0, 0, 0, 0, pressedTabletModifiers);
+ case QPointingDevice::DeviceType::Airbrush: {
+ const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
+ const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(p), window);
+ const auto delay = QTest::defaultMouseDelay();
+ // often QTest::defaultMouseDelay() == 0; but avoid infinite velocity
+ QTest::lastMouseTimestamp += delay ? delay : 1;
+ QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
+ pressedTabletButton, pressedTabletButton == Qt::NoButton ? 0 : 0.75,
+ 0, 0, 0, 0, 0, pressedTabletModifiers);
break;
+ }
default:
qWarning() << "can't send a move event from" << dev;
break;
@@ -588,11 +599,15 @@ namespace QQuickTest {
break;
case QPointingDevice::DeviceType::Puck:
case QPointingDevice::DeviceType::Stylus:
- case QPointingDevice::DeviceType::Airbrush:
- QTest::lastMouseTimestamp += QTest::defaultMouseDelay();
- QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p),
+ case QPointingDevice::DeviceType::Airbrush: {
+ const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
+ const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(p), window);
+ const auto delay = QTest::defaultMouseDelay();
+ QTest::lastMouseTimestamp += delay ? delay : 1;
+ QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
Qt::NoButton, 0, 0, 0, 0, 0, 0, modifiers);
break;
+ }
default:
qWarning() << "can't send a press event from" << dev;
break;
diff --git a/tests/auto/quick/qquicklistview/data/delegateWithMouseArea.qml b/tests/auto/quick/qquicklistview/data/delegateWithMouseArea.qml
index e0b8222bfb..4bdf7033a9 100644
--- a/tests/auto/quick/qquicklistview/data/delegateWithMouseArea.qml
+++ b/tests/auto/quick/qquicklistview/data/delegateWithMouseArea.qml
@@ -17,12 +17,15 @@ ListView {
width: 500
height: 500
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
+ border.width: 4
+ border.color: ma.pressed ? "red" : "white"
Text {
text: index
font.pixelSize: 128
anchors.centerIn: parent
}
MouseArea {
+ id: ma
anchors.fill: parent
}
}
diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
index a1e7d5b4ca..2629941135 100644
--- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
+++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
@@ -39,6 +39,8 @@ Q_DECLARE_METATYPE(QQuickListView::Orientation)
Q_DECLARE_METATYPE(QQuickFlickable::FlickableDirection)
Q_DECLARE_METATYPE(Qt::Key)
+Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
+
using namespace QQuickViewTestUtils;
using namespace QQuickVisualTestUtils;
@@ -268,6 +270,8 @@ private slots:
void addOnCompleted();
void setPositionOnLayout();
void touchCancel();
+ void cancelDelegatePastDragThreshold_data();
+ void cancelDelegatePastDragThreshold();
void resizeAfterComponentComplete();
void dragOverFloatingHeaderOrFooter();
@@ -340,6 +344,12 @@ private:
QQuickView *m_view;
QString testForView;
QPointingDevice *touchDevice = QTest::createTouchDevice();
+#if QT_CONFIG(tabletevent)
+ QScopedPointer<const QPointingDevice> tabletStylusDevice = QScopedPointer<const QPointingDevice>(
+ QPointingDevicePrivate::tabletDevice(QInputDevice::DeviceType::Stylus,
+ QPointingDevice::PointerType::Pen,
+ QPointingDeviceUniqueId::fromNumericId(1234567890)));
+#endif
};
class TestObject : public QObject
@@ -9744,6 +9754,61 @@ void tst_QQuickListView::touchCancel() // QTBUG-74679
QTRY_COMPARE(listview->contentY(), 500.0);
}
+void tst_QQuickListView::cancelDelegatePastDragThreshold_data()
+{
+ QTest::addColumn<const QPointingDevice *>("device");
+
+ QTest::newRow("primary") << QPointingDevice::primaryPointingDevice();
+ QTest::newRow("touch") << static_cast<const QPointingDevice*>(touchDevice); // TODO QTBUG-107864
+#if QT_CONFIG(tabletevent) && !defined(Q_OS_QNX)
+ QTest::newRow("stylus") << tabletStylusDevice.get();
+#endif
+}
+
+void tst_QQuickListView::cancelDelegatePastDragThreshold() // QTBUG-118903
+{
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ QFETCH(const QPointingDevice *, device);
+
+#if QT_CONFIG(tabletevent)
+ QVERIFY(qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents));
+#endif
+
+ QScopedPointer<QQuickView> window(createView());
+ window->setSource(testFileUrl("delegateWithMouseArea.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject());
+ QVERIFY(listview);
+ QQuickMouseArea *mouseArea = listview->currentItem()->findChild<QQuickMouseArea *>();
+ QVERIFY(mouseArea);
+ QSignalSpy canceledSpy(mouseArea, &QQuickMouseArea::canceled);
+
+ QPoint p = mouseArea->mapToScene(mouseArea->boundingRect().center()).toPoint();
+ // MouseArea grabs on press
+ QQuickTest::pointerPress(device, window.get(), 1, p);
+ QTRY_VERIFY(mouseArea->isPressed());
+ // drag past the drag threshold until ListView takes over the grab
+ p -= {0, dragThreshold + 1};
+ QQuickTest::pointerMove(device, window.get(), 1, p);
+ int movesWhenGrabbed = 1;
+ for (int i = 2; i < 6; ++i) {
+ p -= {0, 1};
+ QQuickTest::pointerMove(device, window.get(), 1, p);
+ if (device == tabletStylusDevice.get())
+ QTest::qWait(1);
+ if (listview->isDragging())
+ movesWhenGrabbed = i;
+ }
+ qCDebug(lcTests) << "ListView took over grab after" << movesWhenGrabbed << "moves";
+ QVERIFY(listview->isDragging());
+ // MouseArea's grab got canceled
+ QCOMPARE(canceledSpy.size(), 1);
+ QCOMPARE(mouseArea->isPressed(), false);
+ QQuickTest::pointerRelease(device, window.get(), 1, p);
+}
+
void tst_QQuickListView::resizeAfterComponentComplete() // QTBUG-76487
{
QScopedPointer<QQuickView> window(createView());