QQuickCanvas renames
[profile/ivi/qtdeclarative.git] / tests / auto / quick / qquickflickable / tst_qquickflickable.cpp
index 4b157a4..cb8dba3 100644 (file)
 ****************************************************************************/
 #include <qtest.h>
 #include <QtTest/QSignalSpy>
+#include <QtGui/QStyleHints>
 #include <QtQml/qqmlengine.h>
 #include <QtQml/qqmlcomponent.h>
 #include <QtQuick/qquickview.h>
 #include <private/qquickflickable_p.h>
 #include <private/qquickflickable_p_p.h>
+#include <private/qquicktransition_p.h>
 #include <private/qqmlvaluetype_p.h>
 #include <math.h>
 #include "../../shared/util.h"
@@ -65,6 +67,7 @@ private slots:
     void verticalViewportSize();
     void properties();
     void boundsBehavior();
+    void rebound();
     void maximumFlickVelocity();
     void flickDeceleration();
     void pressDelay();
@@ -72,11 +75,19 @@ private slots:
     void flickableDirection();
     void resizeContent();
     void returnToBounds();
+    void returnToBounds_data();
     void wheel();
+    void movingAndFlicking();
+    void movingAndFlicking_data();
     void movingAndDragging();
+    void movingAndDragging_data();
+    void flickOnRelease();
+    void pressWhileFlicking();
     void disabled();
     void flickVelocity();
     void margins();
+    void cancelOnMouseGrab();
+    void clickAndDragWhenTransformed();
 
 private:
     QQmlEngine engine;
@@ -189,6 +200,108 @@ void tst_qquickflickable::boundsBehavior()
     QCOMPARE(spy.count(),3);
 }
 
+void tst_qquickflickable::rebound()
+{
+#ifdef Q_OS_MAC
+    QSKIP("Producing flicks on Mac CI impossible due to timing problems");
+#endif
+
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("rebound.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QVERIFY(window->rootObject() != 0);
+
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
+    QVERIFY(flickable != 0);
+
+    QQuickTransition *rebound = window->rootObject()->findChild<QQuickTransition*>("rebound");
+    QVERIFY(rebound);
+    QSignalSpy reboundSpy(rebound, SIGNAL(runningChanged()));
+
+    QSignalSpy movementStartedSpy(flickable, SIGNAL(movementStarted()));
+    QSignalSpy movementEndedSpy(flickable, SIGNAL(movementEnded()));
+    QSignalSpy vMoveSpy(flickable, SIGNAL(movingVerticallyChanged()));
+    QSignalSpy hMoveSpy(flickable, SIGNAL(movingHorizontallyChanged()));
+
+    // flick and test the transition is run
+    flick(window, QPoint(20,20), QPoint(120,120), 200);
+
+    QTRY_COMPARE(window->rootObject()->property("transitionsStarted").toInt(), 2);
+    QCOMPARE(hMoveSpy.count(), 1);
+    QCOMPARE(vMoveSpy.count(), 1);
+    QCOMPARE(movementStartedSpy.count(), 1);
+    QCOMPARE(movementEndedSpy.count(), 0);
+    QVERIFY(rebound->running());
+
+    QTRY_VERIFY(!flickable->isMoving());
+    QCOMPARE(flickable->contentX(), 0.0);
+    QCOMPARE(flickable->contentY(), 0.0);
+
+    QCOMPARE(hMoveSpy.count(), 2);
+    QCOMPARE(vMoveSpy.count(), 2);
+    QCOMPARE(movementStartedSpy.count(), 1);
+    QCOMPARE(movementEndedSpy.count(), 1);
+    QCOMPARE(window->rootObject()->property("transitionsStarted").toInt(), 2);
+    QVERIFY(!rebound->running());
+    QCOMPARE(reboundSpy.count(), 2);
+
+    hMoveSpy.clear();
+    vMoveSpy.clear();
+    movementStartedSpy.clear();
+    movementEndedSpy.clear();
+    window->rootObject()->setProperty("transitionsStarted", 0);
+    window->rootObject()->setProperty("transitionsFinished", 0);
+
+    // flick and trigger the transition multiple times
+    // (moving signals are emitted as soon as the first transition starts)
+    flick(window, QPoint(20,20), QPoint(120,120), 200);     // both x and y will bounce back
+    flick(window, QPoint(20,120), QPoint(120,20), 200);     // only x will bounce back
+
+    QVERIFY(flickable->isMoving());
+    QVERIFY(window->rootObject()->property("transitionsStarted").toInt() >= 1);
+    QCOMPARE(hMoveSpy.count(), 1);
+    QCOMPARE(vMoveSpy.count(), 1);
+    QCOMPARE(movementStartedSpy.count(), 1);
+
+    QTRY_VERIFY(!flickable->isMoving());
+    QCOMPARE(flickable->contentX(), 0.0);
+
+    // moving started/stopped signals should only have been emitted once,
+    // and when they are, all transitions should have finished
+    QCOMPARE(hMoveSpy.count(), 2);
+    QCOMPARE(vMoveSpy.count(), 2);
+    QCOMPARE(movementStartedSpy.count(), 1);
+    QCOMPARE(movementEndedSpy.count(), 1);
+
+    hMoveSpy.clear();
+    vMoveSpy.clear();
+    movementStartedSpy.clear();
+    movementEndedSpy.clear();
+    window->rootObject()->setProperty("transitionsStarted", 0);
+    window->rootObject()->setProperty("transitionsFinished", 0);
+
+    // disable and the default transition should run
+    // (i.e. moving but transition->running = false)
+    window->rootObject()->setProperty("transitionEnabled", false);
+
+    flick(window, QPoint(20,20), QPoint(120,120), 200);
+    QCOMPARE(window->rootObject()->property("transitionsStarted").toInt(), 0);
+    QCOMPARE(hMoveSpy.count(), 1);
+    QCOMPARE(vMoveSpy.count(), 1);
+    QCOMPARE(movementStartedSpy.count(), 1);
+    QCOMPARE(movementEndedSpy.count(), 0);
+
+    QTRY_VERIFY(!flickable->isMoving());
+    QCOMPARE(hMoveSpy.count(), 2);
+    QCOMPARE(vMoveSpy.count(), 2);
+    QCOMPARE(movementStartedSpy.count(), 1);
+    QCOMPARE(movementEndedSpy.count(), 1);
+    QCOMPARE(window->rootObject()->property("transitionsStarted").toInt(), 0);
+
+    delete window;
+}
+
 void tst_qquickflickable::maximumFlickVelocity()
 {
     QQmlComponent component(&engine);
@@ -243,19 +356,19 @@ void tst_qquickflickable::pressDelay()
 // QTBUG-17361
 void tst_qquickflickable::nestedPressDelay()
 {
-    QQuickView *canvas = new QQuickView;
-    canvas->setSource(testFileUrl("nestedPressDelay.qml"));
-    canvas->show();
-    canvas->requestActivateWindow();
-    QVERIFY(canvas->rootObject() != 0);
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("nestedPressDelay.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QVERIFY(window->rootObject() != 0);
 
-    QQuickFlickable *outer = qobject_cast<QQuickFlickable*>(canvas->rootObject());
+    QQuickFlickable *outer = qobject_cast<QQuickFlickable*>(window->rootObject());
     QVERIFY(outer != 0);
 
-    QQuickFlickable *inner = canvas->rootObject()->findChild<QQuickFlickable*>("innerFlickable");
+    QQuickFlickable *inner = window->rootObject()->findChild<QQuickFlickable*>("innerFlickable");
     QVERIFY(inner != 0);
 
-    QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(150, 150));
+    QTest::mousePress(window, Qt::LeftButton, 0, QPoint(150, 150));
     // the MouseArea is not pressed immediately
     QVERIFY(outer->property("pressed").toBool() == false);
 
@@ -263,9 +376,9 @@ void tst_qquickflickable::nestedPressDelay()
     // QTRY_VERIFY() has 5sec timeout, so will timeout well within 10sec.
     QTRY_VERIFY(outer->property("pressed").toBool() == true);
 
-    QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(150, 150));
+    QTest::mouseRelease(window, Qt::LeftButton, 0, QPoint(150, 150));
 
-    delete canvas;
+    delete window;
 }
 
 void tst_qquickflickable::flickableDirection()
@@ -319,13 +432,19 @@ void tst_qquickflickable::resizeContent()
     delete root;
 }
 
