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