QQuickCanvas renames
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickpathview.cpp
index f8098c8..cb56dd2 100644 (file)
@@ -41,7 +41,7 @@
 
 #include "qquickpathview_p.h"
 #include "qquickpathview_p_p.h"
-#include "qquickcanvas.h"
+#include "qquickwindow.h"
 
 #include <QtQuick/private/qquickstate_p.h>
 #include <private/qqmlglobal_p.h>
 
 // The number of samples to use in calculating the velocity of a flick
 #ifndef QML_FLICK_SAMPLEBUFFER
-#define QML_FLICK_SAMPLEBUFFER 3
+#define QML_FLICK_SAMPLEBUFFER 1
 #endif
 
 // The number of samples to discard when calculating the flick velocity.
 // Touch panels often produce inaccurate results as the finger is lifted.
 #ifndef QML_FLICK_DISCARDSAMPLES
-#define QML_FLICK_DISCARDSAMPLES 1
+#define QML_FLICK_DISCARDSAMPLES 0
 #endif
 
+// The default maximum velocity of a flick.
+#ifndef QML_FLICK_DEFAULTMAXVELOCITY
+#define QML_FLICK_DEFAULTMAXVELOCITY 2500
+#endif
+
+
 QT_BEGIN_NAMESPACE
 
+const qreal MinimumFlickVelocity = 75.0;
+
 inline qreal qmlMod(qreal x, qreal y)
 {
 #ifdef QT_USE_MATH_H_FLOATS
@@ -105,6 +113,23 @@ void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &va
     m_metaobject->setValue(name, val);
 }
 
+QQuickPathViewPrivate::QQuickPathViewPrivate()
+  : path(0), currentIndex(0), currentItemOffset(0.0), startPc(0)
+    , offset(0.0), offsetAdj(0.0), mappedRange(1.0)
+    , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
+    , autoHighlight(true), highlightUp(false), layoutScheduled(false)
+    , moving(false), flicking(false), dragging(false), requestedOnPath(false), inRequest(false)
+    , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY)
+    , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
+    , firstIndex(-1), pathItems(-1), requestedIndex(-1), requestedZ(0)
+    , moveReason(Other), moveDirection(Shortest), attType(0), highlightComponent(0), highlightItem(0)
+    , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
+    , highlightPosition(0)
+    , highlightRangeStart(0), highlightRangeEnd(0)
+    , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
+    , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
+{
+}
 
 void QQuickPathViewPrivate::init()
 {
@@ -113,9 +138,11 @@ void QQuickPathViewPrivate::init()
     q->setAcceptedMouseButtons(Qt::LeftButton);
     q->setFlag(QQuickItem::ItemIsFocusScope);
     q->setFiltersChildMouseEvents(true);
-    FAST_CONNECT(&tl, SIGNAL(updated()), q, SLOT(ticked()))
-    lastPosTime.invalidate();
-    FAST_CONNECT(&tl, SIGNAL(completed()), q, SLOT(movementEnding()))
+    qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()),
+                      q, QQuickPathView, SLOT(ticked()))
+    timer.invalidate();
+    qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
+                      q, QQuickPathView, SLOT(movementEnding()))
 }
 
 QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
@@ -127,7 +154,6 @@ QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
     inRequest = true;
     QQuickItem *item = model->item(modelIndex, false);
     if (item) {
-        QQml_setParent_noEvent(item, q);
         item->setParentItem(q);
         requestedIndex = -1;
         qPathViewAttachedType = attType;
@@ -154,7 +180,6 @@ void QQuickPathView::createdItem(int index, QQuickItem *item)
             att->setOnPath(false);
         }
         item->setParentItem(this);
-        QQml_setParent_noEvent(item, this);
         d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
     } else {
         d->requestedIndex = -1;
@@ -241,7 +266,8 @@ qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
 
     if (model && index >= 0 && index < modelCount) {
         qreal start = 0.0;
-        if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange)
+        if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
+                                   || snapMode != QQuickPathView::NoSnap))
             start = highlightRangeStart;
         qreal globalPos = index + offset;
         globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
@@ -409,14 +435,30 @@ void QQuickPathViewPrivate::regenerate()
     q->refill();
 }
 
