QQuickCanvas renames
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickpathview.cpp
index ec31731..cb56dd2 100644 (file)
@@ -1,10 +1,9 @@
 /****************************************************************************
 **
 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
 ** Contact: http://www.qt-project.org/
 **
-** This file is part of the QtDeclarative module of the Qt Toolkit.
+** This file is part of the QtQml module of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:LGPL$
 ** GNU Lesser General Public License Usage
 **
 **
 **
+**
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/
 
 #include "qquickpathview_p.h"
 #include "qquickpathview_p_p.h"
-#include "qquickcanvas.h"
+#include "qquickwindow.h"
 
-#include <QtQuick/private/qdeclarativestate_p.h>
-#include <private/qdeclarativeopenmetaobject_p.h>
+#include <QtQuick/private/qquickstate_p.h>
+#include <private/qqmlglobal_p.h>
+#include <private/qqmlopenmetaobject_p.h>
 #include <private/qlistmodelinterface_p.h>
-#include <private/qdeclarativechangeset_p.h>
+#include <private/qquickchangeset_p.h>
 
 #include <QtGui/qevent.h>
 #include <QtGui/qevent.h>
 #include <QtCore/qmath.h>
 #include <math.h>
 
+// The number of samples to use in calculating the velocity of a flick
+#ifndef QML_FLICK_SAMPLEBUFFER
+#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 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
@@ -67,16 +87,16 @@ inline qreal qmlMod(qreal x, qreal y)
         return fmod(x, y);
 }
 
-static QDeclarativeOpenMetaObjectType *qPathViewAttachedType = 0;
+static QQmlOpenMetaObjectType *qPathViewAttachedType = 0;
 
 QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
 : QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
 {
     if (qPathViewAttachedType) {
-        m_metaobject = new QDeclarativeOpenMetaObject(this, qPathViewAttachedType);
+        m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
         m_metaobject->setCached(true);
     } else {
-        m_metaobject = new QDeclarativeOpenMetaObject(this);
+        m_metaobject = new QQmlOpenMetaObject(this);
     }
 }
 
@@ -93,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()
 {
@@ -101,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)
@@ -115,7 +154,6 @@ QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
     inRequest = true;
     QQuickItem *item = model->item(modelIndex, false);
     if (item) {
-        QDeclarative_setParent_noEvent(item, q);
         item->setParentItem(q);
         requestedIndex = -1;
         qPathViewAttachedType = attType;
@@ -142,7 +180,6 @@ void QQuickPathView::createdItem(int index, QQuickItem *item)
             att->setOnPath(false);
         }
         item->setParentItem(this);
-        QDeclarative_setParent_noEvent(item, this);
         d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
     } else {
         d->requestedIndex = -1;
@@ -189,12 +226,12 @@ QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
     return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
 }
 
-QDeclarativeOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
+QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
 {
     Q_Q(QQuickPathView);
     if (!attType) {
         // pre-create one metatype to share with all attached objects
-        attType = new QDeclarativeOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
+        attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
         foreach (const QString &attr, path->attributes())
             attType->createProperty(attr.toUtf8());
     }
@@ -229,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;
@@ -262,12 +300,12 @@ void QQuickPathViewPrivate::createHighlight()
 
     QQuickItem *item = 0;
     if (highlightComponent) {
-        QDeclarativeContext *creationContext = highlightComponent->creationContext();
-        QDeclarativeContext *highlightContext = new QDeclarativeContext(
+        QQmlContext *creationContext = highlightComponent->creationContext();
+        QQmlContext *highlightContext = new QQmlContext(
                 creationContext ? creationContext : qmlContext(q));
         QObject *nobj = highlightComponent->create(highlightContext);
         if (nobj) {
-            QDeclarative_setParent_noEvent(highlightContext, nobj);
+            QQml_setParent_noEvent(highlightContext, nobj);
             item = qobject_cast<QQuickItem *>(nobj);
             if (!item)
                 delete nobj;
@@ -278,7 +316,7 @@ void QQuickPathViewPrivate::createHighlight()
         item = new QQuickItem;
     }
     if (item) {
-        QDeclarative_setParent_noEvent(item, q);
+        QQml_setParent_noEvent(item, q);
         item->setParentItem(q);
         highlightItem = item;
         changed = true;
@@ -377,8 +415,8 @@ void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
             att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
     }
     QPointF pf = path->pointAt(percent);
-    item->setX(qRound(pf.x() - item->width()/2));
-    item->setY(qRound(pf.y() - item->height()/2));
+    item->setX(pf.x() - item->width()/2);
+    item->setY(pf.y() - item->height()/2);
 }
 
 void QQuickPathViewPrivate::regenerate()
@@ -397,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.
 
@@ -415,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/declarative/pathview/ContactModel.qml 0
+    \snippet qml/pathview/ContactModel.qml 0
 
     This data can be represented as a PathView, like this:
 
-    \snippet doc/src/snippets/declarative/pathview/pathview.qml 0
+    \snippet qml/pathview/pathview.qml 0
 
     \image pathview.gif
 
@@ -452,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/declarative/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)
@@ -514,7 +568,7 @@ QQuickPathView::~QQuickPathView()
 
     This property may be used to adjust the appearance of the current item.
 
-    \snippet doc/src/snippets/declarative/pathview/pathview.qml 1
+    \snippet qml/pathview/pathview.qml 1
 */
 
 /*!
@@ -523,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}
 */
