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