-// QtQuick 1.1
 void tst_qquickflickable::returnToBounds()
 {
-    QQmlEngine engine;
-    QQmlComponent c(&engine, testFileUrl("resize.qml"));
-    QQuickItem *root = qobject_cast<QQuickItem*>(c.create());
-    QQuickFlickable *obj = findItem<QQuickFlickable>(root, "flick");
+    QFETCH(bool, setRebound);
+
+    QQuickView *window = new QQuickView;
+    window->rootContext()->setContextProperty("setRebound", setRebound);
+    window->setSource(testFileUrl("resize.qml"));
+    QVERIFY(window->rootObject() != 0);
+    QQuickFlickable *obj = findItem<QQuickFlickable>(window->rootObject(), "flick");
+
+    QQuickTransition *rebound = window->rootObject()->findChild<QQuickTransition*>("rebound");
+    QVERIFY(rebound);
+    QSignalSpy reboundSpy(rebound, SIGNAL(runningChanged()));
 
     QVERIFY(obj != 0);
     QCOMPARE(obj->contentX(), 0.);
@@ -338,29 +457,44 @@ void tst_qquickflickable::returnToBounds()
     QTRY_COMPARE(obj->contentX(), 100.);
     QTRY_COMPARE(obj->contentY(), 400.);
 
-    QMetaObject::invokeMethod(root, "returnToBounds");
+    QMetaObject::invokeMethod(window->rootObject(), "returnToBounds");
+
+    if (setRebound)
+        QTRY_VERIFY(rebound->running());
 
     QTRY_COMPARE(obj->contentX(), 0.);
     QTRY_COMPARE(obj->contentY(), 0.);
 
-    delete root;
+    QVERIFY(!rebound->running());
+    QCOMPARE(reboundSpy.count(), setRebound ? 2 : 0);
+
+    delete window;
+}
+
+void tst_qquickflickable::returnToBounds_data()
+{
+    QTest::addColumn<bool>("setRebound");
+
+    QTest::newRow("with bounds transition") << true;
+    QTest::newRow("with bounds transition") << false;
 }
 
 void tst_qquickflickable::wheel()
 {
-    QQuickView *canvas = new QQuickView;
-    canvas->setSource(testFileUrl("wheel.qml"));
-    canvas->show();
-    canvas->requestActivateWindow();
-    QVERIFY(canvas->rootObject() != 0);
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("wheel.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QVERIFY(window->rootObject() != 0);
 
-    QQuickFlickable *flick = canvas->rootObject()->findChild<QQuickFlickable*>("flick");
+    QQuickFlickable *flick = window->rootObject()->findChild<QQuickFlickable*>("flick");
     QVERIFY(flick != 0);
 
     {
-        QWheelEvent event(QPoint(200, 200), -120, Qt::NoButton, Qt::NoModifier, Qt::Vertical);
+        QPoint pos(200, 200);
+        QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(), QPoint(0,-120), -120, Qt::Vertical, Qt::NoButton, Qt::NoModifier);
         event.setAccepted(false);
-        QGuiApplication::sendEvent(canvas, &event);
+        QGuiApplication::sendEvent(window, &event);
     }
 
     QTRY_VERIFY(flick->contentY() > 0);
@@ -370,27 +504,223 @@ void tst_qquickflickable::wheel()
     QVERIFY(flick->contentY() == 0);
 
     {
-        QWheelEvent event(QPoint(200, 200), -120, Qt::NoButton, Qt::NoModifier, Qt::Horizontal);
+        QPoint pos(200, 200);
+        QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(), QPoint(-120,0), -120, Qt::Horizontal, Qt::NoButton, Qt::NoModifier);
+
         event.setAccepted(false);
-        QGuiApplication::sendEvent(canvas, &event);
+        QGuiApplication::sendEvent(window, &event);
     }
 
     QTRY_VERIFY(flick->contentX() > 0);
     QVERIFY(flick->contentY() == 0);
 