@@ -540,10 +594,12 @@ void QQuickPathView::setModel(const QVariant &model)
         return;
 
     if (d->model) {
-        disconnect(d->model, SIGNAL(modelUpdated(QDeclarativeChangeSet,bool)),
-                this, SLOT(modelUpdated(QDeclarativeChangeSet,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);
@@ -572,10 +628,12 @@ void QQuickPathView::setModel(const QVariant &model)
     }
     d->modelCount = 0;
     if (d->model) {
-        connect(d->model, SIGNAL(modelUpdated(QDeclarativeChangeSet,bool)),
-                this, SLOT(modelUpdated(QDeclarativeChangeSet,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()));
@@ -606,21 +664,23 @@ int QQuickPathView::count() const
     This property holds the path used to lay out the items.
     For more information see the \l Path documentation.
 */
-QDeclarativePath *QQuickPathView::path() const
+QQuickPath *QQuickPathView::path() const
 {
     Q_D(const QQuickPathView);
     return d->path;
 }
 
-void QQuickPathView::setPath(QDeclarativePath *path)
+void QQuickPathView::setPath(QQuickPath *path)
 {
     Q_D(QQuickPathView);
     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) {
@@ -645,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))
@@ -661,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();
         }
@@ -683,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()
 {
@@ -697,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);
 }
 
 /*!
@@ -726,6 +782,7 @@ qreal QQuickPathView::offset() const
 void QQuickPathView::setOffset(qreal offset)
 {
     Q_D(QQuickPathView);
+    d->moveReason = QQuickPathViewPrivate::Other;
     d->setOffset(offset);
     d->updateCurrent();
 }
@@ -775,13 +832,13 @@ void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
     \sa highlightItem, highlightRangeMode
 */
 
-QDeclarativeComponent *QQuickPathView::highlight() const
+QQmlComponent *QQuickPathView::highlight() const
 {
     Q_D(const QQuickPathView);
     return d->highlightComponent;
 }
 
-void QQuickPathView::setHighlight(QDeclarativeComponent *highlight)
+void QQuickPathView::setHighlight(QQmlComponent *highlight)
 {
     Q_D(QQuickPathView);
     if (highlight != d->highlightComponent) {
@@ -852,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();
 }
@@ -869,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();
 }
@@ -886,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();
 }
@@ -963,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.
@@ -1012,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
@@ -1043,24 +1136,40 @@ 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/declarative/pathview/pathview.qml 1
+    \snippet qml/pathview/pathview.qml 1
 */
-QDeclarativeComponent *QQuickPathView::delegate() const
+QQmlComponent *QQuickPathView::delegate() const
 {
     Q_D(const QQuickPathView);
      if (d->model) {
@@ -1071,7 +1180,7 @@ QDeclarativeComponent *QQuickPathView::delegate() const
     return 0;
 }
 
-void QQuickPathView::setDelegate(QDeclarativeComponent *delegate)
+void QQuickPathView::setDelegate(QQmlComponent *delegate)
 {
     Q_D(QQuickPathView);
     if (delegate == this->delegate())
@@ -1116,14 +1225,66 @@ 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
 {
-    //XXX maybe do recursively at increasing resolution.
+    qreal samples = qMin(path->path().length()/5, qreal(500.0));
+    qreal res = path->path().length()/samples;
+
     qreal mindist = 1e10; // big number
     QPointF nearPoint = path->pointAt(0);
     qreal nearPc = 0;
-    for (qreal i=1; i < 1000; i++) {
-        QPointF pt = path->pointAt(i/1000.0);
+
+    // get rough pos
+    for (qreal i=1; i < samples; i++) {
+        QPointF pt = path->pointAt(i/samples);
+        QPointF diff = pt - point;
+        qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
+        if (dist < mindist) {
+            nearPoint = pt;
+            nearPc = i;
+            mindist = dist;
+        }
+    }
+
+    // now refine
+    qreal approxPc = nearPc;
+    for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
+        QPointF pt = path->pointAt(i/samples);
         QPointF diff = pt - point;
         qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
         if (dist < mindist) {
@@ -1134,11 +1295,41 @@ QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercen
     }
 
     if (nearPercent)
-        *nearPercent = nearPc / 1000.0;
+        *nearPercent = nearPc / samples;
 
     return nearPoint;
 }
 
+void QQuickPathViewPrivate::addVelocitySample(qreal v)
+{
+    velocityBuffer.append(v);
+    if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
+        velocityBuffer.remove(0);
+}
+
+qreal QQuickPathViewPrivate::calcVelocity() const
+{
+    qreal velocity = 0;
+    if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
+        int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
+        for (int i = 0; i < count; ++i) {
+            qreal v = velocityBuffer.at(i);
+            velocity += v;
+        }
+        velocity /= count;
+    }
+    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);
@@ -1152,15 +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;
-    QPointF scenePoint = q->mapToScene(event->localPos());
+    velocityBuffer.clear();
     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
@@ -1173,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();
 }
 
@@ -1200,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) {
@@ -1224,16 +1411,19 @@ void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
             else if (diff < -modelCount/2)
                 diff += modelCount;
 
-            lastElapsed = QQuickItemPrivate::restart(lastPosTime);
-            lastDist = diff;
-            startPc = newPc;
+            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)
@@ -1253,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 elapsed = qreal(lastElapsed + QQuickItemPrivate::elapsed(lastPosTime)) / 1000.;
-    qreal velocity = elapsed > 0. ? lastDist / elapsed : 0;
-    if (model && modelCount && qAbs(velocity) > 1.) {
-        qreal count = pathItems == -1 ? modelCount : pathItems;
-        if (qAbs(velocity) > count * 2) // limit velocity
-            velocity = (velocity > 0 ? count : -count) * 2;
+    qreal velocity = calcVelocity();
+    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;
-        // + 0.25 to encourage moving at least one item in the flick direction
-        qreal dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
-        if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
-            // round to nearest item.
-            if (velocity > 0.)
-                dist = qRound(dist + offset) - offset;
-            else
-                dist = qRound(dist - offset) + offset;
+        qreal dist = 0;
+        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.;
@@ -1280,11 +1489,14 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
             } else {
                 accel = v2 / (2.0f * qAbs(dist));
             }
