Fix touch to mouse synthesis/propagation.
authorRobin Burchell <robin.burchell@jollamobile.com>
Wed, 13 Nov 2013 11:41:19 +0000 (12:41 +0100)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Fri, 13 Dec 2013 10:30:12 +0000 (11:30 +0100)
Having mouse events synthesised from both QtGui and internally in QtQuick  is
not a great way togo about things, especially when QtGui doesn't have the same
degree of knowledge as QtQuick about the items in the scene.

Thus, we now accept all events inside QtQuick to block QtGui synthesis, which
should fix a significant amount of edge-case touch breakage/bad behavior.

Change-Id: I14e1c87761c8f43160049b5e6f9da15b4e5edbb7
Done-with: Martin Jones <martin.jones@jollamobile.com>
Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com>
src/quick/items/qquickflickable.cpp
src/quick/items/qquickitem.cpp
src/quick/items/qquickwindow.cpp
src/quick/items/qquickwindow_p.h
tests/auto/quick/qquickitem/tst_qquickitem.cpp
tests/auto/quick/touchmouse/tst_touchmouse.cpp

index 15627aa..fa18d4a 100644 (file)
@@ -2048,12 +2048,13 @@ bool QQuickFlickable::sendMouseEvent(QQuickItem *item, QMouseEvent *event)
         if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || grabberDisabled) {
             d->clearDelayedPress();
             grabMouse();
+        } else if (d->delayedPressEvent) {
+            grabMouse();
         }
 
