Make touch event delivery in the canvas reentrant
authorLaszlo Agocs <laszlo.p.agocs@nokia.com>
Tue, 24 Apr 2012 07:51:59 +0000 (10:51 +0300)
committerQt by Nokia <qt-info@nokia.com>
Wed, 25 Apr 2012 07:47:25 +0000 (09:47 +0200)
At the moment the unlikely case of spinning the event loop from a
QQuickItem event handler for TouchBegin is not handled properly: It
will end up not delivering the subsequent TouchUpdate and TouchEnd
events to the item, leaving it in a state that should not normally
happen.

Change-Id: I668d065c9cf1a299bfd9268a9125d7a7e32f6786
Reviewed-by: Frederik Gladhorn <frederik.gladhorn@nokia.com>
src/quick/items/qquickcanvas.cpp
tests/auto/quick/qquickcanvas/tst_qquickcanvas.cpp

index f4aa735..0410658 100644 (file)
@@ -1493,14 +1493,19 @@ bool QQuickCanvasPrivate::deliverTouchPoints(QQuickItem *item, QTouchEvent *even
             touchEvent.setTouchPoints(eventPoints);
             touchEvent.setTimestamp(event->timestamp());
 
+            for (int i = 0; i < matchingPoints.count(); ++i)
+                itemForTouchPointId[matchingPoints[i].id()] = item;
+
             touchEvent.accept();
             q->sendEvent(item, &touchEvent);
 
             if (touchEvent.isAccepted()) {
-                for (int i=0; i<matchingPoints.count(); i++) {
-                    itemForTouchPointId[matchingPoints[i].id()] = item;
+                for (int i = 0; i < matchingPoints.count(); ++i)
                     acceptedNewPoints->insert(matchingPoints[i].id());
-                }
+            } else {
+                for (int i = 0; i < matchingPoints.count(); ++i)
+                    if (itemForTouchPointId.value(matchingPoints[i].id()) == item)
+                        itemForTouchPointId.remove(matchingPoints[i].id());
             }
         }
     }
index ca2165d..ed3dceb 100644 (file)
@@ -144,7 +144,8 @@ class TestTouchItem : public QQuickRectangle
     Q_OBJECT
 public:
     TestTouchItem(QQuickItem *parent = 0)
-        : QQuickRectangle(parent), acceptEvents(true), mousePressId(0)
+        : QQuickRectangle(parent), acceptEvents(true), mousePressId(0),
+          spinLoopWhenPressed(false), touchEventCount(0)
     {
         border()->setWidth(1);
         setAcceptedMouseButtons(Qt::LeftButton);
@@ -164,17 +165,29 @@ public:
         mousePressNum = 0;
     }
 
+    void clearTouchEventCounter()
+    {
+        touchEventCount = 0;
+    }
+
     bool acceptEvents;
     TouchEventData lastEvent;
     int mousePressId;
+    bool spinLoopWhenPressed;
+    int touchEventCount;
+
 protected:
     virtual void touchEvent(QTouchEvent *event) {
         if (!acceptEvents) {
             event->ignore();
             return;
         }
+        ++touchEventCount;
         lastEvent = makeTouchData(event->type(), event->window(), event->touchPointStates(), event->touchPoints());
         event->accept();
+        if (spinLoopWhenPressed && event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
+            QCoreApplication::processEvents();
+        }
     }
 
     virtual void mousePressEvent(QMouseEvent *) {
@@ -230,6 +243,7 @@ private slots:
     void touchEvent_propagation();
     void touchEvent_propagation_data();
     void touchEvent_cancel();
+    void touchEvent_reentrant();
 
     void clearCanvas();
 
@@ -540,6 +554,42 @@ void tst_qquickcanvas::touchEvent_cancel()
     delete canvas;
 }
 
+void tst_qquickcanvas::touchEvent_reentrant()
+{
+    TestTouchItem::clearMousePressCounter();
+
+    QQuickCanvas *canvas = new QQuickCanvas;
+    canvas->resize(250, 250);
+    canvas->setPos(100, 100);
+    canvas->show();
+
+    TestTouchItem *item = new TestTouchItem(canvas->rootItem());
+
+    item->spinLoopWhenPressed = true; // will call processEvents() from the touch handler
+
+    item->setPos(QPointF(50, 50));
+    item->setSize(QSizeF(150, 150));
+    QPointF pos(60, 60);
+
+    // None of these should commit from the dtor.
+    QTest::QTouchEventSequence press = QTest::touchEvent(canvas, touchDevice, false).press(0, pos.toPoint(), canvas);
+    pos += QPointF(2, 2);
+    QTest::QTouchEventSequence move = QTest::touchEvent(canvas, touchDevice, false).move(0, pos.toPoint(), canvas);
+    QTest::QTouchEventSequence release = QTest::touchEvent(canvas, touchDevice, false).release(0, pos.toPoint(), canvas);
+
+    // Now commit (i.e. call QWindowSystemInterface::handleTouchEvent), but do not process the events yet.
+    press.commit(false);
+    move.commit(false);
+    release.commit(false);
+
+    QCoreApplication::processEvents();
+
+    QTRY_COMPARE(item->touchEventCount, 3);
+
+    delete item;
+    delete canvas;
+}
+
 void tst_qquickcanvas::clearCanvas()
 {
     QQuickCanvas *canvas = new QQuickCanvas;