aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2020-09-30 19:36:09 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2020-11-17 08:24:44 +0100
commita2209698d3584a7c05d0c12aa61de050fe0e78fd (patch)
tree58fc82632a88af5988b5c20dd37b44ecc67abf40 /src
parenta8122590058e57a340a42eab0a34010a3a3c5271 (diff)
Don't synthesize mouse from touch for items that accept touch
Followup to 1457df74f4c1d770e1e820de8cd082be1bd2489e : if an item that has acceptTouchEvents() == true merely fails to accept one touch event, that does not mean a mouse event should be sent. Finish changing the default to false: handling touch events is opt-in, just like handling mouse events; most items don't. And if you opt in, then you MUST handle touch events, because you will NOT receive mouse events as a fall-back. Now that Flickable handles touch, filtering multi-touch events becomes relevant. There was a failure in tst_touchmouse::mouseOnFlickableOnPinch when Flickable grabs a stationary touchpoint at the same time as another touchpoint is pressed, preventing a child PinchArea from reacting. So there's a new rule: just as we start over with event delivery when a new point is pressed, QQuickFlickable::filterPointerEvent() should also not immediately grab when any point is newly pressed; it can afford to wait, because it's filtering, so it will be able to see if one point is dragged past the drag threshold later on. When a parent (such as Flickable) contains only mouse-handling items (such as MouseArea), the parent should filter the touch event if it is able (if acceptTouchEvents() returns true). Flickable is now able to. Filtering parents that are not able to filter touch events can still filter a synth-mouse event as before. But filtering both must be avoided: then we would have the problem that Flickable filters a touch move, sees that it's being dragged past the drag threshold, and sets d->stealMouse to true to indicate that it wants to steal the _next_ event; then it filters a synth-mouse move, and that's perceived as being the next event even though it's just a different view of the same event, so it steals it. In tst_qquickflickable::nestedMouseAreaUsingTouch we rely on the delay caused by waiting for the next event: the MouseArea is trying to drag an item and the Flickable wants to flick; both of them decide on the same event that the drag threshold is exceeded. But MouseArea calls setKeepMouseGrab() immediately, whereas Flickable doesn't try to steal the grab until the next event, and then it sees the keepMouseGrab flag has been set, so it doesn't do it. If Flickable could filter the same event twice (once as touch, once as synth-mouse), this logic doesn't work, so it's effectively "more grabby" than intended. So it works better to have it filter only the actual touch event, not the synth-mouse that comes after. When the child has pointer handlers, we need to visit them, and therefore we should let Flickable filter a touch event on the way. tst_FlickableInterop::touchDragFlickableBehindButton() depends on this. [ChangeLog][QtQuick][QQuickWindow] In Qt 6, a QQuickItem subclass must explicitly call setAcceptTouchEvents(true) to receive QTouchEvents, and then it must handle them: we no longer fall back to sending a QMouseEvent if the touch event is not accepted. If it has additionally called setFiltersChildMouseEvents(true), then it will filter touch events, not any synthetic mouse events that may be needed for some children. Task-number: QTBUG-87018 Fixes: QTBUG-88169 Change-Id: I8784fe097198c99c754c4ebe205bef8fe490f6f4 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/quick/items/qquickflickable.cpp7
-rw-r--r--src/quick/items/qquickitem.cpp4
-rw-r--r--src/quick/items/qquickwindow.cpp25
3 files changed, 19 insertions, 17 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp
index a6171ade78..31c1c33b10 100644
--- a/src/quick/items/qquickflickable.cpp
+++ b/src/quick/items/qquickflickable.cpp
@@ -2455,13 +2455,16 @@ void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h)
*/
bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
{
- if (!(QQuickWindowPrivate::isMouseEvent(event) ||
- QQuickWindowPrivate::isTouchEvent(event) ||
+ const bool isTouch = QQuickWindowPrivate::isTouchEvent(event);
+ if (!(QQuickWindowPrivate::isMouseEvent(event) || isTouch ||
QQuickWindowPrivate::isTabletEvent(event)))
return false; // don't filter hover events or wheel events, for example
Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself");
qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver;
Q_D(QQuickFlickable);
+ // If a touch event contains a new press point, don't steal right away: watch the movements for a while
+ if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed))
+ d->stealMouse = false;
const auto &firstPoint = event->points().first();
QPointF localPos = mapFromScene(firstPoint.scenePosition());
bool receiverDisabled = receiver && !receiver->isEnabled();
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index e71ae85c54..116d1a370b 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -3172,11 +3172,7 @@ QQuickItemPrivate::QQuickItemPrivate()
, antialiasingValid(false)
, isTabFence(false)
, replayingPressEvent(false)
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- , touchEnabled(true)
-#else
, touchEnabled(false)
-#endif
, hasCursorHandler(false)
, dirtyAttributes(0)
, nextDirtyItem(nullptr)
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 2955b15bca..0a23fa126a 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -2775,6 +2775,7 @@ void QQuickWindowPrivate::deliverUpdatedPoints(QPointerEvent *event)
break;
// If the grabber is an item or the grabbing handler didn't handle it,
// then deliver the event to the item (which may have multiple handlers).
+ hasFiltered.clear();
deliverMatchingPointsToItem(receiver, true, event);
}
@@ -2900,6 +2901,10 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG
&& !exclusiveGrabbers(pointerEvent).contains(item))
return;
+ // If any parent filters the event, we're done.
+ if (sendFilteredPointerEvent(pointerEvent, item))
+ return;
+
// TODO: unite this mouse point delivery with the synthetic mouse event below
if (isMouse) {
auto button = static_cast<QSinglePointEvent *>(pointerEvent)->button();
@@ -2917,7 +2922,7 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG
auto mouseGrabber = pointerEvent->exclusiveGrabber(point);
if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) {
// we don't need item->mouseUngrabEvent() because QQuickWindowPrivate::onGrabChanged does it
- } else if (item->isEnabled() && item->isVisible()) {
+ } else if (item->isEnabled() && item->isVisible() && point.state() != QEventPoint::State::Released) {
pointerEvent->setExclusiveGrabber(point, item);
}
point.setAccepted(true);
@@ -2946,10 +2951,8 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG
qCDebug(DBG_TOUCH) << "actually delivering" << &touchEvent << " to " << item;
QCoreApplication::sendEvent(item, &touchEvent);
eventAccepted = touchEvent.isAccepted();
- }
-
- // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it.
- if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) {
+ } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) {
+ // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it.
if (!eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton)) {
// send mouse event
if (deliverTouchAsMouse(item, &touchEvent))
@@ -3211,6 +3214,7 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu
if (!filteringParent)
return false;
bool filtered = false;
+ const bool hasHandlers = QQuickItemPrivate::get(receiver)->hasPointerHandlers();
if (filteringParent->filtersChildMouseEvents() && !hasFiltered.contains(filteringParent)) {
hasFiltered.append(filteringParent);
if (isMouseEvent(event)) {
@@ -3239,7 +3243,7 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu
}
}
} else if (isTouchEvent(event)) {
- bool acceptsTouchEvents = receiver->acceptTouchEvents();
+ const bool acceptsTouchEvents = receiver->acceptTouchEvents() || hasHandlers;
auto device = event->device();
if (device->type() == QInputDevice::DeviceType::TouchPad &&
device->capabilities().testFlag(QInputDevice::Capability::MouseEmulation)) {
@@ -3257,8 +3261,8 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu
for (auto point : filteringParentTouchEvent.points())
event->setExclusiveGrabber(point, filteringParent);
return true;
- } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents) &&
- (!acceptsTouchEvents || !filteringParent->acceptTouchEvents()))) {
+ } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
+ !filteringParent->acceptTouchEvents()) {
qCDebug(DBG_TOUCH) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent
<< "; accepts touch?" << filteringParent->acceptTouchEvents()
<< "receiver accepts touch?" << acceptsTouchEvents
@@ -3303,12 +3307,11 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu
}
filtered = true;
}
- if (touchMouseUnset) {
+ if (touchMouseUnset)
// Now that we're done sending a synth mouse event, and it wasn't grabbed,
// the touchpoint is no longer acting as a synthetic mouse. Restore previous state.
cancelTouchMouseSynthesis();
- mouseEvent.point(0).setAccepted(false); // because touchToMouseEvent() set it true
- }
+ mouseEvent.point(0).setAccepted(false); // because touchToMouseEvent() set it true
// Only one touchpoint can be treated as a synthetic mouse, so after childMouseEventFilter
// has been called once, we're done with this loop over the touchpoints.
break;