+void QQuickPathViewPrivate::setDragging(bool d)
+{
+    Q_Q(QQuickPathView);
+    if (dragging == d)
+        return;
+
+    dragging = d;
+    if (dragging)
+        emit q->dragStarted();
+    else
+        emit q->dragEnded();
+
+    emit q->draggingChanged();
+}
+
 /*!
     \qmlclass PathView QQuickPathView
     \inqmlmodule QtQuick 2
-    \ingroup qml-view-elements
-    \brief The PathView element lays out model-provided items on a path.
+    \ingroup qtquick-paths
+    \ingroup qtquick-views
     \inherits Item
+    \brief Lays out model-provided items on a path
 
-    A PathView displays data from models created from built-in QML elements like ListModel
+    A PathView displays data from models created from built-in QML types like ListModel
     and XmlListModel, or custom model classes defined in C++ that inherit from
     QAbstractListModel.
 
@@ -427,11 +469,11 @@ void QQuickPathViewPrivate::regenerate()
 
     For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
 
-    \snippet doc/src/snippets/qml/pathview/ContactModel.qml 0
+    \snippet qml/pathview/ContactModel.qml 0
 
     This data can be represented as a PathView, like this:
 
-    \snippet doc/src/snippets/qml/pathview/pathview.qml 0
+    \snippet qml/pathview/pathview.qml 0
 
     \image pathview.gif
 
@@ -464,14 +506,14 @@ void QQuickPathViewPrivate::regenerate()
     this attached property directly as \c PathView.isCurrentItem, while the child
     \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
 
-    \snippet doc/src/snippets/qml/pathview/pathview.qml 1
+    \snippet qml/pathview/pathview.qml 1
 
-    \bold Note that views do not enable \e clip automatically.  If the view
+    \b Note that views do not enable \e clip automatically.  If the view
     is not clipped by another item or the screen, it will be necessary
     to set \e {clip: true} in order to have the out of view items clipped
     nicely.
 
-    \sa Path, {declarative/modelviews/pathview}{PathView example}
+    \sa Path, {quick/modelviews/pathview}{PathView example}
 */
 
 QQuickPathView::QQuickPathView(QQuickItem *parent)
@@ -526,7 +568,7 @@ QQuickPathView::~QQuickPathView()
 
     This property may be used to adjust the appearance of the current item.
 
-    \snippet doc/src/snippets/qml/pathview/pathview.qml 1
+    \snippet qml/pathview/pathview.qml 1
 */
 
 /*!
@@ -535,7 +577,7 @@ QQuickPathView::~QQuickPathView()
 
     The model provides a set of data that is used to create the items for the view.
     For large or dynamic datasets the model is usually provided by a C++ model object.
-    Models can also be created directly in QML, using the ListModel element.
+    Models can also be created directly in QML, using the ListModel type.
 
     \sa {qmlmodels}{Data Models}
 */
@@ -552,10 +594,12 @@ void QQuickPathView::setModel(const QVariant &model)
         return;
 
     if (d->model) {
-        disconnect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
-                this, SLOT(modelUpdated(QQuickChangeSet,bool)));
-        disconnect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
-        disconnect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
+        qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
+                             this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
+        qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
+                             this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
+        qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
+                             this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
         for (int i=0; i<d->items.count(); i++){
             QQuickItem *p = d->items[i];
             d->releaseItem(p);
@@ -584,10 +628,12 @@ void QQuickPathView::setModel(const QVariant &model)
     }
     d->modelCount = 0;
     if (d->model) {
-        connect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
-                this, SLOT(modelUpdated(QQuickChangeSet,bool)));
-        connect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
-        connect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
+        qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
+                          this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
+        qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
+                          this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
+        qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
+                          this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
         d->modelCount = d->model->count();
         if (d->model->count())
             d->offset = qmlMod(d->offset, qreal(d->model->count()));
@@ -630,9 +676,11 @@ void QQuickPathView::setPath(QQuickPath *path)
     if (d->path == path)
         return;
     if (d->path)
-        disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
+        qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
+                             this, QQuickPathView, SLOT(pathUpdated()));
     d->path = path;
-    connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
+    qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
+                      this, QQuickPathView, SLOT(pathUpdated()));
     if (d->isValid() && isComponentComplete()) {
         d->clear();
         if (d->attType) {
@@ -657,8 +705,9 @@ int QQuickPathView::currentIndex() const
 void QQuickPathView::setCurrentIndex(int idx)
 {
     Q_D(QQuickPathView);
-    if (d->model && d->modelCount)
-        idx = qAbs(idx % d->modelCount);
+    idx = d->modelCount
+        ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
+        : 0;
     if (d->model && (idx != d->currentIndex || !d->currentItem)) {
         if (d->currentItem) {
             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
@@ -673,7 +722,7 @@ void QQuickPathView::setCurrentIndex(int idx)
         if (d->modelCount) {
             d->createCurrentItem();
             if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
-                d->snapToCurrent();
+                d->snapToIndex(d->currentIndex);
             d->currentItemOffset = d->positionOfIndex(d->currentIndex);
             d->updateHighlight();
         }
@@ -695,7 +744,7 @@ QQuickItem *QQuickPathView::currentItem() const
 
     Increments the current index.
 
-    \bold Note: methods should only be called after the Component has completed.
+    \b Note: methods should only be called after the Component has completed.
 */
 void QQuickPathView::incrementCurrentIndex()
 {
@@ -709,18 +758,13 @@ void QQuickPathView::incrementCurrentIndex()
 
     Decrements the current index.
 
-    \bold Note: methods should only be called after the Component has completed.
+    \b Note: methods should only be called after the Component has completed.
 */
 void QQuickPathView::decrementCurrentIndex()
 {
     Q_D(QQuickPathView);
-    if (d->model && d->modelCount) {
-        int idx = currentIndex()-1;
-        if (idx < 0)
-            idx = d->modelCount - 1;
-        d->moveDirection = QQuickPathViewPrivate::Negative;
-        setCurrentIndex(idx);
-    }
+    d->moveDirection = QQuickPathViewPrivate::Negative;
+    setCurrentIndex(currentIndex()-1);
 }
 
 /*!
@@ -738,6 +782,7 @@ qreal QQuickPathView::offset() const
 void QQuickPathView::setOffset(qreal offset)
 {
     Q_D(QQuickPathView);
+    d->moveReason = QQuickPathViewPrivate::Other;
     d->setOffset(offset);
     d->updateCurrent();
 }
@@ -864,7 +909,7 @@ void QQuickPathView::setPreferredHighlightBegin(qreal start)
     if (d->highlightRangeStart == start || start < 0 || start > 1.0)
         return;
     d->highlightRangeStart = start;
-    d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+    d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
     refill();
     emit preferredHighlightBeginChanged();
 }
@@ -881,7 +926,7 @@ void QQuickPathView::setPreferredHighlightEnd(qreal end)
     if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
         return;
     d->highlightRangeEnd = end;
-    d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+    d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
     refill();
     emit preferredHighlightEndChanged();
 }
@@ -898,10 +943,12 @@ void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
     if (d->highlightRangeMode == mode)
         return;
     d->highlightRangeMode = mode;
-    d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+    d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
     if (d->haveHighlightRange) {
         d->regenerate();
-        d->snapToCurrent();
+        int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
+        if (index >= 0)
+            d->snapToIndex(index);
     }
     emit highlightRangeModeChanged();
 }
@@ -975,6 +1022,28 @@ void QQuickPathView::setFlickDeceleration(qreal dec)
 }
 
 /*!
+    \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
+    This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
+
+    The default value is platform dependent.
+*/
+qreal QQuickPathView::maximumFlickVelocity() const
+{
+    Q_D(const QQuickPathView);
+    return d->maximumFlickVelocity;
+}
+
+void QQuickPathView::setMaximumFlickVelocity(qreal vel)
+{
+    Q_D(QQuickPathView);
+    if (vel == d->maximumFlickVelocity)
+        return;
+    d->maximumFlickVelocity = vel;
+    emit maximumFlickVelocityChanged();
+}
+
+
+/*!
     \qmlproperty bool QtQuick2::PathView::interactive
 
     A user cannot drag or flick a PathView that is not interactive.
@@ -1024,6 +1093,18 @@ bool QQuickPathView::isFlicking() const
 }
 
 /*!
+    \qmlproperty bool QtQuick2::PathView::dragging
+
+    This property holds whether the view is currently moving
+    due to the user dragging the view.
+*/
+bool QQuickPathView::isDragging() const
+{
+    Q_D(const QQuickPathView);
+    return d->dragging;
+}
+
+/*!
     \qmlsignal QtQuick2::PathView::onMovementStarted()
 
     This handler is called when the view begins moving due to user
@@ -1055,22 +1136,38 @@ bool QQuickPathView::isFlicking() const
 */
 
 /*!
+    \qmlsignal QtQuick2::PathView::onDragStarted()
+
+    This handler is called when the view starts to be dragged due to user
+    interaction.
+*/
+
+/*!
+    \qmlsignal QtQuick2::PathView::onDragEnded()
+
+    This handler is called when the user stops dragging the view.
+
+    If the velocity of the drag is suffient at the time the
+    touch/mouse button is released then a flick will start.
+*/
+
+/*!
     \qmlproperty Component QtQuick2::PathView::delegate
 
     The delegate provides a template defining each item instantiated by the view.
     The index is exposed as an accessible \c index property.  Properties of the
     model are also available depending upon the type of \l {qmlmodels}{Data Model}.
 
-    The number of elements in the delegate has a direct effect on the
+    The number of objects and bindings in the delegate has a direct effect on the
     flicking performance of the view when pathItemCount is specified.  If at all possible, place functionality
     that is not needed for the normal display of the delegate in a \l Loader which
-    can load additional elements when needed.
+    can load additional components when needed.
 
     Note that the PathView will layout the items based on the size of the root
     item in the delegate.
 
     Here is an example delegate:
-    \snippet doc/src/snippets/qml/pathview/pathview.qml 1
+    \snippet qml/pathview/pathview.qml 1
 */
 QQmlComponent *QQuickPathView::delegate() const
 {
@@ -1128,6 +1225,41 @@ void QQuickPathView::setPathItemCount(int i)
     emit pathItemCountChanged();
 }
 
+/*!
+    \qmlproperty enumeration QtQuick2::PathView::snapMode
+
+    This property determines how the items will settle following a drag or flick.
+    The possible values are:
+
+    \list
+    \li PathView.NoSnap (default) - the items stop anywhere along the path.
+    \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
+    \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
+        \l preferredHighlightBegin at the time the press is released.  This mode is particularly
+        useful for moving one page at a time.
+    \endlist
+
+    \c snapMode does not affect the \l currentIndex.  To update the
+    \l currentIndex as the view is moved, set \l highlightRangeMode
+    to \c PathView.StrictlyEnforceRange (default for PathView).
+
+    \sa highlightRangeMode
+*/
+QQuickPathView::SnapMode QQuickPathView::snapMode() const
+{
+    Q_D(const QQuickPathView);
+    return d->snapMode;
+}
+
+void QQuickPathView::setSnapMode(SnapMode mode)
+{
+    Q_D(QQuickPathView);
+    if (mode == d->snapMode)
+        return;
+    d->snapMode = mode;
+    emit snapModeChanged();
+}
+
 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
 {
     qreal samples = qMin(path->path().length()/5, qreal(500.0));
@@ -1189,6 +1321,15 @@ qreal QQuickPathViewPrivate::calcVelocity() const
     return velocity;
 }
 
+qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
+{
+    if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
+        return event->timestamp();
+    }
+
+    return QQuickItemPrivate::elapsed(timer);
+}
+
 void QQuickPathView::mousePressEvent(QMouseEvent *event)
 {
     Q_D(QQuickPathView);
@@ -1202,16 +1343,13 @@ void QQuickPathView::mousePressEvent(QMouseEvent *event)
 
 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
 {
-    Q_Q(QQuickPathView);
-    if (!interactive || !items.count())
+    if (!interactive || !items.count() || !model || !modelCount)
         return;
     velocityBuffer.clear();
-    QPointF scenePoint = q->mapToScene(event->localPos());
     int idx = 0;
     for (; idx < items.count(); ++idx) {
-        QRectF rect = items.at(idx)->boundingRect();
-        rect = items.at(idx)->mapRectToScene(rect);
-        if (rect.contains(scenePoint))
+        QQuickItem *item = items.at(idx);
+        if (item->contains(item->mapFromScene(event->windowPos())))
             break;
     }
     if (idx == items.count() && dragMargin == 0.)  // didn't click on an item
@@ -1224,14 +1362,14 @@ void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
             return;
     }
 
-    if (tl.isActive() && flicking)
+
+    if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
         stealMouse = true; // If we've been flicked then steal the click.
     else
         stealMouse = false;
 
-    lastElapsed = 0;
-    lastDist = 0;
-    QQuickItemPrivate::start(lastPosTime);
+    QQuickItemPrivate::start(timer);
+    lastPosTime = computeCurrentTime(event);
     tl.clear();
 }
 
@@ -1251,20 +1389,18 @@ void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
 {
     Q_Q(QQuickPathView);
-    if (!interactive || !lastPosTime.isValid())
+    if (!interactive || !timer.isValid() || !model || !modelCount)
         return;
 
+    qint64 currentTimestamp = computeCurrentTime(event);
     qreal newPc;
     QPointF pathPoint = pointNear(event->localPos(), &newPc);
     if (!stealMouse) {
         QPointF delta = pathPoint - startPoint;
         if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
             stealMouse = true;
-            startPc = newPc;
         }
-    }
-
-    if (stealMouse) {
+    } else {
         moveReason = QQuickPathViewPrivate::Mouse;
         qreal diff = (newPc - startPc)*modelCount*mappedRange;
         if (diff) {
@@ -1275,17 +1411,19 @@ void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
             else if (diff < -modelCount/2)
                 diff += modelCount;
 
-            lastElapsed = QQuickItemPrivate::restart(lastPosTime);
-            lastDist = diff;
-            startPc = newPc;
-            addVelocitySample(diff / (qreal(lastElapsed) / 1000.));
+            qint64 elapsed = currentTimestamp - lastPosTime;
+            if (elapsed > 0)
+                addVelocitySample(diff / (qreal(elapsed) / 1000.));
         }
         if (!moving) {
             moving = true;
             emit q->movingChanged();
             emit q->movementStarted();
         }
+        setDragging(true);
     }
+    startPc = newPc;
+    lastPosTime = currentTimestamp;
 }
 
 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
@@ -1305,26 +1443,45 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
     Q_Q(QQuickPathView);
     stealMouse = false;
     q->setKeepMouseGrab(false);
-    if (!interactive || !lastPosTime.isValid())
+    setDragging(false);
+    if (!interactive || !timer.isValid() || !model || !modelCount) {
+        timer.invalidate();
+        if (!tl.isActive())
+            q->movementEnding();
         return;
+    }
 
     qreal velocity = calcVelocity();
-    if (model && modelCount && qAbs(velocity) > 0.5) {
-        qreal count = pathItems == -1 ? modelCount : pathItems;
-        if (qAbs(velocity) > count * 2) // limit velocity
-            velocity = (velocity > 0 ? count : -count) * 2;
+    qreal count = modelCount*mappedRange;
+    qreal pixelVelocity = (path->path().length()/count) * velocity;
+    if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
+        if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
+            // limit velocity
+            qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
+            velocity = maxVel / (path->path().length()/count);
+        }
         // Calculate the distance to be travelled
         qreal v2 = velocity*velocity;
         qreal accel = deceleration/10;
         qreal dist = 0;
-        if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
-            // + 0.25 to encourage moving at least one item in the flick direction
-            dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
-            // round to nearest item.
-            if (velocity > 0.)
-                dist = qRound(dist + offset) - offset;
-            else
-                dist = qRound(dist - offset) + offset;
+        if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
+                || snapMode != QQuickPathView::NoSnap)) {
+            if (snapMode == QQuickPathView::SnapOneItem) {
+                // encourage snapping one item in direction of motion
+                if (velocity > 0.)
+                    dist = qRound(0.5 + offset) - offset;
+                else
+                    dist = qRound(0.5 - offset) + offset;
+            } else {
+                // + 0.25 to encourage moving at least one item in the flick direction
+                dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
+
+                // round to nearest item.
+                if (velocity > 0.)
+                    dist = qRound(dist + offset) - offset;
+                else
+                    dist = qRound(dist - offset) + offset;
+            }
             // Calculate accel required to stop on item boundary
             if (dist <= 0.) {
                 dist = 0.;
@@ -1335,6 +1492,7 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
         } else {
             dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
         }
+        flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
         offsetAdj = 0.0;
         moveOffset.setValue(offset);
         tl.accel(moveOffset, velocity, accel, dist);
@@ -1348,7 +1506,7 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
         fixOffset();
     }
 
-    lastPosTime.invalidate();
+    timer.invalidate();
     if (!tl.isActive())
         q->movementEnding();
 }
@@ -1356,12 +1514,13 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
 {
     Q_D(QQuickPathView);
-    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
-    QQuickCanvas *c = canvas();
+    QPointF localPos = mapFromScene(event->windowPos());
+
+    QQuickWindow *c = window();
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool stealThisEvent = d->stealMouse;
-    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
-        QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+    if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
+        QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
                                event->button(), event->buttons(), event->modifiers());
         mouseEvent.setAccepted(false);
 
@@ -1384,8 +1543,8 @@ bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
             grabMouse();
 
         return d->stealMouse;
-    } else if (d->lastPosTime.isValid()) {
-        d->lastPosTime.invalidate();
+    } else if (d->timer.isValid()) {
+        d->timer.invalidate();
         d->fixOffset();
     }
     if (event->type() == QEvent::MouseButtonRelease)
@@ -1419,7 +1578,11 @@ void QQuickPathView::mouseUngrabEvent()
         // fix our state
         d->stealMouse = false;
         setKeepMouseGrab(false);
-        d->lastPosTime.invalidate();
+        d->timer.invalidate();
+        d->fixOffset();
+        d->setDragging(false);
+        if (!d->tl.isActive())
+            movementEnding();
     }
 }
 
@@ -1446,6 +1609,7 @@ void QQuickPathView::componentComplete()
         d->regenerate();
     }
     d->updateHighlight();
+    d->updateCurrent();
 
     if (d->modelCount)
         emit countChanged();
@@ -1454,10 +1618,12 @@ void QQuickPathView::componentComplete()
 void QQuickPathView::refill()
 {
     Q_D(QQuickPathView);
+
+    d->layoutScheduled = false;
+
     if (!d->isValid() || !isComponentComplete())
         return;
 
-    d->layoutScheduled = false;
     bool currentVisible = false;
 
     // first move existing items and remove items off path
@@ -1497,7 +1663,8 @@ void QQuickPathView::refill()
         if (d->items.count() < count) {
             int idx = qRound(d->modelCount - d->offset) % d->modelCount;
             qreal startPos = 0.0;
-            if (d->haveHighlightRange && d->highlightRangeMode != QQuickPathView::NoHighlightRange)
+            if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
+                                          || d->snapMode != QQuickPathView::NoSnap))
                 startPos = d->highlightRangeStart;
             if (d->firstIndex >= 0) {
                 startPos = d->positionOfIndex(d->firstIndex);
@@ -1611,7 +1778,6 @@ void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
             currentChanged = true;
         } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
             // current item has been removed.