-    delete canvas;
+    delete window;
+}
+
+void tst_qquickflickable::movingAndFlicking_data()
+{
+    QTest::addColumn<bool>("verticalEnabled");
+    QTest::addColumn<bool>("horizontalEnabled");
+    QTest::addColumn<QPoint>("flickToWithoutSnapBack");
+    QTest::addColumn<QPoint>("flickToWithSnapBack");
+
+    QTest::newRow("vertical")
+            << true << false
+            << QPoint(50, 100)
+            << QPoint(50, 300);
+
+    QTest::newRow("horizontal")
+            << false << true
+            << QPoint(-50, 200)
+            << QPoint(150, 200);
+
+    QTest::newRow("both")
+            << true << true
+            << QPoint(-50, 100)
+            << QPoint(150, 300);
+}
+
+void tst_qquickflickable::movingAndFlicking()
+{
+#ifdef Q_OS_MAC
+    QSKIP("Producing flicks on Mac CI impossible due to timing problems");
+#endif
+
+    QFETCH(bool, verticalEnabled);
+    QFETCH(bool, horizontalEnabled);
+    QFETCH(QPoint, flickToWithoutSnapBack);
+    QFETCH(QPoint, flickToWithSnapBack);
+
+    const QPoint flickFrom(50, 200);   // centre
+
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("flickable03.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QTest::qWaitForWindowActive(window);
+    QVERIFY(window->rootObject() != 0);
+
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
+    QVERIFY(flickable != 0);
+
+    QSignalSpy vMoveSpy(flickable, SIGNAL(movingVerticallyChanged()));
+    QSignalSpy hMoveSpy(flickable, SIGNAL(movingHorizontallyChanged()));
+    QSignalSpy moveSpy(flickable, SIGNAL(movingChanged()));
+    QSignalSpy vFlickSpy(flickable, SIGNAL(flickingVerticallyChanged()));
+    QSignalSpy hFlickSpy(flickable, SIGNAL(flickingHorizontallyChanged()));
+    QSignalSpy flickSpy(flickable, SIGNAL(flickingChanged()));
+
+    QSignalSpy moveStartSpy(flickable, SIGNAL(movementStarted()));
+    QSignalSpy moveEndSpy(flickable, SIGNAL(movementEnded()));
+    QSignalSpy flickStartSpy(flickable, SIGNAL(flickStarted()));
+    QSignalSpy flickEndSpy(flickable, SIGNAL(flickEnded()));
+
+    // do a flick that keeps the view within the bounds
+    flick(window, flickFrom, flickToWithoutSnapBack, 200);
+
+    QVERIFY(flickable->isMoving());
+    QCOMPARE(flickable->isMovingHorizontally(), horizontalEnabled);
+    QCOMPARE(flickable->isMovingVertically(), verticalEnabled);
+    QVERIFY(flickable->isFlicking());
+    QCOMPARE(flickable->isFlickingHorizontally(), horizontalEnabled);
+    QCOMPARE(flickable->isFlickingVertically(), verticalEnabled);
+
+    QCOMPARE(moveSpy.count(), 1);
+    QCOMPARE(vMoveSpy.count(), verticalEnabled ? 1 : 0);
+    QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 1 : 0);
+    QCOMPARE(flickSpy.count(), 1);
+    QCOMPARE(vFlickSpy.count(), verticalEnabled ? 1 : 0);
+    QCOMPARE(hFlickSpy.count(), horizontalEnabled ? 1 : 0);
+
+    QCOMPARE(moveStartSpy.count(), 1);
+    QCOMPARE(flickStartSpy.count(), 1);
+
+    // wait for any motion to end
+    QTRY_VERIFY(!flickable->isMoving());
+
+    QVERIFY(!flickable->isMovingHorizontally());
+    QVERIFY(!flickable->isMovingVertically());
+    QVERIFY(!flickable->isFlicking());
+    QVERIFY(!flickable->isFlickingHorizontally());
+    QVERIFY(!flickable->isFlickingVertically());
+
+    QCOMPARE(moveSpy.count(), 2);
+    QCOMPARE(vMoveSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 2 : 0);
+    QCOMPARE(flickSpy.count(), 2);
+    QCOMPARE(vFlickSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hFlickSpy.count(), horizontalEnabled ? 2 : 0);
+
+    QCOMPARE(moveStartSpy.count(), 1);
+    QCOMPARE(moveEndSpy.count(), 1);
+    QCOMPARE(flickStartSpy.count(), 1);
+    QCOMPARE(flickEndSpy.count(), 1);
+
+    // Stop on a full pixel after user interaction
+    if (verticalEnabled)
+        QCOMPARE(flickable->contentY(), (qreal)qRound(flickable->contentY()));
+    if (horizontalEnabled)
+        QCOMPARE(flickable->contentX(), (qreal)qRound(flickable->contentX()));
+
+    // clear for next flick
+    vMoveSpy.clear(); hMoveSpy.clear(); moveSpy.clear();
+    vFlickSpy.clear(); hFlickSpy.clear(); flickSpy.clear();
+    moveStartSpy.clear(); moveEndSpy.clear();
+    flickStartSpy.clear(); flickEndSpy.clear();
+
+    // do a flick that flicks the view out of bounds
+    flickable->setContentX(0);
+    flickable->setContentY(0);
+    QTRY_VERIFY(!flickable->isMoving());
+    flick(window, flickFrom, flickToWithSnapBack, 200);
+
+    QVERIFY(flickable->isMoving());
+    QCOMPARE(flickable->isMovingHorizontally(), horizontalEnabled);
+    QCOMPARE(flickable->isMovingVertically(), verticalEnabled);
+    QVERIFY(flickable->isFlicking());
+    QCOMPARE(flickable->isFlickingHorizontally(), horizontalEnabled);
+    QCOMPARE(flickable->isFlickingVertically(), verticalEnabled);
+
+    QCOMPARE(moveSpy.count(), 1);
+    QCOMPARE(vMoveSpy.count(), verticalEnabled ? 1 : 0);
+    QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 1 : 0);
+    QCOMPARE(flickSpy.count(), 1);
+    QCOMPARE(vFlickSpy.count(), verticalEnabled ? 1 : 0);
+    QCOMPARE(hFlickSpy.count(), horizontalEnabled ? 1 : 0);
+
+    QCOMPARE(moveStartSpy.count(), 1);
+    QCOMPARE(moveEndSpy.count(), 0);
+    QCOMPARE(flickStartSpy.count(), 1);
+    QCOMPARE(flickEndSpy.count(), 0);
+
+    // wait for any motion to end
+    QTRY_VERIFY(!flickable->isMoving());
+
+    QVERIFY(!flickable->isMovingHorizontally());
+    QVERIFY(!flickable->isMovingVertically());
+    QVERIFY(!flickable->isFlicking());
+    QVERIFY(!flickable->isFlickingHorizontally());
+    QVERIFY(!flickable->isFlickingVertically());
+
+    QCOMPARE(moveSpy.count(), 2);
+    QCOMPARE(vMoveSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 2 : 0);
+    QCOMPARE(flickSpy.count(), 2);
+    QCOMPARE(vFlickSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hFlickSpy.count(), horizontalEnabled ? 2 : 0);
+
+    QCOMPARE(moveStartSpy.count(), 1);
+    QCOMPARE(moveEndSpy.count(), 1);
+    QCOMPARE(flickStartSpy.count(), 1);
+    QCOMPARE(flickEndSpy.count(), 1);
+
+    QCOMPARE(flickable->contentX(), 0.0);
+    QCOMPARE(flickable->contentY(), 0.0);
+
+    delete window;
+}
+
+
+void tst_qquickflickable::movingAndDragging_data()
+{
+    QTest::addColumn<bool>("verticalEnabled");
+    QTest::addColumn<bool>("horizontalEnabled");
+    QTest::addColumn<QPoint>("moveByWithoutSnapBack");
+    QTest::addColumn<QPoint>("moveByWithSnapBack");
+
+    QTest::newRow("vertical")
+            << true << false
+            << QPoint(0, -10)
+            << QPoint(0, 20);
+
+    QTest::newRow("horizontal")
+            << false << true
+            << QPoint(-10, 0)
+            << QPoint(20, 0);
+
+    QTest::newRow("both")
+            << true << true
+            << QPoint(-10, -10)
+            << QPoint(20, 20);
 }
 
 void tst_qquickflickable::movingAndDragging()
 {
-    QQuickView *canvas = new QQuickView;
-    canvas->setSource(testFileUrl("flickable03.qml"));
-    canvas->show();
-    canvas->requestActivateWindow();
-    QTest::qWaitForWindowShown(canvas);
-    QVERIFY(canvas->rootObject() != 0);
-
-    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(canvas->rootObject());
+    QFETCH(bool, verticalEnabled);
+    QFETCH(bool, horizontalEnabled);
+    QFETCH(QPoint, moveByWithoutSnapBack);
+    QFETCH(QPoint, moveByWithSnapBack);
+
+    const QPoint moveFrom(50, 200);   // centre
+
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("flickable03.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QTest::qWaitForWindowShown(window);
+    QVERIFY(window->rootObject() != 0);
+
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
     QVERIFY(flickable != 0);
 
     QSignalSpy vDragSpy(flickable, SIGNAL(draggingVerticallyChanged()));
@@ -399,106 +729,218 @@ void tst_qquickflickable::movingAndDragging()
     QSignalSpy vMoveSpy(flickable, SIGNAL(movingVerticallyChanged()));
     QSignalSpy hMoveSpy(flickable, SIGNAL(movingHorizontallyChanged()));
     QSignalSpy moveSpy(flickable, SIGNAL(movingChanged()));
+
     QSignalSpy dragStartSpy(flickable, SIGNAL(dragStarted()));
     QSignalSpy dragEndSpy(flickable, SIGNAL(dragEnded()));
+    QSignalSpy moveStartSpy(flickable, SIGNAL(movementStarted()));
+    QSignalSpy moveEndSpy(flickable, SIGNAL(movementEnded()));
 
-    //Vertical
-    QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(50, 90));
-
-    QTest::mouseMove(canvas, QPoint(50, 80));
-    QTest::mouseMove(canvas, QPoint(50, 70));
-    QTest::mouseMove(canvas, QPoint(50, 60));
+    // start the drag
+    QTest::mousePress(window, Qt::LeftButton, 0, moveFrom);
+    QTest::mouseMove(window, moveFrom + moveByWithoutSnapBack);
+    QTest::mouseMove(window, moveFrom + moveByWithoutSnapBack*2);
+    QTest::mouseMove(window, moveFrom + moveByWithoutSnapBack*3);
 
-    QMouseEvent moveEvent(QEvent::MouseMove, QPoint(50, 80), Qt::LeftButton, Qt::LeftButton, 0);
-
-    QVERIFY(!flickable->isDraggingHorizontally());
-    QVERIFY(flickable->isDraggingVertically());
+    QVERIFY(flickable->isMoving());
+    QCOMPARE(flickable->isMovingHorizontally(), horizontalEnabled);
+    QCOMPARE(flickable->isMovingVertically(), verticalEnabled);
     QVERIFY(flickable->isDragging());
-    QCOMPARE(vDragSpy.count(), 1);
-    QCOMPARE(dragSpy.count(), 1);
-    QCOMPARE(hDragSpy.count(), 0);
-    QCOMPARE(dragStartSpy.count(), 1);
-    QCOMPARE(dragEndSpy.count(), 0);
+    QCOMPARE(flickable->isDraggingHorizontally(), horizontalEnabled);
+    QCOMPARE(flickable->isDraggingVertically(), verticalEnabled);
 
-    QVERIFY(!flickable->isMovingHorizontally());
-    QVERIFY(flickable->isMovingVertically());
-    QVERIFY(flickable->isMoving());
-    QCOMPARE(vMoveSpy.count(), 1);
     QCOMPARE(moveSpy.count(), 1);
-    QCOMPARE(hMoveSpy.count(), 0);
+    QCOMPARE(vMoveSpy.count(), verticalEnabled ? 1 : 0);
+    QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 1 : 0);
+    QCOMPARE(dragSpy.count(), 1);
+    QCOMPARE(vDragSpy.count(), verticalEnabled ? 1 : 0);
+    QCOMPARE(hDragSpy.count(), horizontalEnabled ? 1 : 0);
+
+    QCOMPARE(moveStartSpy.count(), 1);
+    QCOMPARE(dragStartSpy.count(), 1);
 