+        } 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);
-        tl.callback(QDeclarativeTimeLineCallback(&moveOffset, fixOffsetCallback, this));
+        tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
         if (!flicking) {
             flicking = true;
             emit q->flickingChanged();
@@ -1294,7 +1506,7 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
         fixOffset();
     }
 
-    lastPosTime.invalidate();
+    timer.invalidate();
     if (!tl.isActive())
         q->movementEnding();
 }
@@ -1302,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);
 
@@ -1330,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)
@@ -1365,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();
     }
 }
 
@@ -1392,6 +1609,7 @@ void QQuickPathView::componentComplete()
         d->regenerate();
     }
     d->updateHighlight();
+    d->updateCurrent();
 
     if (d->modelCount)
         emit countChanged();
@@ -1400,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
@@ -1443,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);
@@ -1503,14 +1724,14 @@ void QQuickPathView::refill()
             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
                 att->setOnPath(false);
         } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
-            if (d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false)) {
+            if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
                 d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
                 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
                     att->setIsCurrentItem(true);
             }
         }
     } else if (!waiting && !d->currentItem) {
-        if (d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true)) {
+        if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
             d->currentItem->setFocus(true);
             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
                 att->setIsCurrentItem(true);
@@ -1530,7 +1751,7 @@ void QQuickPathView::refill()
         d->releaseItem(d->itemCache.takeLast());
 }
 
-void QQuickPathView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool reset)
+void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
 {
     Q_D(QQuickPathView);
     if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
@@ -1551,13 +1772,12 @@ void QQuickPathView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
     int moveOffset;
     bool currentChanged = false;
     bool changedOffset = false;
-    foreach (const QDeclarativeChangeSet::Remove &r, changeSet.removes()) {
+    foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
         if (moveId == -1 && d->currentIndex >= r.index + r.count) {
             d->currentIndex -= r.count;
             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;
@@ -1567,6 +1787,7 @@ void QQuickPathView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
                 d->releaseItem(d->currentItem);
                 d->currentItem = 0;
             }
+            d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
             currentChanged = true;
         }
 
@@ -1577,7 +1798,7 @@ void QQuickPathView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
         }
         d->modelCount -= r.count;
     }
-    foreach (const QDeclarativeChangeSet::Insert &i, changeSet.inserts()) {
+    foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
         if (d->modelCount) {
             if (moveId == -1 && i.index <= d->currentIndex) {
                 d->currentIndex += i.count;
@@ -1600,6 +1821,8 @@ void QQuickPathView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool r
     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();
@@ -1656,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)
@@ -1674,13 +1897,13 @@ void QQuickPathViewPrivate::createCurrentItem()
         return;
     int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
     if (itemIndex < items.count()) {
-        if (currentItem = getItem(currentIndex, currentIndex, true)) {
+        if ((currentItem = getItem(currentIndex, currentIndex, true))) {
             currentItem->setFocus(true);
             if (QQuickPathViewAttached *att = attached(currentItem))
                 att->setIsCurrentItem(true);
         }
     } else if (currentIndex >= 0 && currentIndex < modelCount) {
-        if (currentItem = getItem(currentIndex, currentIndex, false)) {
+        if ((currentItem = getItem(currentIndex, currentIndex, false))) {
             updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
             if (QQuickPathViewAttached *att = attached(currentItem))
                 att->setIsCurrentItem(true);
@@ -1691,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;
@@ -1719,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;
@@ -1748,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));
@@ -1757,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));