PathView was missing the maximumFlickVelocity property
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickpathview.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickpathview_p.h"
43 #include "qquickpathview_p_p.h"
44 #include "qquickcanvas.h"
45
46 #include <QtQuick/private/qquickstate_p.h>
47 #include <private/qqmlglobal_p.h>
48 #include <private/qqmlopenmetaobject_p.h>
49 #include <private/qlistmodelinterface_p.h>
50 #include <private/qquickchangeset_p.h>
51
52 #include <QtGui/qevent.h>
53 #include <QtGui/qevent.h>
54 #include <QtGui/qguiapplication.h>
55 #include <QtGui/qstylehints.h>
56 #include <QtCore/qmath.h>
57 #include <math.h>
58
59 // The number of samples to use in calculating the velocity of a flick
60 #ifndef QML_FLICK_SAMPLEBUFFER
61 #define QML_FLICK_SAMPLEBUFFER 3
62 #endif
63
64 // The number of samples to discard when calculating the flick velocity.
65 // Touch panels often produce inaccurate results as the finger is lifted.
66 #ifndef QML_FLICK_DISCARDSAMPLES
67 #define QML_FLICK_DISCARDSAMPLES 1
68 #endif
69
70 // The default maximum velocity of a flick.
71 #ifndef QML_FLICK_DEFAULTMAXVELOCITY
72 #define QML_FLICK_DEFAULTMAXVELOCITY 2500
73 #endif
74
75
76 QT_BEGIN_NAMESPACE
77
78 const qreal MinimumFlickVelocity = 75.0;
79
80 inline qreal qmlMod(qreal x, qreal y)
81 {
82 #ifdef QT_USE_MATH_H_FLOATS
83     if (sizeof(qreal) == sizeof(float))
84         return fmodf(float(x), float(y));
85     else
86 #endif
87         return fmod(x, y);
88 }
89
90 static QQmlOpenMetaObjectType *qPathViewAttachedType = 0;
91
92 QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
93 : QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
94 {
95     if (qPathViewAttachedType) {
96         m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
97         m_metaobject->setCached(true);
98     } else {
99         m_metaobject = new QQmlOpenMetaObject(this);
100     }
101 }
102
103 QQuickPathViewAttached::~QQuickPathViewAttached()
104 {
105 }
106
107 QVariant QQuickPathViewAttached::value(const QByteArray &name) const
108 {
109     return m_metaobject->value(name);
110 }
111 void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
112 {
113     m_metaobject->setValue(name, val);
114 }
115
116 QQuickPathViewPrivate::QQuickPathViewPrivate()
117   : path(0), currentIndex(0), currentItemOffset(0.0), startPc(0), lastDist(0)
118     , lastElapsed(0), offset(0.0), offsetAdj(0.0), mappedRange(1.0)
119     , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
120     , autoHighlight(true), highlightUp(false), layoutScheduled(false)
121     , moving(false), flicking(false), requestedOnPath(false), inRequest(false)
122     , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY)
123     , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
124     , firstIndex(-1), pathItems(-1), requestedIndex(-1), requestedZ(0)
125     , moveReason(Other), moveDirection(Shortest), attType(0), highlightComponent(0), highlightItem(0)
126     , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
127     , highlightPosition(0)
128     , highlightRangeStart(0), highlightRangeEnd(0)
129     , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
130     , highlightMoveDuration(300), modelCount(0)
131 {
132 }
133
134 void QQuickPathViewPrivate::init()
135 {
136     Q_Q(QQuickPathView);
137     offset = 0;
138     q->setAcceptedMouseButtons(Qt::LeftButton);
139     q->setFlag(QQuickItem::ItemIsFocusScope);
140     q->setFiltersChildMouseEvents(true);
141     FAST_CONNECT(&tl, SIGNAL(updated()), q, SLOT(ticked()))
142     lastPosTime.invalidate();
143     FAST_CONNECT(&tl, SIGNAL(completed()), q, SLOT(movementEnding()))
144 }
145
146 QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
147 {
148     Q_Q(QQuickPathView);
149     requestedIndex = modelIndex;
150     requestedOnPath = onPath;
151     requestedZ = z;
152     inRequest = true;
153     QQuickItem *item = model->item(modelIndex, false);
154     if (item) {
155         QQml_setParent_noEvent(item, q);
156         item->setParentItem(q);
157         requestedIndex = -1;
158         qPathViewAttachedType = attType;
159         QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
160         qPathViewAttachedType = 0;
161         if (att)
162             att->setOnPath(onPath);
163         QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
164         itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
165     }
166     inRequest = false;
167     return item;
168 }
169
170 void QQuickPathView::createdItem(int index, QQuickItem *item)
171 {
172     Q_D(QQuickPathView);
173     if (d->requestedIndex != index) {
174         qPathViewAttachedType = d->attachedType();
175         QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
176         qPathViewAttachedType = 0;
177         if (att) {
178             att->m_view = this;
179             att->setOnPath(false);
180         }
181         item->setParentItem(this);
182         QQml_setParent_noEvent(item, this);
183         d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
184     } else {
185         d->requestedIndex = -1;
186         if (!d->inRequest)
187             refill();
188     }
189 }
190
191 void QQuickPathView::initItem(int index, QQuickItem *item)
192 {
193     Q_D(QQuickPathView);
194     if (d->requestedIndex == index) {
195         item->setParentItem(this);
196         qPathViewAttachedType = d->attachedType();
197         QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
198         qPathViewAttachedType = 0;
199         if (att) {
200             att->m_view = this;
201             qreal percent = d->positionOfIndex(index);
202             foreach (const QString &attr, d->path->attributes())
203                 att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
204             item->setZ(d->requestedZ);
205             if (att)
206                 att->setOnPath(d->requestedOnPath);
207         }
208     }
209 }
210
211 void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
212 {
213     if (!item || !model)
214         return;
215     QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
216     itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
217     if (model->release(item) == 0) {
218         // item was not destroyed, and we no longer reference it.
219         if (QQuickPathViewAttached *att = attached(item))
220             att->setOnPath(false);
221     }
222 }
223
224 QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
225 {
226     return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
227 }
228
229 QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
230 {
231     Q_Q(QQuickPathView);
232     if (!attType) {
233         // pre-create one metatype to share with all attached objects
234         attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
235         foreach (const QString &attr, path->attributes())
236             attType->createProperty(attr.toUtf8());
237     }
238
239     return attType;
240 }
241
242 void QQuickPathViewPrivate::clear()
243 {
244     if (currentItem) {
245         releaseItem(currentItem);
246         currentItem = 0;
247     }
248     for (int i=0; i<items.count(); i++){
249         QQuickItem *p = items[i];
250         releaseItem(p);
251     }
252     items.clear();
253 }
254
255 void QQuickPathViewPrivate::updateMappedRange()
256 {
257     if (model && pathItems != -1 && pathItems < modelCount)
258         mappedRange = qreal(pathItems)/modelCount;
259     else
260         mappedRange = 1.0;
261 }
262
263 qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
264 {
265     qreal pos = -1.0;
266
267     if (model && index >= 0 && index < modelCount) {
268         qreal start = 0.0;
269         if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange)
270             start = highlightRangeStart;
271         qreal globalPos = index + offset;
272         globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
273         if (pathItems != -1 && pathItems < modelCount) {
274             globalPos += start * mappedRange;
275             globalPos = qmlMod(globalPos, 1.0);
276             if (globalPos < mappedRange)
277                 pos = globalPos / mappedRange;
278         } else {
279             pos = qmlMod(globalPos + start, 1.0);
280         }
281     }
282
283     return pos;
284 }
285
286 void QQuickPathViewPrivate::createHighlight()
287 {
288     Q_Q(QQuickPathView);
289     if (!q->isComponentComplete())
290         return;
291
292     bool changed = false;
293     if (highlightItem) {
294         highlightItem->setParentItem(0);
295         highlightItem->deleteLater();
296         highlightItem = 0;
297         changed = true;
298     }
299
300     QQuickItem *item = 0;
301     if (highlightComponent) {
302         QQmlContext *creationContext = highlightComponent->creationContext();
303         QQmlContext *highlightContext = new QQmlContext(
304                 creationContext ? creationContext : qmlContext(q));
305         QObject *nobj = highlightComponent->create(highlightContext);
306         if (nobj) {
307             QQml_setParent_noEvent(highlightContext, nobj);
308             item = qobject_cast<QQuickItem *>(nobj);
309             if (!item)
310                 delete nobj;
311         } else {
312             delete highlightContext;
313         }
314     } else {
315         item = new QQuickItem;
316     }
317     if (item) {
318         QQml_setParent_noEvent(item, q);
319         item->setParentItem(q);
320         highlightItem = item;
321         changed = true;
322     }
323     if (changed)
324         emit q->highlightItemChanged();
325 }
326
327 void QQuickPathViewPrivate::updateHighlight()
328 {
329     Q_Q(QQuickPathView);
330     if (!q->isComponentComplete() || !isValid())
331         return;
332     if (highlightItem) {
333         if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
334             updateItem(highlightItem, highlightRangeStart);
335         } else {
336             qreal target = currentIndex;
337
338             offsetAdj = 0.0;
339             tl.reset(moveHighlight);
340             moveHighlight.setValue(highlightPosition);
341
342             const int duration = highlightMoveDuration;
343
344             if (target - highlightPosition > modelCount/2) {
345                 highlightUp = false;
346                 qreal distance = modelCount - target + highlightPosition;
347                 tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
348                 tl.set(moveHighlight, modelCount-0.01);
349                 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
350             } else if (target - highlightPosition <= -modelCount/2) {
351                 highlightUp = true;
352                 qreal distance = modelCount - highlightPosition + target;
353                 tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
354                 tl.set(moveHighlight, 0.0);
355                 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
356             } else {
357                 highlightUp = highlightPosition - target < 0;
358                 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
359             }
360         }
361     }
362 }
363
364 void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
365 {
366     if (pos != highlightPosition) {
367         qreal start = 0.0;
368         qreal end = 1.0;
369         if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
370             start = highlightRangeStart;
371             end = highlightRangeEnd;
372         }
373
374         qreal range = qreal(modelCount);
375         // calc normalized position of highlight relative to offset
376         qreal relativeHighlight = qmlMod(pos + offset, range) / range;
377
378         if (!highlightUp && relativeHighlight > end * mappedRange) {
379             qreal diff = 1.0 - relativeHighlight;
380             setOffset(offset + diff * range);
381         } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) {
382             qreal diff = relativeHighlight - (end - start) * mappedRange;
383             setOffset(offset - diff * range - 0.00001);
384         }
385
386         highlightPosition = pos;
387         qreal pathPos = positionOfIndex(pos);
388         updateItem(highlightItem, pathPos);
389         if (QQuickPathViewAttached *att = attached(highlightItem))
390             att->setOnPath(pathPos != -1.0);
391     }
392 }
393
394 void QQuickPathView::pathUpdated()
395 {
396     Q_D(QQuickPathView);
397     QList<QQuickItem*>::iterator it = d->items.begin();
398     while (it != d->items.end()) {
399         QQuickItem *item = *it;
400         if (QQuickPathViewAttached *att = d->attached(item))
401             att->m_percent = -1;
402         ++it;
403     }
404     refill();
405 }
406
407 void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
408 {
409     if (QQuickPathViewAttached *att = attached(item)) {
410         if (qFuzzyCompare(att->m_percent, percent))
411             return;
412         att->m_percent = percent;
413         foreach (const QString &attr, path->attributes())
414             att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
415     }
416     QPointF pf = path->pointAt(percent);
417     item->setX(pf.x() - item->width()/2);
418     item->setY(pf.y() - item->height()/2);
419 }
420
421 void QQuickPathViewPrivate::regenerate()
422 {
423     Q_Q(QQuickPathView);
424     if (!q->isComponentComplete())
425         return;
426
427     clear();
428
429     if (!isValid())
430         return;
431
432     firstIndex = -1;
433     updateMappedRange();
434     q->refill();
435 }
436
437 /*!
438     \qmlclass PathView QQuickPathView
439     \inqmlmodule QtQuick 2
440     \ingroup qml-view-elements
441     \brief The PathView element lays out model-provided items on a path.
442     \inherits Item
443
444     A PathView displays data from models created from built-in QML elements like ListModel
445     and XmlListModel, or custom model classes defined in C++ that inherit from
446     QAbstractListModel.
447
448     The view has a \l model, which defines the data to be displayed, and
449     a \l delegate, which defines how the data should be displayed.
450     The \l delegate is instantiated for each item on the \l path.
451     The items may be flicked to move them along the path.
452
453     For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
454
455     \snippet doc/src/snippets/qml/pathview/ContactModel.qml 0
456
457     This data can be represented as a PathView, like this:
458
459     \snippet doc/src/snippets/qml/pathview/pathview.qml 0
460
461     \image pathview.gif
462
463     (Note the above example uses PathAttribute to scale and modify the
464     opacity of the items as they rotate. This additional code can be seen in the
465     PathAttribute documentation.)
466
467     PathView does not automatically handle keyboard navigation.  This is because
468     the keys to use for navigation will depend upon the shape of the path.  Navigation
469     can be added quite simply by setting \c focus to \c true and calling
470     \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
471     using the left and right arrow keys:
472
473     \qml
474     PathView {
475         // ...
476         focus: true
477         Keys.onLeftPressed: decrementCurrentIndex()
478         Keys.onRightPressed: incrementCurrentIndex()
479     }
480     \endqml
481
482     The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
483
484     Delegates are instantiated as needed and may be destroyed at any time.
485     State should \e never be stored in a delegate.
486
487     PathView attaches a number of properties to the root item of the delegate, for example
488     \c {PathView.isCurrentItem}.  In the following example, the root delegate item can access
489     this attached property directly as \c PathView.isCurrentItem, while the child
490     \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
491
492     \snippet doc/src/snippets/qml/pathview/pathview.qml 1
493
494     \b Note that views do not enable \e clip automatically.  If the view
495     is not clipped by another item or the screen, it will be necessary
496     to set \e {clip: true} in order to have the out of view items clipped
497     nicely.
498
499     \sa Path, {declarative/modelviews/pathview}{PathView example}
500 */
501
502 QQuickPathView::QQuickPathView(QQuickItem *parent)
503   : QQuickItem(*(new QQuickPathViewPrivate), parent)
504 {
505     Q_D(QQuickPathView);
506     d->init();
507 }
508
509 QQuickPathView::~QQuickPathView()
510 {
511     Q_D(QQuickPathView);
512     d->clear();
513     if (d->attType)
514         d->attType->release();
515     if (d->ownModel)
516         delete d->model;
517 }
518
519 /*!
520     \qmlattachedproperty PathView QtQuick2::PathView::view
521     This attached property holds the view that manages this delegate instance.
522
523     It is attached to each instance of the delegate.
524 */
525
526 /*!
527     \qmlattachedproperty bool QtQuick2::PathView::onPath
528     This attached property holds whether the item is currently on the path.
529
530     If a pathItemCount has been set, it is possible that some items may
531     be instantiated, but not considered to be currently on the path.
532     Usually, these items would be set invisible, for example:
533
534     \qml
535     Component {
536         Rectangle {
537             visible: PathView.onPath
538             // ...
539         }
540     }
541     \endqml
542
543     It is attached to each instance of the delegate.
544 */
545
546 /*!
547     \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
548     This attached property is true if this delegate is the current item; otherwise false.
549
550     It is attached to each instance of the delegate.
551
552     This property may be used to adjust the appearance of the current item.
553
554     \snippet doc/src/snippets/qml/pathview/pathview.qml 1
555 */
556
557 /*!
558     \qmlproperty model QtQuick2::PathView::model
559     This property holds the model providing data for the view.
560
561     The model provides a set of data that is used to create the items for the view.
562     For large or dynamic datasets the model is usually provided by a C++ model object.
563     Models can also be created directly in QML, using the ListModel element.
564
565     \sa {qmlmodels}{Data Models}
566 */
567 QVariant QQuickPathView::model() const
568 {
569     Q_D(const QQuickPathView);
570     return d->modelVariant;
571 }
572
573 void QQuickPathView::setModel(const QVariant &model)
574 {
575     Q_D(QQuickPathView);
576     if (d->modelVariant == model)
577         return;
578
579     if (d->model) {
580         disconnect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
581                 this, SLOT(modelUpdated(QQuickChangeSet,bool)));
582         disconnect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
583         disconnect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
584         for (int i=0; i<d->items.count(); i++){
585             QQuickItem *p = d->items[i];
586             d->releaseItem(p);
587         }
588         d->items.clear();
589     }
590
591     d->modelVariant = model;
592     QObject *object = qvariant_cast<QObject*>(model);
593     QQuickVisualModel *vim = 0;
594     if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
595         if (d->ownModel) {
596             delete d->model;
597             d->ownModel = false;
598         }
599         d->model = vim;
600     } else {
601         if (!d->ownModel) {
602             d->model = new QQuickVisualDataModel(qmlContext(this));
603             d->ownModel = true;
604             if (isComponentComplete())
605                 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
606         }
607         if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
608             dataModel->setModel(model);
609     }
610     d->modelCount = 0;
611     if (d->model) {
612         connect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
613                 this, SLOT(modelUpdated(QQuickChangeSet,bool)));
614         connect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
615         connect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
616         d->modelCount = d->model->count();
617         if (d->model->count())
618             d->offset = qmlMod(d->offset, qreal(d->model->count()));
619         if (d->offset < 0)
620             d->offset = d->model->count() + d->offset;
621 }
622     d->regenerate();
623     if (d->currentIndex < d->modelCount)
624         setOffset(qmlMod(d->modelCount - d->currentIndex, d->modelCount));
625     else
626         d->fixOffset();
627     emit countChanged();
628     emit modelChanged();
629 }
630
631 /*!
632     \qmlproperty int QtQuick2::PathView::count
633     This property holds the number of items in the model.
634 */
635 int QQuickPathView::count() const
636 {
637     Q_D(const QQuickPathView);
638     return d->model ? d->modelCount : 0;
639 }
640
641 /*!
642     \qmlproperty Path QtQuick2::PathView::path
643     This property holds the path used to lay out the items.
644     For more information see the \l Path documentation.
645 */
646 QQuickPath *QQuickPathView::path() const
647 {
648     Q_D(const QQuickPathView);
649     return d->path;
650 }
651
652 void QQuickPathView::setPath(QQuickPath *path)
653 {
654     Q_D(QQuickPathView);
655     if (d->path == path)
656         return;
657     if (d->path)
658         disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
659     d->path = path;
660     connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
661     if (d->isValid() && isComponentComplete()) {
662         d->clear();
663         if (d->attType) {
664             d->attType->release();
665             d->attType = 0;
666         }
667         d->regenerate();
668     }
669     emit pathChanged();
670 }
671
672 /*!
673     \qmlproperty int QtQuick2::PathView::currentIndex
674     This property holds the index of the current item.
675 */
676 int QQuickPathView::currentIndex() const
677 {
678     Q_D(const QQuickPathView);
679     return d->currentIndex;
680 }
681
682 void QQuickPathView::setCurrentIndex(int idx)
683 {
684     Q_D(QQuickPathView);
685     if (d->model && d->modelCount)
686         idx = qAbs(idx % d->modelCount);
687     if (d->model && (idx != d->currentIndex || !d->currentItem)) {
688         if (d->currentItem) {
689             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
690                 att->setIsCurrentItem(false);
691             d->releaseItem(d->currentItem);
692         }
693         int oldCurrentIdx = d->currentIndex;
694         QQuickItem *oldCurrentItem = d->currentItem;
695         d->currentItem = 0;
696         d->moveReason = QQuickPathViewPrivate::SetIndex;
697         d->currentIndex = idx;
698         if (d->modelCount) {
699             d->createCurrentItem();
700             if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
701                 d->snapToCurrent();
702             d->currentItemOffset = d->positionOfIndex(d->currentIndex);
703             d->updateHighlight();
704         }
705         if (oldCurrentIdx != d->currentIndex)
706             emit currentIndexChanged();
707         if (oldCurrentItem != d->currentItem)
708             emit currentItemChanged();
709     }
710 }
711
712 QQuickItem *QQuickPathView::currentItem() const
713 {
714     Q_D(const QQuickPathView);
715     return d->currentItem;
716 }
717
718 /*!
719     \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
720
721     Increments the current index.
722
723     \b Note: methods should only be called after the Component has completed.
724 */
725 void QQuickPathView::incrementCurrentIndex()
726 {
727     Q_D(QQuickPathView);
728     d->moveDirection = QQuickPathViewPrivate::Positive;
729     setCurrentIndex(currentIndex()+1);
730 }
731
732 /*!
733     \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
734
735     Decrements the current index.
736
737     \b Note: methods should only be called after the Component has completed.
738 */
739 void QQuickPathView::decrementCurrentIndex()
740 {
741     Q_D(QQuickPathView);
742     if (d->model && d->modelCount) {
743         int idx = currentIndex()-1;
744         if (idx < 0)
745             idx = d->modelCount - 1;
746         d->moveDirection = QQuickPathViewPrivate::Negative;
747         setCurrentIndex(idx);
748     }
749 }
750
751 /*!
752     \qmlproperty real QtQuick2::PathView::offset
753
754     The offset specifies how far along the path the items are from their initial positions.
755     This is a real number that ranges from 0.0 to the count of items in the model.
756 */
757 qreal QQuickPathView::offset() const
758 {
759     Q_D(const QQuickPathView);
760     return d->offset;
761 }
762
763 void QQuickPathView::setOffset(qreal offset)
764 {
765     Q_D(QQuickPathView);
766     d->setOffset(offset);
767     d->updateCurrent();
768 }
769
770 void QQuickPathViewPrivate::setOffset(qreal o)
771 {
772     Q_Q(QQuickPathView);
773     if (offset != o) {
774         if (isValid() && q->isComponentComplete()) {
775             offset = qmlMod(o, qreal(modelCount));
776             if (offset < 0)
777                 offset += qreal(modelCount);
778             q->refill();
779         } else {
780             offset = o;
781         }
782         emit q->offsetChanged();
783     }
784 }
785
786 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
787 {
788     setOffset(o+offsetAdj);
789 }
790
791 /*!
792     \qmlproperty Component QtQuick2::PathView::highlight
793     This property holds the component to use as the highlight.
794
795     An instance of the highlight component will be created for each view.
796     The geometry of the resultant component instance will be managed by the view
797     so as to stay with the current item.
798
799     The below example demonstrates how to make a simple highlight.  Note the use
800     of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
801     the highlight is hidden when flicked away from the path.
802
803     \qml
804     Component {
805         Rectangle {
806             visible: PathView.onPath
807             // ...
808         }
809     }
810     \endqml
811
812     \sa highlightItem, highlightRangeMode
813 */
814
815 QQmlComponent *QQuickPathView::highlight() const
816 {
817     Q_D(const QQuickPathView);
818     return d->highlightComponent;
819 }
820
821 void QQuickPathView::setHighlight(QQmlComponent *highlight)
822 {
823     Q_D(QQuickPathView);
824     if (highlight != d->highlightComponent) {
825         d->highlightComponent = highlight;
826         d->createHighlight();
827         d->updateHighlight();
828         emit highlightChanged();
829     }
830 }
831
832 /*!
833   \qmlproperty Item QtQuick2::PathView::highlightItem
834
835   \c highlightItem holds the highlight item, which was created
836   from the \l highlight component.
837
838   \sa highlight
839 */
840 QQuickItem *QQuickPathView::highlightItem()
841 {
842     Q_D(const QQuickPathView);
843     return d->highlightItem;
844 }
845 /*!
846     \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
847     \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
848     \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
849
850     These properties set the preferred range of the highlight (current item)
851     within the view.  The preferred values must be in the range 0.0-1.0.
852
853     If highlightRangeMode is set to \e PathView.NoHighlightRange
854
855     If highlightRangeMode is set to \e PathView.ApplyRange the view will
856     attempt to maintain the highlight within the range, however
857     the highlight can move outside of the range at the ends of the path
858     or due to a mouse interaction.
859
860     If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
861     move outside of the range.  This means that the current item will change
862     if a keyboard or mouse action would cause the highlight to move
863     outside of the range.
864
865     Note that this is the correct way to influence where the
866     current item ends up when the view moves. For example, if you want the
867     currently selected item to be in the middle of the path, then set the
868     highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
869     Then, when the path scrolls,
870     the currently selected item will be the item at that position. This also applies to
871     when the currently selected item changes - it will scroll to within the preferred
872     highlight range. Furthermore, the behaviour of the current item index will occur
873     whether or not a highlight exists.
874
875     The default value is \e PathView.StrictlyEnforceRange.
876
877     Note that a valid range requires preferredHighlightEnd to be greater
878     than or equal to preferredHighlightBegin.
879 */
880 qreal QQuickPathView::preferredHighlightBegin() const
881 {
882     Q_D(const QQuickPathView);
883     return d->highlightRangeStart;
884 }
885
886 void QQuickPathView::setPreferredHighlightBegin(qreal start)
887 {
888     Q_D(QQuickPathView);
889     if (d->highlightRangeStart == start || start < 0 || start > 1.0)
890         return;
891     d->highlightRangeStart = start;
892     d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
893     refill();
894     emit preferredHighlightBeginChanged();
895 }
896
897 qreal QQuickPathView::preferredHighlightEnd() const
898 {
899     Q_D(const QQuickPathView);
900     return d->highlightRangeEnd;
901 }
902
903 void QQuickPathView::setPreferredHighlightEnd(qreal end)
904 {
905     Q_D(QQuickPathView);
906     if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
907         return;
908     d->highlightRangeEnd = end;
909     d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
910     refill();
911     emit preferredHighlightEndChanged();
912 }
913
914 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
915 {
916     Q_D(const QQuickPathView);
917     return d->highlightRangeMode;
918 }
919
920 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
921 {
922     Q_D(QQuickPathView);
923     if (d->highlightRangeMode == mode)
924         return;
925     d->highlightRangeMode = mode;
926     d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
927     if (d->haveHighlightRange) {
928         d->regenerate();
929         d->snapToCurrent();
930     }
931     emit highlightRangeModeChanged();
932 }
933
934 /*!
935     \qmlproperty int QtQuick2::PathView::highlightMoveDuration
936     This property holds the move animation duration of the highlight delegate.
937
938     If the highlightRangeMode is StrictlyEnforceRange then this property
939     determines the speed that the items move along the path.
940
941     The default value for the duration is 300ms.
942 */
943 int QQuickPathView::highlightMoveDuration() const
944 {
945     Q_D(const QQuickPathView);
946     return d->highlightMoveDuration;
947 }
948
949 void QQuickPathView::setHighlightMoveDuration(int duration)
950 {
951     Q_D(QQuickPathView);
952     if (d->highlightMoveDuration == duration)
953         return;
954     d->highlightMoveDuration = duration;
955     emit highlightMoveDurationChanged();
956 }
957
958 /*!
959     \qmlproperty real QtQuick2::PathView::dragMargin
960     This property holds the maximum distance from the path that initiate mouse dragging.
961
962     By default the path can only be dragged by clicking on an item.  If
963     dragMargin is greater than zero, a drag can be initiated by clicking
964     within dragMargin pixels of the path.
965 */
966 qreal QQuickPathView::dragMargin() const
967 {
968     Q_D(const QQuickPathView);
969     return d->dragMargin;
970 }
971
972 void QQuickPathView::setDragMargin(qreal dragMargin)
973 {
974     Q_D(QQuickPathView);
975     if (d->dragMargin == dragMargin)
976         return;
977     d->dragMargin = dragMargin;
978     emit dragMarginChanged();
979 }
980
981 /*!
982     \qmlproperty real QtQuick2::PathView::flickDeceleration
983     This property holds the rate at which a flick will decelerate.
984
985     The default is 100.
986 */
987 qreal QQuickPathView::flickDeceleration() const
988 {
989     Q_D(const QQuickPathView);
990     return d->deceleration;
991 }
992
993 void QQuickPathView::setFlickDeceleration(qreal dec)
994 {
995     Q_D(QQuickPathView);
996     if (d->deceleration == dec)
997         return;
998     d->deceleration = dec;
999     emit flickDecelerationChanged();
1000 }
1001
1002 /*!
1003     \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1004     This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1005
1006     The default value is platform dependent.
1007 */
1008 qreal QQuickPathView::maximumFlickVelocity() const
1009 {
1010     Q_D(const QQuickPathView);
1011     return d->maximumFlickVelocity;
1012 }
1013
1014 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1015 {
1016     Q_D(QQuickPathView);
1017     if (vel == d->maximumFlickVelocity)
1018         return;
1019     d->maximumFlickVelocity = vel;
1020     emit maximumFlickVelocityChanged();
1021 }
1022
1023
1024 /*!
1025     \qmlproperty bool QtQuick2::PathView::interactive
1026
1027     A user cannot drag or flick a PathView that is not interactive.
1028
1029     This property is useful for temporarily disabling flicking. This allows
1030     special interaction with PathView's children.
1031 */
1032 bool QQuickPathView::isInteractive() const
1033 {
1034     Q_D(const QQuickPathView);
1035     return d->interactive;
1036 }
1037
1038 void QQuickPathView::setInteractive(bool interactive)
1039 {
1040     Q_D(QQuickPathView);
1041     if (interactive != d->interactive) {
1042         d->interactive = interactive;
1043         if (!interactive)
1044             d->tl.clear();
1045         emit interactiveChanged();
1046     }
1047 }
1048
1049 /*!
1050     \qmlproperty bool QtQuick2::PathView::moving
1051
1052     This property holds whether the view is currently moving
1053     due to the user either dragging or flicking the view.
1054 */
1055 bool QQuickPathView::isMoving() const
1056 {
1057     Q_D(const QQuickPathView);
1058     return d->moving;
1059 }
1060
1061 /*!
1062     \qmlproperty bool QtQuick2::PathView::flicking
1063
1064     This property holds whether the view is currently moving
1065     due to the user flicking the view.
1066 */
1067 bool QQuickPathView::isFlicking() const
1068 {
1069     Q_D(const QQuickPathView);
1070     return d->flicking;
1071 }
1072
1073 /*!
1074     \qmlsignal QtQuick2::PathView::onMovementStarted()
1075
1076     This handler is called when the view begins moving due to user
1077     interaction.
1078 */
1079
1080 /*!
1081     \qmlsignal QtQuick2::PathView::onMovementEnded()
1082
1083     This handler is called when the view stops moving due to user
1084     interaction.  If a flick was generated, this handler will
1085     be triggered once the flick stops.  If a flick was not
1086     generated, the handler will be triggered when the
1087     user stops dragging - i.e. a mouse or touch release.
1088 */
1089
1090 /*!
1091     \qmlsignal QtQuick2::PathView::onFlickStarted()
1092
1093     This handler is called when the view is flicked.  A flick
1094     starts from the point that the mouse or touch is released,
1095     while still in motion.
1096 */
1097
1098 /*!
1099     \qmlsignal QtQuick2::PathView::onFlickEnded()
1100
1101     This handler is called when the view stops moving due to a flick.
1102 */
1103
1104 /*!
1105     \qmlproperty Component QtQuick2::PathView::delegate
1106
1107     The delegate provides a template defining each item instantiated by the view.
1108     The index is exposed as an accessible \c index property.  Properties of the
1109     model are also available depending upon the type of \l {qmlmodels}{Data Model}.
1110
1111     The number of elements in the delegate has a direct effect on the
1112     flicking performance of the view when pathItemCount is specified.  If at all possible, place functionality
1113     that is not needed for the normal display of the delegate in a \l Loader which
1114     can load additional elements when needed.
1115
1116     Note that the PathView will layout the items based on the size of the root
1117     item in the delegate.
1118
1119     Here is an example delegate:
1120     \snippet doc/src/snippets/qml/pathview/pathview.qml 1
1121 */
1122 QQmlComponent *QQuickPathView::delegate() const
1123 {
1124     Q_D(const QQuickPathView);
1125      if (d->model) {
1126         if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1127             return dataModel->delegate();
1128     }
1129
1130     return 0;
1131 }
1132
1133 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1134 {
1135     Q_D(QQuickPathView);
1136     if (delegate == this->delegate())
1137         return;
1138     if (!d->ownModel) {
1139         d->model = new QQuickVisualDataModel(qmlContext(this));
1140         d->ownModel = true;
1141     }
1142     if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1143         int oldCount = dataModel->count();
1144         dataModel->setDelegate(delegate);
1145         d->modelCount = dataModel->count();
1146         d->regenerate();
1147         if (oldCount != dataModel->count())
1148             emit countChanged();
1149         emit delegateChanged();
1150     }
1151 }
1152
1153 /*!
1154   \qmlproperty int QtQuick2::PathView::pathItemCount
1155   This property holds the number of items visible on the path at any one time.
1156 */
1157 int QQuickPathView::pathItemCount() const
1158 {
1159     Q_D(const QQuickPathView);
1160     return d->pathItems;
1161 }
1162
1163 void QQuickPathView::setPathItemCount(int i)
1164 {
1165     Q_D(QQuickPathView);
1166     if (i == d->pathItems)
1167         return;
1168     if (i < 1)
1169         i = 1;
1170     d->pathItems = i;
1171     d->updateMappedRange();
1172     if (d->isValid() && isComponentComplete()) {
1173         d->regenerate();
1174     }
1175     emit pathItemCountChanged();
1176 }
1177
1178 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1179 {
1180     qreal samples = qMin(path->path().length()/5, qreal(500.0));
1181     qreal res = path->path().length()/samples;
1182
1183     qreal mindist = 1e10; // big number
1184     QPointF nearPoint = path->pointAt(0);
1185     qreal nearPc = 0;
1186
1187     // get rough pos
1188     for (qreal i=1; i < samples; i++) {
1189         QPointF pt = path->pointAt(i/samples);
1190         QPointF diff = pt - point;
1191         qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1192         if (dist < mindist) {
1193             nearPoint = pt;
1194             nearPc = i;
1195             mindist = dist;
1196         }
1197     }
1198
1199     // now refine
1200     qreal approxPc = nearPc;
1201     for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
1202         QPointF pt = path->pointAt(i/samples);
1203         QPointF diff = pt - point;
1204         qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1205         if (dist < mindist) {
1206             nearPoint = pt;
1207             nearPc = i;
1208             mindist = dist;
1209         }
1210     }
1211
1212     if (nearPercent)
1213         *nearPercent = nearPc / samples;
1214
1215     return nearPoint;
1216 }
1217
1218 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1219 {
1220     velocityBuffer.append(v);
1221     if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1222         velocityBuffer.remove(0);
1223 }
1224
1225 qreal QQuickPathViewPrivate::calcVelocity() const
1226 {
1227     qreal velocity = 0;
1228     if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1229         int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1230         for (int i = 0; i < count; ++i) {
1231             qreal v = velocityBuffer.at(i);
1232             velocity += v;
1233         }
1234         velocity /= count;
1235     }
1236     return velocity;
1237 }
1238
1239 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1240 {
1241     Q_D(QQuickPathView);
1242     if (d->interactive) {
1243         d->handleMousePressEvent(event);
1244         event->accept();
1245     } else {
1246         QQuickItem::mousePressEvent(event);
1247     }
1248 }
1249
1250 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1251 {
1252     Q_Q(QQuickPathView);
1253     if (!interactive || !items.count() || !model || !modelCount)
1254         return;
1255     velocityBuffer.clear();
1256     QPointF scenePoint = q->mapToScene(event->localPos());
1257     int idx = 0;
1258     for (; idx < items.count(); ++idx) {
1259         QRectF rect = items.at(idx)->boundingRect();
1260         rect = items.at(idx)->mapRectToScene(rect);
1261         if (rect.contains(scenePoint))
1262             break;
1263     }
1264     if (idx == items.count() && dragMargin == 0.)  // didn't click on an item
1265         return;
1266
1267     startPoint = pointNear(event->localPos(), &startPc);
1268     if (idx == items.count()) {
1269         qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y());
1270         if (distance > dragMargin)
1271             return;
1272     }
1273
1274
1275     if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1276         stealMouse = true; // If we've been flicked then steal the click.
1277     else
1278         stealMouse = false;
1279
1280     lastElapsed = 0;
1281     lastDist = 0;
1282     QQuickItemPrivate::start(lastPosTime);
1283     tl.clear();
1284 }
1285
1286 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1287 {
1288     Q_D(QQuickPathView);
1289     if (d->interactive) {
1290         d->handleMouseMoveEvent(event);
1291         if (d->stealMouse)
1292             setKeepMouseGrab(true);
1293         event->accept();
1294     } else {
1295         QQuickItem::mouseMoveEvent(event);
1296     }
1297 }
1298
1299 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1300 {
1301     Q_Q(QQuickPathView);
1302     if (!interactive || !lastPosTime.isValid() || !model || !modelCount)
1303         return;
1304
1305     qreal newPc;
1306     QPointF pathPoint = pointNear(event->localPos(), &newPc);
1307     if (!stealMouse) {
1308         QPointF delta = pathPoint - startPoint;
1309         if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1310             stealMouse = true;
1311             startPc = newPc;
1312         }
1313     }
1314
1315     if (stealMouse) {
1316         moveReason = QQuickPathViewPrivate::Mouse;
1317         qreal diff = (newPc - startPc)*modelCount*mappedRange;
1318         if (diff) {
1319             q->setOffset(offset + diff);
1320
1321             if (diff > modelCount/2)
1322                 diff -= modelCount;
1323             else if (diff < -modelCount/2)
1324                 diff += modelCount;
1325
1326             lastElapsed = QQuickItemPrivate::restart(lastPosTime);
1327             lastDist = diff;
1328             startPc = newPc;
1329             addVelocitySample(diff / (qreal(lastElapsed) / 1000.));
1330         }
1331         if (!moving) {
1332             moving = true;
1333             emit q->movingChanged();
1334             emit q->movementStarted();
1335         }
1336     }
1337 }
1338
1339 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1340 {
1341     Q_D(QQuickPathView);
1342     if (d->interactive) {
1343         d->handleMouseReleaseEvent(event);
1344         event->accept();
1345         ungrabMouse();
1346     } else {
1347         QQuickItem::mouseReleaseEvent(event);
1348     }
1349 }
1350
1351 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1352 {
1353     Q_Q(QQuickPathView);
1354     stealMouse = false;
1355     q->setKeepMouseGrab(false);
1356     if (!interactive || !lastPosTime.isValid() || !model || !modelCount) {
1357         lastPosTime.invalidate();
1358         if (!tl.isActive())
1359             q->movementEnding();
1360         return;
1361     }
1362
1363     qreal velocity = calcVelocity();
1364     qreal count = modelCount*mappedRange;
1365     qreal pixelVelocity = (path->path().length()/count) * velocity;
1366     if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
1367         if (qAbs(pixelVelocity) > maximumFlickVelocity) {
1368             // limit velocity
1369             qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1370             velocity = maxVel / (path->path().length()/count);
1371         }
1372         // Calculate the distance to be travelled
1373         qreal v2 = velocity*velocity;
1374         qreal accel = deceleration/10;
1375         qreal dist = 0;
1376         if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1377             // + 0.25 to encourage moving at least one item in the flick direction
1378             dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1379             // round to nearest item.
1380             if (velocity > 0.)
1381                 dist = qRound(dist + offset) - offset;
1382             else
1383                 dist = qRound(dist - offset) + offset;
1384             // Calculate accel required to stop on item boundary
1385             if (dist <= 0.) {
1386                 dist = 0.;
1387                 accel = 0.;
1388             } else {
1389                 accel = v2 / (2.0f * qAbs(dist));
1390             }
1391         } else {
1392             dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1393         }
1394         flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1395         offsetAdj = 0.0;
1396         moveOffset.setValue(offset);
1397         tl.accel(moveOffset, velocity, accel, dist);
1398         tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1399         if (!flicking) {
1400             flicking = true;
1401             emit q->flickingChanged();
1402             emit q->flickStarted();
1403         }
1404     } else {
1405         fixOffset();
1406     }
1407
1408     lastPosTime.invalidate();
1409     if (!tl.isActive())
1410         q->movementEnding();
1411 }
1412
1413 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1414 {
1415     Q_D(QQuickPathView);
1416     QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
1417     QQuickCanvas *c = canvas();
1418     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
1419     bool stealThisEvent = d->stealMouse;
1420     if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
1421         QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
1422                                event->button(), event->buttons(), event->modifiers());
1423         mouseEvent.setAccepted(false);
1424
1425         switch (mouseEvent.type()) {
1426         case QEvent::MouseMove:
1427             d->handleMouseMoveEvent(&mouseEvent);
1428             break;
1429         case QEvent::MouseButtonPress:
1430             d->handleMousePressEvent(&mouseEvent);
1431             stealThisEvent = d->stealMouse;   // Update stealThisEvent in case changed by function call above
1432             break;
1433         case QEvent::MouseButtonRelease:
1434             d->handleMouseReleaseEvent(&mouseEvent);
1435             break;
1436         default:
1437             break;
1438         }
1439         grabber = c->mouseGrabberItem();
1440         if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1441             grabMouse();
1442
1443         return d->stealMouse;
1444     } else if (d->lastPosTime.isValid()) {
1445         d->lastPosTime.invalidate();
1446         d->fixOffset();
1447     }
1448     if (event->type() == QEvent::MouseButtonRelease)
1449         d->stealMouse = false;
1450     return false;
1451 }
1452
1453 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1454 {
1455     Q_D(QQuickPathView);
1456     if (!isVisible() || !d->interactive)
1457         return QQuickItem::childMouseEventFilter(i, e);
1458
1459     switch (e->type()) {
1460     case QEvent::MouseButtonPress:
1461     case QEvent::MouseMove:
1462     case QEvent::MouseButtonRelease:
1463         return sendMouseEvent(static_cast<QMouseEvent *>(e));
1464     default:
1465         break;
1466     }
1467
1468     return QQuickItem::childMouseEventFilter(i, e);
1469 }
1470
1471 void QQuickPathView::mouseUngrabEvent()
1472 {
1473     Q_D(QQuickPathView);
1474     if (d->stealMouse) {
1475         // if our mouse grab has been removed (probably by a Flickable),
1476         // fix our state
1477         d->stealMouse = false;
1478         setKeepMouseGrab(false);
1479         d->lastPosTime.invalidate();
1480         d->fixOffset();
1481         if (!d->tl.isActive())
1482             movementEnding();
1483     }
1484 }
1485
1486 void QQuickPathView::updatePolish()
1487 {
1488     QQuickItem::updatePolish();
1489     refill();
1490 }
1491
1492 void QQuickPathView::componentComplete()
1493 {
1494     Q_D(QQuickPathView);
1495     if (d->model && d->ownModel)
1496         static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1497
1498     QQuickItem::componentComplete();
1499
1500     d->createHighlight();
1501     // It is possible that a refill has already happended to to Path
1502     // bindings being handled in the componentComplete().  If so
1503     // don't do it again.
1504     if (d->items.count() == 0 && d->model) {
1505         d->modelCount = d->model->count();
1506         d->regenerate();
1507     }
1508     d->updateHighlight();
1509
1510     if (d->modelCount)
1511         emit countChanged();
1512 }
1513
1514 void QQuickPathView::refill()
1515 {
1516     Q_D(QQuickPathView);
1517     if (!d->isValid() || !isComponentComplete())
1518         return;
1519
1520     d->layoutScheduled = false;
1521     bool currentVisible = false;
1522
1523     // first move existing items and remove items off path
1524     int idx = d->firstIndex;
1525     QList<QQuickItem*>::iterator it = d->items.begin();
1526     while (it != d->items.end()) {
1527         qreal pos = d->positionOfIndex(idx);
1528         QQuickItem *item = *it;
1529         if (pos >= 0.0) {
1530             d->updateItem(item, pos);
1531             if (idx == d->currentIndex) {
1532                 currentVisible = true;
1533                 d->currentItemOffset = pos;
1534             }
1535             ++it;
1536         } else {
1537             // qDebug() << "release";
1538             d->updateItem(item, 1.0);
1539             d->releaseItem(item);
1540             if (it == d->items.begin()) {
1541                 if (++d->firstIndex >= d->modelCount)
1542                     d->firstIndex = 0;
1543             }
1544             it = d->items.erase(it);
1545         }
1546         ++idx;
1547         if (idx >= d->modelCount)
1548             idx = 0;
1549     }
1550     if (!d->items.count())
1551         d->firstIndex = -1;
1552
1553     bool waiting = false;
1554     if (d->modelCount) {
1555         // add items to beginning and end
1556         int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1557         if (d->items.count() < count) {
1558             int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1559             qreal startPos = 0.0;
1560             if (d->haveHighlightRange && d->highlightRangeMode != QQuickPathView::NoHighlightRange)
1561                 startPos = d->highlightRangeStart;
1562             if (d->firstIndex >= 0) {
1563                 startPos = d->positionOfIndex(d->firstIndex);
1564                 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1565             }
1566             qreal pos = d->positionOfIndex(idx);
1567             while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1568 //                qDebug() << "append" << idx;
1569                 QQuickItem *item = d->getItem(idx, idx+1);
1570                 if (!item) {
1571                     waiting = true;
1572                     break;
1573                 }
1574                 if (d->currentIndex == idx) {
1575                     currentVisible = true;
1576                     d->currentItemOffset = pos;
1577                 }
1578                 if (d->items.count() == 0)
1579                     d->firstIndex = idx;
1580                 d->items.append(item);
1581                 d->updateItem(item, pos);
1582                 ++idx;
1583                 if (idx >= d->modelCount)
1584                     idx = 0;
1585                 pos = d->positionOfIndex(idx);
1586             }
1587
1588             idx = d->firstIndex - 1;
1589             if (idx < 0)
1590                 idx = d->modelCount - 1;
1591             pos = d->positionOfIndex(idx);
1592             while (!waiting && (pos >= 0.0 && pos < startPos) && d->items.count() < count) {
1593 //                 qDebug() << "prepend" << idx;
1594                 QQuickItem *item = d->getItem(idx, idx+1);
1595                 if (!item) {
1596                     waiting = true;
1597                     break;
1598                 }
1599                 if (d->currentIndex == idx) {
1600                     currentVisible = true;
1601                     d->currentItemOffset = pos;
1602                 }
1603                 d->items.prepend(item);
1604                 d->updateItem(item, pos);
1605                 d->firstIndex = idx;
1606                 idx = d->firstIndex - 1;
1607                 if (idx < 0)
1608                     idx = d->modelCount - 1;
1609                 pos = d->positionOfIndex(idx);
1610             }
1611         }
1612     }
1613
1614     if (!currentVisible) {
1615         d->currentItemOffset = 1.0;
1616         if (d->currentItem) {
1617             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1618                 att->setOnPath(false);
1619         } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
1620             if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
1621                 d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
1622                 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1623                     att->setIsCurrentItem(true);
1624             }
1625         }
1626     } else if (!waiting && !d->currentItem) {
1627         if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
1628             d->currentItem->setFocus(true);
1629             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1630                 att->setIsCurrentItem(true);
1631         }
1632     }
1633
1634     if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1635         d->updateItem(d->highlightItem, d->highlightRangeStart);
1636         if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1637             att->setOnPath(true);
1638     } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
1639         d->updateItem(d->highlightItem, d->currentItemOffset);
1640         if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1641             att->setOnPath(currentVisible);
1642     }
1643     while (d->itemCache.count())
1644         d->releaseItem(d->itemCache.takeLast());
1645 }
1646
1647 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1648 {
1649     Q_D(QQuickPathView);
1650     if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1651         return;
1652
1653     if (reset) {
1654         d->modelCount = d->model->count();
1655         d->regenerate();
1656         emit countChanged();
1657         return;
1658     }
1659
1660     if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1661         return;
1662
1663     const int modelCount = d->modelCount;
1664     int moveId = -1;
1665     int moveOffset;
1666     bool currentChanged = false;
1667     bool changedOffset = false;
1668     foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
1669         if (moveId == -1 && d->currentIndex >= r.index + r.count) {
1670             d->currentIndex -= r.count;
1671             currentChanged = true;
1672         } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
1673             // current item has been removed.
1674             d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
1675             if (r.isMove()) {
1676                 moveId = r.moveId;
1677                 moveOffset = d->currentIndex - r.index;
1678             } else if (d->currentItem) {
1679                 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1680                     att->setIsCurrentItem(true);
1681                 d->releaseItem(d->currentItem);
1682                 d->currentItem = 0;
1683             }
1684             currentChanged = true;
1685         }
1686
1687         if (r.index > d->currentIndex) {
1688             changedOffset = true;
1689             d->offset -= r.count;
1690             d->offsetAdj -= r.count;
1691         }
1692         d->modelCount -= r.count;
1693     }
1694     foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
1695         if (d->modelCount) {
1696             if (moveId == -1 && i.index <= d->currentIndex) {
1697                 d->currentIndex += i.count;
1698                 currentChanged = true;
1699             } else {
1700                 if (moveId != -1 && moveId == i.moveId) {
1701                     d->currentIndex = i.index + moveOffset;
1702                     currentChanged = true;
1703                 }
1704                 if (i.index > d->currentIndex) {
1705                     d->offset += i.count;
1706                     d->offsetAdj += i.count;
1707                     changedOffset = true;
1708                 }
1709             }
1710         }
1711         d->modelCount += i.count;
1712     }
1713
1714     d->offset = qmlMod(d->offset, d->modelCount);
1715     if (d->offset < 0)
1716         d->offset += d->modelCount;
1717
1718     d->itemCache += d->items;
1719     d->items.clear();
1720
1721     if (!d->modelCount) {
1722         while (d->itemCache.count())
1723             d->releaseItem(d->itemCache.takeLast());
1724         d->offset = 0;
1725         changedOffset = true;
1726         d->tl.reset(d->moveOffset);
1727     } else {
1728         if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1729             d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
1730             changedOffset = true;
1731         }
1732         d->firstIndex = -1;
1733         d->updateMappedRange();
1734         d->scheduleLayout();
1735     }
1736     if (changedOffset)
1737         emit offsetChanged();
1738     if (currentChanged)
1739         emit currentIndexChanged();
1740     if (d->modelCount != modelCount)
1741         emit countChanged();
1742 }
1743
1744 void QQuickPathView::destroyingItem(QQuickItem *item)
1745 {
1746     Q_UNUSED(item);
1747 }
1748
1749 void QQuickPathView::ticked()
1750 {
1751     Q_D(QQuickPathView);
1752     d->updateCurrent();
1753 }
1754
1755 void QQuickPathView::movementEnding()
1756 {
1757     Q_D(QQuickPathView);
1758     if (d->flicking) {
1759         d->flicking = false;
1760         emit flickingChanged();
1761         emit flickEnded();
1762     }
1763     if (d->moving && !d->stealMouse) {
1764         d->moving = false;
1765         emit movingChanged();
1766         emit movementEnded();
1767     }
1768 }
1769
1770 // find the item closest to the snap position
1771 int QQuickPathViewPrivate::calcCurrentIndex()
1772 {
1773     int current = -1;
1774     if (modelCount && model && items.count()) {
1775         offset = qmlMod(offset, modelCount);
1776         if (offset < 0)
1777             offset += modelCount;
1778         current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1779         current = current % modelCount;
1780     }
1781
1782     return current;
1783 }
1784
1785 void QQuickPathViewPrivate::createCurrentItem()
1786 {
1787     if (requestedIndex != -1)
1788         return;
1789     int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
1790     if (itemIndex < items.count()) {
1791         if ((currentItem = getItem(currentIndex, currentIndex, true))) {
1792             currentItem->setFocus(true);
1793             if (QQuickPathViewAttached *att = attached(currentItem))
1794                 att->setIsCurrentItem(true);
1795         }
1796     } else if (currentIndex >= 0 && currentIndex < modelCount) {
1797         if ((currentItem = getItem(currentIndex, currentIndex, false))) {
1798             updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
1799             if (QQuickPathViewAttached *att = attached(currentItem))
1800                 att->setIsCurrentItem(true);
1801         }
1802     }
1803 }
1804
1805 void QQuickPathViewPrivate::updateCurrent()
1806 {
1807     Q_Q(QQuickPathView);
1808     if (moveReason != Mouse)
1809         return;
1810     if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
1811         return;
1812
1813     int idx = calcCurrentIndex();
1814     if (model && idx != currentIndex) {
1815         if (currentItem) {
1816             if (QQuickPathViewAttached *att = attached(currentItem))
1817                 att->setIsCurrentItem(false);
1818             releaseItem(currentItem);
1819         }
1820         currentIndex = idx;
1821         currentItem = 0;
1822         createCurrentItem();
1823         emit q->currentIndexChanged();
1824     }
1825 }
1826
1827 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
1828 {
1829     ((QQuickPathViewPrivate *)d)->fixOffset();
1830 }
1831
1832 void QQuickPathViewPrivate::fixOffset()
1833 {
1834     Q_Q(QQuickPathView);
1835     if (model && items.count()) {
1836         if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1837             int curr = calcCurrentIndex();
1838             if (curr != currentIndex)
1839                 q->setCurrentIndex(curr);
1840             else
1841                 snapToCurrent();
1842         }
1843     }
1844 }
1845
1846 void QQuickPathViewPrivate::snapToCurrent()
1847 {
1848     if (!model || modelCount <= 0)
1849         return;
1850
1851     qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
1852
1853     if (offset == targetOffset)
1854         return;
1855
1856     moveReason = Other;
1857     offsetAdj = 0.0;
1858     tl.reset(moveOffset);
1859     moveOffset.setValue(offset);
1860
1861     const int duration = highlightMoveDuration;
1862
1863     if (!duration) {
1864         tl.set(moveOffset, targetOffset);
1865     } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
1866         qreal distance = modelCount - targetOffset + offset;
1867         if (targetOffset > moveOffset) {
1868             tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
1869             tl.set(moveOffset, modelCount);
1870             tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
1871         } else {
1872             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1873         }
1874     } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
1875         qreal distance = modelCount - offset + targetOffset;
1876         if (targetOffset < moveOffset) {
1877             tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
1878             tl.set(moveOffset, 0.0);
1879             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
1880         } else {
1881             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1882         }
1883     } else {
1884         tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1885     }
1886     moveDirection = Shortest;
1887 }
1888
1889 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
1890 {
1891     return new QQuickPathViewAttached(obj);
1892 }
1893
1894 QT_END_NAMESPACE
1895