-        // Do not accept this event when filtering, as this would force the mouse grab to the child
         const bool filtered = stealThisEvent || d->delayedPressEvent || grabberDisabled;
         if (filtered) {
-            event->setAccepted(false);
+            event->setAccepted(true);
         }
         return filtered;
     } else if (d->lastPosTime != -1) {
index a0329f7..f565fba 100644 (file)
@@ -6629,15 +6629,7 @@ void QQuickItem::grabMouse()
     if (!d->window)
         return;
     QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(d->window);
-    if (windowPriv->mouseGrabberItem == this)
-        return;
-
-    QQuickItem *oldGrabber = windowPriv->mouseGrabberItem;
-    windowPriv->mouseGrabberItem = this;
-    if (oldGrabber) {
-        QEvent ev(QEvent::UngrabMouse);
-        d->window->sendEvent(oldGrabber, &ev);
-    }
+    windowPriv->setMouseGrabber(this);
 }
 
 /*!
index dfc70d7..f355afc 100644 (file)
@@ -599,6 +599,28 @@ bool QQuickWindowPrivate::translateTouchToMouse(QQuickItem *item, QTouchEvent *e
     return false;
 }
 
+void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber)
+{
+    Q_Q(QQuickWindow);
+    if (mouseGrabberItem == grabber)
+        return;
+
+    QQuickItem *oldGrabber = mouseGrabberItem;
+    mouseGrabberItem = grabber;
+
+    if (touchMouseId != -1) {
+        // update the touch item for mouse touch id to the new grabber
+        itemForTouchPointId.remove(touchMouseId);
+        if (grabber)
+            itemForTouchPointId[touchMouseId] = grabber;
+    }
+
+    if (oldGrabber) {
+        QEvent ev(QEvent::UngrabMouse);
+        q->sendEvent(oldGrabber, &ev);
+    }
+}
+
 void QQuickWindowPrivate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
 {
     QMatrix4x4 transformMatrix(transform);
@@ -1221,8 +1243,11 @@ bool QQuickWindow::event(QEvent *e)
     case QEvent::TouchEnd: {
         QTouchEvent *touch = static_cast<QTouchEvent*>(e);
         d->translateTouchEvent(touch);
-        // return in order to avoid the QWindow::event below
-        return d->deliverTouchEvent(touch);
+        d->deliverTouchEvent(touch);
+        // we consume all touch events ourselves to avoid duplicate
+        // mouse delivery by QtGui mouse synthesis
+        e->accept();
+        return true;
     }
         break;
     case QEvent::TouchCancel:
@@ -1776,7 +1801,10 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QTouchEv
     // First check whether the parent wants to be a filter,
     // and if the parent accepts the event we are done.
     if (sendFilteredTouchEvent(item->parentItem(), item, event)) {
-        event->accept();
+        // If the touch was accepted (regardless by whom or in what form),
+        // update acceptedNewPoints
+        foreach (int id, matchingNewPoints)
+            acceptedNewPoints->insert(id);
         return true;
     }
 
index c23745b..418633b 100644 (file)
@@ -127,6 +127,7 @@ public:
     QPointF lastMousePosition;
     bool translateTouchToMouse(QQuickItem *item, QTouchEvent *event);
     void translateTouchEvent(QTouchEvent *touchEvent);
+    void setMouseGrabber(QQuickItem *grabber);
     static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
     static QMouseEvent *cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos = 0);
     bool deliverInitialMousePressEvent(QQuickItem *, QMouseEvent *);
index 2c6dcd7..ad3c4fc 100644 (file)
@@ -1310,7 +1310,10 @@ void tst_qquickitem::touchEventAcceptIgnore()
         bool accepted = window.event(&event);
 
         QVERIFY(item->touchEventReached);
-        QCOMPARE(accepted && event.isAccepted(), itemSupportsTouch);
+
+        // always true because QtQuick always eats touch events so as to not
+        // allow QtGui to synthesise them for us.
+        QCOMPARE(accepted && event.isAccepted(), true);
     }
     {
         QTouchEvent::TouchPoint point;
@@ -1330,7 +1333,10 @@ void tst_qquickitem::touchEventAcceptIgnore()
         bool accepted = window.event(&event);
 
         QCOMPARE(item->touchEventReached, itemSupportsTouch);
-        QCOMPARE(accepted && event.isAccepted(), itemSupportsTouch);
+
+        // always true because QtQuick always eats touch events so as to not
+        // allow QtGui to synthesise them for us.
+        QCOMPARE(accepted && event.isAccepted(), true);
     }
     {
         QTouchEvent::TouchPoint point;
@@ -1350,7 +1356,10 @@ void tst_qquickitem::touchEventAcceptIgnore()
         bool accepted = window.event(&event);
 
         QCOMPARE(item->touchEventReached, itemSupportsTouch);
-        QCOMPARE(accepted && event.isAccepted(), itemSupportsTouch);
+
+        // always true because QtQuick always eats touch events so as to not
+        // allow QtGui to synthesise them for us.
+        QCOMPARE(accepted && event.isAccepted(), true);
     }
 }
 
index 6cf0aa4..5b4ad0f 100644 (file)
@@ -154,6 +154,7 @@ private slots:
     void mouseOverTouch();
 
     void buttonOnFlickable();
+    void buttonOnDelayedPressFlickable();
     void buttonOnTouch();
 
     void pinchOnFlickable();
@@ -162,9 +163,22 @@ private slots:
 
     void tapOnDismissiveTopMouseAreaClicksBottomOne();
 
+protected:
+    bool eventFilter(QObject *, QEvent *event)
+    {
+        if (event->type() == QEvent::MouseButtonPress ||
+                event->type() == QEvent::MouseMove ||
+                event->type() == QEvent::MouseButtonRelease) {
+            QMouseEvent *me = static_cast<QMouseEvent*>(event);
+            filteredEventList.append(Event(me->type(), me->pos(), me->globalPos()));
+        }
+        return false;
+    }
+
 private:
     QQuickView *createView();
     QTouchDevice *device;
+    QList<Event> filteredEventList;
 };
 
 QQuickView *tst_TouchMouse::createView()
@@ -496,7 +510,7 @@ void tst_TouchMouse::buttonOnFlickable()
     QCOMPARE(eventItem1->eventList.size(), 0);
     QPoint p1 = QPoint(20, 130);
     QTest::touchEvent(window, device).press(0, p1, window);
-    QCOMPARE(eventItem1->eventList.size(), 2);
+    QTRY_COMPARE(eventItem1->eventList.size(), 2);
     QCOMPARE(eventItem1->eventList.at(0).type, QEvent::TouchBegin);
     QCOMPARE(eventItem1->eventList.at(1).type, QEvent::MouseButtonPress);
     QTest::touchEvent(window, device).release(0, p1, window);
@@ -559,12 +573,96 @@ void tst_TouchMouse::buttonOnFlickable()
     QCOMPARE(eventItem1->eventList.at(3).type, QEvent::MouseMove);
 
     QCOMPARE(windowPriv->mouseGrabberItem, flickable);
+    QCOMPARE(windowPriv->touchMouseId, 0);
+    QCOMPARE(windowPriv->itemForTouchPointId[0], flickable);
     QVERIFY(flickable->isMovingVertically());
 
     QTest::touchEvent(window, device).release(0, p3, window);
     delete window;
 }
 
+void tst_TouchMouse::buttonOnDelayedPressFlickable()
+{
+    // flickable - height 500 / 1000
+    //   - eventItem1 y: 100, height 100
+    //   - eventItem2 y: 300, height 100
+
+    qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true);
+    filteredEventList.clear();
+
+    QQuickView *window = createView();
+
+    window->setSource(testFileUrl("buttononflickable.qml"));
+    window->show();
+    QVERIFY(QTest::qWaitForWindowExposed(window));
+    window->requestActivate();
+    QVERIFY(QTest::qWaitForWindowActive(window));
+    QVERIFY(window->rootObject() != 0);
+
+    QQuickFlickable *flickable = window->rootObject()->findChild<QQuickFlickable*>("flickable");
+    QVERIFY(flickable);
+
+    window->installEventFilter(this);
+
+    flickable->setPressDelay(60);
+
+    // should a mouse area button be clickable on top of flickable? yes :)
+    EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1");
+    QVERIFY(eventItem1);
+    eventItem1->setAcceptedMouseButtons(Qt::LeftButton);
+    eventItem1->acceptMouse = true;
+
+    // should a touch button be touchable on top of flickable? yes :)
+    EventItem *eventItem2 = window->rootObject()->findChild<EventItem*>("eventItem2");
+    QVERIFY(eventItem2);
+    QCOMPARE(eventItem2->eventList.size(), 0);
+    eventItem2->acceptTouch = true;
+
+    // wait to avoid getting a double click event
+    QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10);
+
+    // check that flickable moves - mouse button
+    QCOMPARE(eventItem1->eventList.size(), 0);
+    QPoint p1 = QPoint(10, 110);
+    QTest::touchEvent(window, device).press(0, p1, window);
+    // Flickable initially steals events
+    QCOMPARE(eventItem1->eventList.size(), 0);
+    // but we'll get the delayed mouse press after a delay
+    QTRY_COMPARE(eventItem1->eventList.size(), 1);
+    QCOMPARE(eventItem1->eventList.at(0).type, QEvent::MouseButtonPress);
+
+    // eventItem1 should have the mouse grab, and have moved the itemForTouchPointId
+    // for the touchMouseId to the new grabber.
+    QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window);
+    QCOMPARE(windowPriv->touchMouseId, 0);
+    QCOMPARE(windowPriv->itemForTouchPointId[0], eventItem1);
+    QCOMPARE(windowPriv->mouseGrabberItem, eventItem1);
+
+    p1 += QPoint(0, -10);
+    QPoint p2 = p1 + QPoint(0, -10);
+    QPoint p3 = p2 + QPoint(0, -10);
+    QTest::qWait(10);
+    QTest::touchEvent(window, device).move(0, p1, window);
+    QTest::qWait(10);
+    QTest::touchEvent(window, device).move(0, p2, window);
+    QTest::qWait(10);
+    QTest::touchEvent(window, device).move(0, p3, window);
+    QVERIFY(flickable->isMovingVertically());
+
+    // flickable should have the mouse grab, and have moved the itemForTouchPointId
+    // for the touchMouseId to the new grabber.
+    QCOMPARE(windowPriv->mouseGrabberItem, flickable);
+    QCOMPARE(windowPriv->touchMouseId, 0);
+    QCOMPARE(windowPriv->itemForTouchPointId[0], flickable);
+
+    QTest::touchEvent(window, device).release(0, p3, window);
+
+    // We should not have received any synthesised mouse events from Qt gui.
+    QCOMPARE(filteredEventList.count(), 0);
+
+    delete window;
+}
+
 void tst_TouchMouse::buttonOnTouch()
 {
     // 400x800