-    QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(50, 60));
+    QTest::mouseRelease(window, Qt::LeftButton, 0, moveFrom + moveByWithoutSnapBack*3);
 
-    QTRY_VERIFY(!flickable->isDraggingVertically());
     QVERIFY(!flickable->isDragging());
-    QCOMPARE(vDragSpy.count(), 2);
+    QVERIFY(!flickable->isDraggingHorizontally());
+    QVERIFY(!flickable->isDraggingVertically());
     QCOMPARE(dragSpy.count(), 2);
-    QCOMPARE(hDragSpy.count(), 0);
+    QCOMPARE(vDragSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hDragSpy.count(), horizontalEnabled ? 2 : 0);
     QCOMPARE(dragStartSpy.count(), 1);
     QCOMPARE(dragEndSpy.count(), 1);
+    // Don't test whether moving finished because a flick could occur
 
     // wait for any motion to end
     QTRY_VERIFY(flickable->isMoving() == false);
 
-    //Horizontal
-    vDragSpy.clear();
-    hDragSpy.clear();
-    dragSpy.clear();
-    vMoveSpy.clear();
-    hMoveSpy.clear();
-    moveSpy.clear();
-    dragStartSpy.clear();
-    dragEndSpy.clear();
-
-    QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(90, 50));
+    QVERIFY(!flickable->isMovingHorizontally());
+    QVERIFY(!flickable->isMovingVertically());
+    QVERIFY(!flickable->isDragging());
+    QVERIFY(!flickable->isDraggingHorizontally());
+    QVERIFY(!flickable->isDraggingVertically());
 
