Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-117160

touchUngrabEvent is not implemented for qquickflickable

    XMLWordPrintable

Details

    • All
    • 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().

       

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            richard Richard Moe Gustavsen
            seokhako Seokha Ko
            Votes:
            0 Vote for this issue
            Watchers:
            11 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes