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