-    QTest::mouseMove(canvas, QPoint(80, 50));
-    QTest::mouseMove(canvas, QPoint(70, 50));
-    QTest::mouseMove(canvas, QPoint(60, 50));
+    QCOMPARE(dragSpy.count(), 2);
+    QCOMPARE(vDragSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hDragSpy.count(), horizontalEnabled ? 2 : 0);
+    QCOMPARE(moveSpy.count(), 2);
+    QCOMPARE(vMoveSpy.count(), verticalEnabled ? 2 : 0);
+    QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 2 : 0);
 
-    QVERIFY(!flickable->isDraggingVertically());
-    QVERIFY(flickable->isDraggingHorizontally());
-    QVERIFY(flickable->isDragging());
-    QCOMPARE(vDragSpy.count(), 0);
-    QCOMPARE(dragSpy.count(), 1);
-    QCOMPARE(hDragSpy.count(), 1);
     QCOMPARE(dragStartSpy.count(), 1);
-    QCOMPARE(dragEndSpy.count(), 0);
+    QCOMPARE(dragEndSpy.count(), 1);
+    QCOMPARE(moveStartSpy.count(), 1);
+    QCOMPARE(moveEndSpy.count(), 1);
+
+    // Stop on a full pixel after user interaction
+    if (verticalEnabled)
+        QCOMPARE(flickable->contentY(), (qreal)qRound(flickable->contentY()));
+    if (horizontalEnabled)
+        QCOMPARE(flickable->contentX(), (qreal)qRound(flickable->contentX()));
+
+    // clear for next drag
+     vMoveSpy.clear(); hMoveSpy.clear(); moveSpy.clear();
+     vDragSpy.clear(); hDragSpy.clear(); dragSpy.clear();
+     moveStartSpy.clear(); moveEndSpy.clear();
+     dragStartSpy.clear(); dragEndSpy.clear();
+
+     // do a drag that drags the view out of bounds
+     flickable->setContentX(0);
+     flickable->setContentY(0);
+     QTRY_VERIFY(!flickable->isMoving());
+     QTest::mousePress(window, Qt::LeftButton, 0, moveFrom);
+     QTest::mouseMove(window, moveFrom + moveByWithSnapBack);
+     QTest::mouseMove(window, moveFrom + moveByWithSnapBack*2);
+     QTest::mouseMove(window, moveFrom + moveByWithSnapBack*3);
+
+     QVERIFY(flickable->isMoving());
+     QCOMPARE(flickable->isMovingHorizontally(), horizontalEnabled);
+     QCOMPARE(flickable->isMovingVertically(), verticalEnabled);
+     QVERIFY(flickable->isDragging());
+     QCOMPARE(flickable->isDraggingHorizontally(), horizontalEnabled);
+     QCOMPARE(flickable->isDraggingVertically(), verticalEnabled);
+
+     QCOMPARE(moveSpy.count(), 1);
+     QCOMPARE(vMoveSpy.count(), verticalEnabled ? 1 : 0);
+     QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 1 : 0);
+     QCOMPARE(dragSpy.count(), 1);
+     QCOMPARE(vDragSpy.count(), verticalEnabled ? 1 : 0);
+     QCOMPARE(hDragSpy.count(), horizontalEnabled ? 1 : 0);
+
+     QCOMPARE(moveStartSpy.count(), 1);
+     QCOMPARE(moveEndSpy.count(), 0);
+     QCOMPARE(dragStartSpy.count(), 1);
+     QCOMPARE(dragEndSpy.count(), 0);
+
+     QTest::mouseRelease(window, Qt::LeftButton, 0, moveFrom + moveByWithSnapBack*3);
+
+     // should now start snapping back to bounds (moving but not dragging)
+     QVERIFY(flickable->isMoving());
+     QCOMPARE(flickable->isMovingHorizontally(), horizontalEnabled);
+     QCOMPARE(flickable->isMovingVertically(), verticalEnabled);
+     QVERIFY(!flickable->isDragging());
+     QVERIFY(!flickable->isDraggingHorizontally());
+     QVERIFY(!flickable->isDraggingVertically());
+
+     QCOMPARE(moveSpy.count(), 1);
+     QCOMPARE(vMoveSpy.count(), verticalEnabled ? 1 : 0);
+     QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 1 : 0);
+     QCOMPARE(dragSpy.count(), 2);
+     QCOMPARE(vDragSpy.count(), verticalEnabled ? 2 : 0);
+     QCOMPARE(hDragSpy.count(), horizontalEnabled ? 2 : 0);
+
+     QCOMPARE(moveStartSpy.count(), 1);
+     QCOMPARE(moveEndSpy.count(), 0);
+
+     // wait for any motion to end
+     QTRY_VERIFY(!flickable->isMoving());
+
+     QVERIFY(!flickable->isMovingHorizontally());
+     QVERIFY(!flickable->isMovingVertically());
+     QVERIFY(!flickable->isDragging());
+     QVERIFY(!flickable->isDraggingHorizontally());
+     QVERIFY(!flickable->isDraggingVertically());
+
+     QCOMPARE(moveSpy.count(), 2);
+     QCOMPARE(vMoveSpy.count(), verticalEnabled ? 2 : 0);
+     QCOMPARE(hMoveSpy.count(), horizontalEnabled ? 2 : 0);
+     QCOMPARE(dragSpy.count(), 2);
+     QCOMPARE(vDragSpy.count(), verticalEnabled ? 2 : 0);
+     QCOMPARE(hDragSpy.count(), horizontalEnabled ? 2 : 0);
+
+     QCOMPARE(moveStartSpy.count(), 1);
+     QCOMPARE(moveEndSpy.count(), 1);
+     QCOMPARE(dragStartSpy.count(), 1);
+     QCOMPARE(dragEndSpy.count(), 1);
+
+     QCOMPARE(flickable->contentX(), 0.0);
+     QCOMPARE(flickable->contentY(), 0.0);
+
+    delete window;
+}
 
