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