-            d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
             if (r.isMove()) {
                 moveId = r.moveId;
                 moveOffset = d->currentIndex - r.index;
@@ -1621,6 +1787,7 @@ void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
                 d->releaseItem(d->currentItem);
                 d->currentItem = 0;
             }
+            d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
             currentChanged = true;
         }
 
@@ -1654,6 +1821,8 @@ void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
     d->offset = qmlMod(d->offset, d->modelCount);
     if (d->offset < 0)
         d->offset += d->modelCount;
+    if (d->currentIndex == -1)
+        d->currentIndex = d->calcCurrentIndex();
 
     d->itemCache += d->items;
     d->items.clear();
@@ -1710,7 +1879,7 @@ void QQuickPathView::movementEnding()
 // find the item closest to the snap position
 int QQuickPathViewPrivate::calcCurrentIndex()
 {
-    int current = -1;
+    int current = 0;
     if (modelCount && model && items.count()) {
         offset = qmlMod(offset, modelCount);
         if (offset < 0)
@@ -1745,7 +1914,7 @@ void QQuickPathViewPrivate::createCurrentItem()
 void QQuickPathViewPrivate::updateCurrent()
 {
     Q_Q(QQuickPathView);
-    if (moveReason != Mouse)
+    if (moveReason == SetIndex)
         return;
     if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
         return;
@@ -1773,22 +1942,23 @@ void QQuickPathViewPrivate::fixOffset()
 {
     Q_Q(QQuickPathView);
     if (model && items.count()) {
-        if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
+        if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
+                || snapMode != QQuickPathView::NoSnap)) {
             int curr = calcCurrentIndex();
-            if (curr != currentIndex)
+            if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
                 q->setCurrentIndex(curr);
             else
-                snapToCurrent();
+                snapToIndex(curr);
         }
     }
 }
 
-void QQuickPathViewPrivate::snapToCurrent()
+void QQuickPathViewPrivate::snapToIndex(int index)
 {
     if (!model || modelCount <= 0)
         return;
 
-    qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
+    qreal targetOffset = qmlMod(modelCount - index, modelCount);
 
     if (offset == targetOffset)
         return;
@@ -1802,7 +1972,7 @@ void QQuickPathViewPrivate::snapToCurrent()
 
     if (!duration) {
         tl.set(moveOffset, targetOffset);
-    } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
+    } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2.0)) {
         qreal distance = modelCount - targetOffset + offset;
         if (targetOffset > moveOffset) {
             tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
@@ -1811,7 +1981,7 @@ void QQuickPathViewPrivate::snapToCurrent()
         } else {
             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
         }
-    } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
+    } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2.0) {
         qreal distance = modelCount - offset + targetOffset;
         if (targetOffset < moveOffset) {
             tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));