-    QVERIFY(!flickable->isMovingVertically());
-    QVERIFY(flickable->isMovingHorizontally());
-    QVERIFY(flickable->isMoving());
-    QCOMPARE(vMoveSpy.count(), 0);
-    QCOMPARE(moveSpy.count(), 1);
-    QCOMPARE(hMoveSpy.count(), 1);
+void tst_qquickflickable::flickOnRelease()
+{
+#ifdef Q_OS_MAC
+    QSKIP("Producing flicks on Mac CI impossible due to timing problems");
+#endif
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("flickable03.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QTest::qWaitForWindowShown(window);
+    QVERIFY(window->rootObject() != 0);
+
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
+    QVERIFY(flickable != 0);
 
-    QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(60, 50));
+    // Vertical with a quick press-move-release: should cause a flick in release.
+    QSignalSpy vFlickSpy(flickable, SIGNAL(flickingVerticallyChanged()));
+    // Use something that generates a huge velocity just to make it testable.
+    // In practice this feature matters on touchscreen devices where the
+    // underlying drivers will hopefully provide a pre-calculated velocity
+    // (based on more data than what the UI gets), thus making this use case
+    // working even with small movements.
+    QTest::mousePress(window, Qt::LeftButton, 0, QPoint(50, 300));
+    QTest::mouseMove(window, QPoint(50, 10), 10);
+    QTest::mouseRelease(window, Qt::LeftButton, 0, QPoint(50, 10), 10);
 
-    QTRY_VERIFY(!flickable->isDraggingHorizontally());
-    QVERIFY(!flickable->isDragging());
-    QCOMPARE(vDragSpy.count(), 0);
-    QCOMPARE(dragSpy.count(), 2);
-    QCOMPARE(hDragSpy.count(), 2);
-    QCOMPARE(dragStartSpy.count(), 1);
-    QCOMPARE(dragEndSpy.count(), 1);
-    // Don't test moving because a flick could occur
+    QCOMPARE(vFlickSpy.count(), 1);
+
+    // wait for any motion to end
+    QTRY_VERIFY(flickable->isMoving() == false);
+
+    // Stop on a full pixel after user interaction
+    QCOMPARE(flickable->contentY(), (qreal)qRound(flickable->contentY()));
 
+    delete window;
+}
+
+void tst_qquickflickable::pressWhileFlicking()
+{
 #ifdef Q_OS_MAC
     QSKIP("Producing flicks on Mac CI impossible due to timing problems");
 #endif
 
-    QTRY_VERIFY(!flickable->isMoving());
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("flickable03.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QTest::qWaitForWindowShown(window);
+    QVERIFY(window->rootObject() != 0);
 
-    vMoveSpy.clear();
-    hMoveSpy.clear();
-    moveSpy.clear();
-    QSignalSpy vFlickSpy(flickable, SIGNAL(flickingVerticallyChanged()));
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
+    QVERIFY(flickable != 0);
+
+    QSignalSpy vMoveSpy(flickable, SIGNAL(movingVerticallyChanged()));
+    QSignalSpy hMoveSpy(flickable, SIGNAL(movingHorizontallyChanged()));
+    QSignalSpy moveSpy(flickable, SIGNAL(movingChanged()));
     QSignalSpy hFlickSpy(flickable, SIGNAL(flickingHorizontallyChanged()));
+    QSignalSpy vFlickSpy(flickable, SIGNAL(flickingVerticallyChanged()));
     QSignalSpy flickSpy(flickable, SIGNAL(flickingChanged()));
 
     // flick then press while it is still moving
     // flicking == false, moving == true;
-    flick(canvas, QPoint(20,190), QPoint(20, 50), 200);
+    flick(window, QPoint(20,190), QPoint(20, 50), 200);
     QVERIFY(flickable->verticalVelocity() > 0.0);
     QVERIFY(flickable->isFlicking());
     QVERIFY(flickable->isFlickingVertically());
@@ -513,47 +955,49 @@ void tst_qquickflickable::movingAndDragging()
     QCOMPARE(hFlickSpy.count(), 0);
     QCOMPARE(flickSpy.count(), 1);
 
-    QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20, 50));
+    QTest::mousePress(window, Qt::LeftButton, 0, QPoint(20, 50));
     QTRY_VERIFY(!flickable->isFlicking());
     QVERIFY(!flickable->isFlickingVertically());
     QVERIFY(flickable->isMoving());
     QVERIFY(flickable->isMovingVertically());
 
