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