From 1bcddaaf318fc37c71c5191913f3487c49444ec6 Mon Sep 17 00:00:00 2001 From: Martin Jones Date: Mon, 21 Feb 2011 14:57:08 +1000 Subject: Prevent recursion due to nested Flickables with pressDelay The outermost Flickable handles pressDelay for all descendents, rather than having it cascade up and replayed multiple times. Change-Id: Id294862469f3ce56b0940fbbb0e041d4c9f64f28 Task-number: QTBUG-17361 Reviewed-by: Michael Brasser --- .../graphicsitems/qdeclarativeflickable.cpp | 48 ++++++++++++++++++++-- .../graphicsitems/qdeclarativeflickable_p.h | 1 + .../graphicsitems/qdeclarativeflickable_p_p.h | 1 + .../tst_qdeclarativeflickable.cpp | 29 +++++++++++++ .../tst_qdeclarativemousearea.cpp | 20 +++------ 5 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/declarative/graphicsitems/qdeclarativeflickable.cpp b/src/declarative/graphicsitems/qdeclarativeflickable.cpp index 5d5fd0b..a3d9f41 100644 --- a/src/declarative/graphicsitems/qdeclarativeflickable.cpp +++ b/src/declarative/graphicsitems/qdeclarativeflickable.cpp @@ -845,7 +845,8 @@ void QDeclarativeFlickable::mousePressEvent(QGraphicsSceneMouseEvent *event) { Q_D(QDeclarativeFlickable); if (d->interactive) { - d->handleMousePressEvent(event); + if (!d->pressed) + d->handleMousePressEvent(event); event->accept(); } else { QDeclarativeItem::mousePressEvent(event); @@ -910,11 +911,27 @@ void QDeclarativeFlickable::wheelEvent(QGraphicsSceneWheelEvent *event) } } +bool QDeclarativeFlickablePrivate::isOutermostPressDelay() const +{ + Q_Q(const QDeclarativeFlickable); + QDeclarativeItem *item = q->parentItem(); + while (item) { + QDeclarativeFlickable *flick = qobject_cast(item); + if (flick && flick->pressDelay() > 0 && flick->isInteractive()) + return false; + item = item->parentItem(); + } + + return true; +} + void QDeclarativeFlickablePrivate::captureDelayedPress(QGraphicsSceneMouseEvent *event) { Q_Q(QDeclarativeFlickable); if (!q->scene() || pressDelay <= 0) return; + if (!isOutermostPressDelay()) + return; delayedPressTarget = q->scene()->mouseGrabberItem(); delayedPressEvent = new QGraphicsSceneMouseEvent(event->type()); delayedPressEvent->setAccepted(false); @@ -970,9 +987,10 @@ void QDeclarativeFlickable::timerEvent(QTimerEvent *event) if (scene()->mouseGrabberItem() == d->delayedPressTarget) d->delayedPressTarget->ungrabMouse(); //Use the event handler that will take care of finding the proper item to propagate the event - QApplication::sendEvent(scene(), d->delayedPressEvent); + QApplication::postEvent(scene(), d->delayedPressEvent); + } else { + delete d->delayedPressEvent; } - delete d->delayedPressEvent; d->delayedPressEvent = 0; } } @@ -1364,6 +1382,22 @@ bool QDeclarativeFlickable::yflick() const return d->flickableDirection & QDeclarativeFlickable::VerticalFlick; } +bool QDeclarativeFlickable::sceneEvent(QEvent *event) +{ + bool rv = QDeclarativeItem::sceneEvent(event); + if (event->type() == QEvent::UngrabMouse) { + Q_D(QDeclarativeFlickable); + if (d->pressed) { + // if our mouse grab has been removed (probably by another Flickable), + // fix our state + d->pressed = false; + d->stealMouse = false; + setKeepMouseGrab(false); + } + } + return rv; +} + bool QDeclarativeFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) { Q_D(QDeclarativeFlickable); @@ -1391,7 +1425,7 @@ bool QDeclarativeFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) d->handleMouseMoveEvent(&mouseEvent); break; case QEvent::GraphicsSceneMousePress: - if (d->delayedPressEvent) + if (d->pressed) // we are already pressed - this is a delayed replay return false; d->handleMousePressEvent(&mouseEvent); @@ -1410,6 +1444,8 @@ bool QDeclarativeFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) // We send the release scene()->sendEvent(s->mouseGrabberItem(), event); // And the event has been consumed + d->stealMouse = false; + d->pressed = false; return true; } d->handleMouseReleaseEvent(&mouseEvent); @@ -1432,6 +1468,7 @@ bool QDeclarativeFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) d->stealMouse = false; d->pressed = false; } + return false; } @@ -1530,6 +1567,9 @@ bool QDeclarativeFlickable::isFlickingVertically() const If the flickable is dragged/flicked before the delay times out the press event will not be delivered. If the button is released within the timeout, both the press and release will be delivered. + + Note that for nested Flickables with pressDelay set, the pressDelay of + inner Flickables is overridden by the outermost Flickable. */ int QDeclarativeFlickable::pressDelay() const { diff --git a/src/declarative/graphicsitems/qdeclarativeflickable_p.h b/src/declarative/graphicsitems/qdeclarativeflickable_p.h index 4fde1d5..a14cc1c 100644 --- a/src/declarative/graphicsitems/qdeclarativeflickable_p.h +++ b/src/declarative/graphicsitems/qdeclarativeflickable_p.h @@ -204,6 +204,7 @@ protected: virtual void viewportMoved(); virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + bool sceneEvent(QEvent *event); bool sendMouseEvent(QGraphicsSceneMouseEvent *event); bool xflick() const; diff --git a/src/declarative/graphicsitems/qdeclarativeflickable_p_p.h b/src/declarative/graphicsitems/qdeclarativeflickable_p_p.h index 5ad6ff6..1b6081c 100644 --- a/src/declarative/graphicsitems/qdeclarativeflickable_p_p.h +++ b/src/declarative/graphicsitems/qdeclarativeflickable_p_p.h @@ -118,6 +118,7 @@ public: void updateBeginningEnd(); + bool isOutermostPressDelay() const; void captureDelayedPress(QGraphicsSceneMouseEvent *event); void clearDelayedPress(); diff --git a/tests/auto/declarative/qdeclarativeflickable/tst_qdeclarativeflickable.cpp b/tests/auto/declarative/qdeclarativeflickable/tst_qdeclarativeflickable.cpp index f4bec8f..736f8f4 100644 --- a/tests/auto/declarative/qdeclarativeflickable/tst_qdeclarativeflickable.cpp +++ b/tests/auto/declarative/qdeclarativeflickable/tst_qdeclarativeflickable.cpp @@ -69,6 +69,7 @@ private slots: void maximumFlickVelocity(); void flickDeceleration(); void pressDelay(); + void nestedPressDelay(); void flickableDirection(); void qgraphicswidget(); void resizeContent(); @@ -246,6 +247,34 @@ void tst_qdeclarativeflickable::pressDelay() QCOMPARE(spy.count(),1); } +// QTBUG-17361 +void tst_qdeclarativeflickable::nestedPressDelay() +{ + QDeclarativeView *canvas = new QDeclarativeView; + canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/nestedPressDelay.qml")); + canvas->show(); + canvas->setFocus(); + QVERIFY(canvas->rootObject() != 0); + + QDeclarativeFlickable *outer = qobject_cast(canvas->rootObject()); + QVERIFY(outer != 0); + + QDeclarativeFlickable *inner = canvas->rootObject()->findChild("innerFlickable"); + QVERIFY(inner != 0); + + QTest::mousePress(canvas->viewport(), Qt::LeftButton, 0, canvas->mapFromScene(QPoint(150, 150))); + // the MouseArea is not pressed immediately + QVERIFY(outer->property("pressed").toBool() == false); + + // The outer pressDelay will prevail (50ms, vs. 10sec) + QTest::qWait(300); + QVERIFY(outer->property("pressed").toBool() == true); + + QTest::mouseRelease(canvas->viewport(), Qt::LeftButton, 0, canvas->mapFromScene(QPoint(150, 150))); + + delete canvas; +} + void tst_qdeclarativeflickable::flickableDirection() { QDeclarativeComponent component(&engine); diff --git a/tests/auto/declarative/qdeclarativemousearea/tst_qdeclarativemousearea.cpp b/tests/auto/declarative/qdeclarativemousearea/tst_qdeclarativemousearea.cpp index 60d51c6..e1c34fc 100644 --- a/tests/auto/declarative/qdeclarativemousearea/tst_qdeclarativemousearea.cpp +++ b/tests/auto/declarative/qdeclarativemousearea/tst_qdeclarativemousearea.cpp @@ -541,15 +541,11 @@ void tst_QDeclarativeMouseArea::preventStealing() QSignalSpy mousePositionSpy(mouseArea, SIGNAL(positionChanged(QDeclarativeMouseEvent*))); - QGraphicsScene *scene = canvas->scene(); - QGraphicsSceneMouseEvent pressEvent(QEvent::GraphicsSceneMousePress); - pressEvent.setScenePos(QPointF(80, 80)); - pressEvent.setButton(Qt::LeftButton); - pressEvent.setButtons(Qt::LeftButton); - QApplication::sendEvent(scene, &pressEvent); + QTest::mousePress(canvas->viewport(), Qt::LeftButton, 0, canvas->mapFromScene(QPoint(80, 80))); // Without preventStealing, mouse movement over MouseArea would // cause the Flickable to steal mouse and trigger content movement. + QGraphicsScene *scene = canvas->scene(); QGraphicsSceneMouseEvent moveEvent(QEvent::GraphicsSceneMouseMove); moveEvent.setScenePos(QPointF(70, 70)); moveEvent.setButton(Qt::LeftButton); @@ -574,17 +570,12 @@ void tst_QDeclarativeMouseArea::preventStealing() QCOMPARE(flickable->contentX(), 0.); QCOMPARE(flickable->contentY(), 0.); - QGraphicsSceneMouseEvent releaseEvent(QEvent::GraphicsSceneMouseRelease); - releaseEvent.setScenePos(QPointF(50, 50)); - releaseEvent.setButton(Qt::LeftButton); - releaseEvent.setButtons(Qt::LeftButton); - QApplication::sendEvent(scene, &releaseEvent); + QTest::mouseRelease(canvas->viewport(), Qt::LeftButton, 0, canvas->mapFromScene(QPoint(50, 50))); // Now allow stealing and confirm Flickable does its thing. canvas->rootObject()->setProperty("stealing", false); - pressEvent.setScenePos(QPointF(80, 80)); - QApplication::sendEvent(scene, &pressEvent); + QTest::mousePress(canvas->viewport(), Qt::LeftButton, 0, canvas->mapFromScene(QPoint(80, 80))); // Without preventStealing, mouse movement over MouseArea would // cause the Flickable to steal mouse and trigger content movement. @@ -606,8 +597,7 @@ void tst_QDeclarativeMouseArea::preventStealing() QCOMPARE(flickable->contentX(), 10.); QCOMPARE(flickable->contentY(), 10.); - releaseEvent.setScenePos(QPointF(50, 50)); - QApplication::sendEvent(scene, &releaseEvent); + QTest::mouseRelease(canvas->viewport(), Qt::LeftButton, 0, canvas->mapFromScene(QPoint(50, 50))); delete canvas; } -- cgit v0.12