Make touch event delivery in the canvas reentrant

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>
This commit is contained in:
Laszlo Agocs 2012-04-24 10:51:59 +03:00 committed by Qt by Nokia
parent 9b1299e0db
commit b8bb9d7561
2 changed files with 59 additions and 4 deletions

View 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());
}
}
}

View 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;