-
Bug
-
Resolution: Fixed
-
P2: Important
-
6.2, 6.5, 6.6
-
-
5093e4c24 (dev), 3544aae72 (6.6), c6f88a807 (tqtc/lts-6.5)
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().