-    QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,50));
+    QTest::mouseRelease(window, Qt::LeftButton, 0, QPoint(20,50));
     QVERIFY(!flickable->isFlicking());
     QVERIFY(!flickable->isFlickingVertically());
     QTRY_VERIFY(!flickable->isMoving());
     QVERIFY(!flickable->isMovingVertically());
+    // Stop on a full pixel after user interaction
+    QCOMPARE(flickable->contentX(), (qreal)qRound(flickable->contentX()));
 
-    delete canvas;
+    delete window;
 }
 
 void tst_qquickflickable::disabled()
 {
-    QQuickView *canvas = new QQuickView;
-    canvas->setSource(testFileUrl("disabled.qml"));
-    canvas->show();
-    canvas->requestActivateWindow();
-    QVERIFY(canvas->rootObject() != 0);
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("disabled.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QVERIFY(window->rootObject() != 0);
 
-    QQuickFlickable *flick = canvas->rootObject()->findChild<QQuickFlickable*>("flickable");
+    QQuickFlickable *flick = window->rootObject()->findChild<QQuickFlickable*>("flickable");
     QVERIFY(flick != 0);
 
-    QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(50, 90));
+    QTest::mousePress(window, Qt::LeftButton, 0, QPoint(50, 90));
 
-    QTest::mouseMove(canvas, QPoint(50, 80));
-    QTest::mouseMove(canvas, QPoint(50, 70));
-    QTest::mouseMove(canvas, QPoint(50, 60));
+    QTest::mouseMove(window, QPoint(50, 80));
+    QTest::mouseMove(window, QPoint(50, 70));
+    QTest::mouseMove(window, QPoint(50, 60));
 
     QVERIFY(flick->isMoving() == false);
 
-    QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(50, 60));
+    QTest::mouseRelease(window, Qt::LeftButton, 0, QPoint(50, 60));
 
     // verify that mouse clicks on other elements still work (QTBUG-20584)
-    QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(50, 10));
-    QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(50, 10));
+    QTest::mousePress(window, Qt::LeftButton, 0, QPoint(50, 10));
+    QTest::mouseRelease(window, Qt::LeftButton, 0, QPoint(50, 10));
 
-    QTRY_VERIFY(canvas->rootObject()->property("clicked").toBool() == true);
+    QTRY_VERIFY(window->rootObject()->property("clicked").toBool() == true);
 }
 
 void tst_qquickflickable::flickVelocity()
@@ -562,22 +1006,22 @@ void tst_qquickflickable::flickVelocity()
     QSKIP("Producing flicks on Mac CI impossible due to timing problems");
 #endif
 
