Details
-
Bug
-
Resolution: Fixed
-
P2: Important
-
6.2, 6.5, 6.6
-
-
5093e4c24 (dev), 3544aae72 (6.6), c6f88a807 (tqtc/lts-6.5)
Description
Short description:
In situations where QWindowSystemInterface::handleTouchCancelEvent is triggered for various reasons (such as losing focus), the interaction of qquickflickable is not canceled.
Simple test case:
void tst_qquickflickable::touchCancel() { QQuickView *window = new QQuickView; QScopedPointer<QQuickView> windowPtr(window); windowPtr->setSource(testFileUrl("flickable03.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); QQuickVisualTestUtils::centerOnScreen(window); QQuickVisualTestUtils::moveMouseAway(window); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QVERIFY(window->rootObject() != nullptr); QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject()); QVERIFY(flickable != nullptr); QSignalSpy movementStartedSpy(flickable, SIGNAL(movementStarted())); QSignalSpy movementEndedSpy(flickable, SIGNAL(movementEnded())); int dragDistance = qApp->styleHints()->startDragDistance(); int newY = dragDistance; QTest::touchEvent(window, touchDevice).press(0, {10, newY}).commit(); QQuickTouchUtils::flush(window); newY += dragDistance; QTest::touchEvent(window, touchDevice).move(0, {10, newY}).commit(); QQuickTouchUtils::flush(window); newY += dragDistance; QTest::touchEvent(window, touchDevice).move(0, {10, newY}).commit(); QQuickTouchUtils::flush(window); newY += dragDistance; QTest::touchEvent(window, touchDevice).move(0, {10, newY}).commit(); QQuickTouchUtils::flush(window); QWindowSystemInterface::handleTouchCancelEvent(nullptr, touchDevice); QCoreApplication::processEvents(); QTRY_COMPARE(movementStartedSpy.size(), 1); QTRY_COMPARE(movementEndedSpy.size(), 1); }
In this test case, movementEnded signal is not occured.
Case 1. quicktemplates2_multitouch disabled
We should not call setAcceptTouchEvents(true) for flickable.
So below fix could be enough.
void QQuickFlickablePrivate::init() { ................ #if QT_CONFIG(quicktemplates2_multitouch) q->setAcceptTouchEvents(true); #endif ................ }
it's just a example because we cannot use quicktemplates2_multitouch define here.
: quicktemplates2_multitouch is a config for Quick Template 2, so it can't be applied to QQuickFlickable. It was just an example for illustration. In Quick Templates 2, if multi-touch is disabled, is there a need for setAcceptTouchEvents(true) in Flickable? It seems somewhat inconsistent. If setAcceptTouchEvents(false) is used, then "grabberItem->mouseUngrabEvent()" will be called in QQuickDeliveryAgentPrivate::onGrabChanged(), triggering qquickflickable's cancelInteraction().
Case 1. quicktemplates2_multitouch enabled
First, we have to override touchUngrabEvent() like
void QQuickFlickable::touchUngrabEvent()
{
Q_D(QQuickFlickable);
if (!d->replayingPressEvent) {
d->cancelInteraction();
}
}
but it's not enough because
when we call QWindowSystemInterface::handleTouchCancelEvent(nullptr, touchDevice);
void QPointingDevicePrivate::sendTouchCancelEvent(QTouchEvent *cancelEvent) { ... for (auto &epd : activePoints.values()) { if (epd.exclusiveGrabber) QCoreApplication::sendEvent(epd.exclusiveGrabber, cancelEvent); // The next touch event can only be a TouchBegin, so clean up. cancelEvent->setExclusiveGrabber(epd.eventPoint, nullptr); cancelEvent->clearPassiveGrabbers(epd.eventPoint); } }
cancelEvent->setExclusiveGrabber() will be called with "nullptr" exclusiveGrabber
then,
void QPointingDevicePrivate::setExclusiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *exclusiveGrabber) { .... if (oldGrabber) emit q->grabChanged(oldGrabber, exclusiveGrabber ? QPointingDevice::CancelGrabExclusive : QPointingDevice::UngrabExclusive, event, persistentPoint->eventPoint); .... }
grabChanged signal will be emitted with QPointingDevice::UngrabExclusive (not CancelGrabExclusive)
This is the part that becomes problematic.
Because
void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, const QPointerEvent *event, const QEventPoint &point) ......... switch (transition) { case QPointingDevice::CancelGrabExclusive: case QPointingDevice::UngrabExclusive: if (isDeliveringTouchAsMouse() || point.device()->type() == QInputDevice::DeviceType::Mouse || point.device()->type() == QInputDevice::DeviceType::TouchPad) { 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) { bool allReleasedOrCancelled = true; if (transition == QPointingDevice::UngrabExclusive && event) { for (const auto &pt : event->points()) { if (pt.state() != QEventPoint::State::Released) { allReleasedOrCancelled = false; break; } } } if (allReleasedOrCancelled) grabberItem->touchUngrabEvent(); } break; ......
When "point.device()>type() == QInputDevice::DeviceType::TouchScreen" and "transition == QPointingDevice::UngrabExclusive", "grabberItem>touchUngrabEvent()" will not be called if any of the event->points() is not in the State::Released.
So, further review is needed for QPointingDevicePrivate::sendTouchCancelEvent(), QPointingDevicePrivate::setExclusiveGrabber(), and QQuickDeliveryAgentPrivate::onGrabChanged().