-    QQuickView *canvas = new QQuickView;
-    canvas->setSource(testFileUrl("flickable03.qml"));
-    canvas->show();
-    canvas->requestActivateWindow();
-    QVERIFY(canvas->rootObject() != 0);
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("flickable03.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QVERIFY(window->rootObject() != 0);
 
-    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(canvas->rootObject());
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
     QVERIFY(flickable != 0);
 
     // flick up
-    flick(canvas, QPoint(20,190), QPoint(20, 50), 200);
+    flick(window, QPoint(20,190), QPoint(20, 50), 200);
     QVERIFY(flickable->verticalVelocity() > 0.0);
     QTRY_VERIFY(flickable->verticalVelocity() == 0.0);
 
     // flick down
-    flick(canvas, QPoint(20,10), QPoint(20, 140), 200);
+    flick(window, QPoint(20,10), QPoint(20, 140), 200);
     QVERIFY(flickable->verticalVelocity() < 0.0);
     QTRY_VERIFY(flickable->verticalVelocity() == 0.0);
 
@@ -585,17 +1029,17 @@ void tst_qquickflickable::flickVelocity()
     QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(flickable);
     bool boosted = false;
     for (int i = 0; i < 6; ++i) {
-        flick(canvas, QPoint(20,390), QPoint(20, 50), 100);
+        flick(window, QPoint(20,390), QPoint(20, 50), 100);
         boosted |= fp->flickBoost > 1.0;
     }
     QVERIFY(boosted);
 
     // Flick in opposite direction -> boost cancelled.
-    flick(canvas, QPoint(20,10), QPoint(20, 340), 200);
+    flick(window, QPoint(20,10), QPoint(20, 340), 200);
     QTRY_VERIFY(flickable->verticalVelocity() < 0.0);
     QVERIFY(fp->flickBoost == 1.0);
 
-    delete canvas;
+    delete window;
 }
 
 void tst_qquickflickable::margins()
@@ -611,8 +1055,8 @@ void tst_qquickflickable::margins()
     QCOMPARE(obj->contentY(), -20.);
     QCOMPARE(obj->contentWidth(), 1600.);
     QCOMPARE(obj->contentHeight(), 600.);
-    QCOMPARE(obj->xOrigin(), 0.);
-    QCOMPARE(obj->yOrigin(), 0.);
+    QCOMPARE(obj->originX(), 0.);
+    QCOMPARE(obj->originY(), 0.);
 
     // Reduce left margin
     obj->setLeftMargin(30);
@@ -657,6 +1101,87 @@ void tst_qquickflickable::margins()
     delete root;
 }
 
+void tst_qquickflickable::cancelOnMouseGrab()
+{
+    QQuickView *window = new QQuickView;
+    window->setSource(testFileUrl("cancel.qml"));
+    window->show();
+    window->requestActivateWindow();
+    QVERIFY(window->rootObject() != 0);
+
+    QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject());
+    QVERIFY(flickable != 0);
+
+    QTest::mousePress(window, Qt::LeftButton, 0, QPoint(10, 10));
+    // drag out of bounds
+    QTest::mouseMove(window, QPoint(50, 50));
+    QTest::mouseMove(window, QPoint(100, 100));
+    QTest::mouseMove(window, QPoint(150, 150));
+
+    QVERIFY(flickable->contentX() != 0);
+    QVERIFY(flickable->contentY() != 0);
+    QVERIFY(flickable->isMoving());
+    QVERIFY(flickable->isDragging());
+
+    // grabbing mouse will cancel flickable interaction.
+    QQuickItem *item = window->rootObject()->findChild<QQuickItem*>("row");
+    item->grabMouse();
+
+    QTRY_COMPARE(flickable->contentX(), 0.);
+    QTRY_COMPARE(flickable->contentY(), 0.);
+    QTRY_VERIFY(!flickable->isMoving());
+    QTRY_VERIFY(!flickable->isDragging());
+
+    QTest::mouseRelease(window, Qt::LeftButton, 0, QPoint(50, 10));
+}
+
+void tst_qquickflickable::clickAndDragWhenTransformed()
+{
+    QQuickView *view = new QQuickView;
+    view->setSource(testFileUrl("transformedFlickable.qml"));
+    view->show();
+    view->requestActivateWindow();
+    QTest::qWaitForWindowShown(view);
+    QVERIFY(view->rootObject() != 0);
+
+    QQuickFlickable *flickable = view->rootObject()->findChild<QQuickFlickable*>("flickable");
+    QVERIFY(flickable != 0);
+
+    // click outside child rect
+    QTest::mousePress(view, Qt::LeftButton, 0, QPoint(190, 190));
+    QTest::qWait(10);
+    QCOMPARE(flickable->property("itemPressed").toBool(), false);
+    QTest::mouseRelease(view, Qt::LeftButton, 0, QPoint(190, 190));
+
+    // click inside child rect
+    QTest::mousePress(view, Qt::LeftButton, 0, QPoint(200, 200));
+    QTest::qWait(10);
+    QCOMPARE(flickable->property("itemPressed").toBool(), true);
+    QTest::mouseRelease(view, Qt::LeftButton, 0, QPoint(200, 200));
+
+    const int threshold = qApp->styleHints()->startDragDistance();
+
+    // drag outside bounds
+    QTest::mousePress(view, Qt::LeftButton, 0, QPoint(160, 160));
+    QTest::qWait(10);
+    QTest::mouseMove(view, QPoint(160 + threshold * 2, 160));
+    QTest::mouseMove(view, QPoint(160 + threshold * 3, 160));
+    QCOMPARE(flickable->isDragging(), false);
+    QCOMPARE(flickable->property("itemPressed").toBool(), false);
+    QTest::mouseRelease(view, Qt::LeftButton, 0, QPoint(180, 160));
+
+    // drag inside bounds
+    QTest::mousePress(view, Qt::LeftButton, 0, QPoint(200, 140));
+    QTest::qWait(10);
+    QTest::mouseMove(view, QPoint(200 + threshold * 2, 140));
+    QTest::mouseMove(view, QPoint(200 + threshold * 3, 140));
+    QCOMPARE(flickable->isDragging(), true);
+    QCOMPARE(flickable->property("itemPressed").toBool(), false);
+    QTest::mouseRelease(view, Qt::LeftButton, 0, QPoint(220, 140));
+
+    delete view;
+}
+
 QTEST_MAIN(tst_qquickflickable)
 
 #include "tst_qquickflickable.moc"