Merge branch 'master' into refactor
[profile/ivi/qtdeclarative.git] / src / qtquick1 / 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 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
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/declarative/pathview/ContactModel.qml 0
369
370     This data can be represented as a PathView, like this:
371
372     \snippet doc/src/snippets/declarative/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/declarative/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/declarative/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/declarative/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     }
1289     if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease)
1290         d->stealMouse = false;
1291     return false;
1292 }
1293
1294 bool QDeclarative1PathView::sceneEventFilter(QGraphicsItem *i, QEvent *e)
1295 {
1296     Q_D(QDeclarative1PathView);
1297     if (!isVisible() || !d->interactive)
1298         return QDeclarativeItem::sceneEventFilter(i, e);
1299
1300     switch (e->type()) {
1301     case QEvent::GraphicsSceneMousePress:
1302     case QEvent::GraphicsSceneMouseMove:
1303     case QEvent::GraphicsSceneMouseRelease:
1304         return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e));
1305     default:
1306         break;
1307     }
1308
1309     return QDeclarativeItem::sceneEventFilter(i, e);
1310 }
1311
1312 bool QDeclarative1PathView::event(QEvent *event)
1313 {
1314     if (event->type() == QEvent::User) {
1315         refill();
1316         return true;
1317     }
1318
1319     return QDeclarativeItem::event(event);
1320 }
1321
1322 void QDeclarative1PathView::componentComplete()
1323 {
1324     Q_D(QDeclarative1PathView);
1325     QDeclarativeItem::componentComplete();
1326     d->createHighlight();
1327     // It is possible that a refill has already happended to to Path
1328     // bindings being handled in the componentComplete().  If so
1329     // don't do it again.
1330     if (d->items.count() == 0 && d->model) {
1331         d->modelCount = d->model->count();
1332         d->regenerate();
1333     }
1334     d->updateHighlight();
1335 }
1336
1337 void QDeclarative1PathView::refill()
1338 {
1339     Q_D(QDeclarative1PathView);
1340     if (!d->isValid() || !isComponentComplete())
1341         return;
1342
1343     d->layoutScheduled = false;
1344     bool currentVisible = false;
1345
1346     // first move existing items and remove items off path
1347     int idx = d->firstIndex;
1348     QList<QDeclarativeItem*>::iterator it = d->items.begin();
1349     while (it != d->items.end()) {
1350         qreal pos = d->positionOfIndex(idx);
1351         QDeclarativeItem *item = *it;
1352         if (pos >= 0.0) {
1353             d->updateItem(item, pos);
1354             if (idx == d->currentIndex) {
1355                 currentVisible = true;
1356                 d->currentItemOffset = pos;
1357             }
1358             ++it;
1359         } else {
1360 //            qDebug() << "release";
1361             d->updateItem(item, 1.0);
1362             d->releaseItem(item);
1363             if (it == d->items.begin()) {
1364                 if (++d->firstIndex >= d->modelCount)
1365                     d->firstIndex = 0;
1366             }
1367             it = d->items.erase(it);
1368         }
1369         ++idx;
1370         if (idx >= d->modelCount)
1371             idx = 0;
1372     }
1373     if (!d->items.count())
1374         d->firstIndex = -1;
1375
1376     if (d->modelCount) {
1377         // add items to beginning and end
1378         int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1379         if (d->items.count() < count) {
1380             int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1381             qreal startPos = 0.0;
1382             if (d->haveHighlightRange && d->highlightRangeMode != QDeclarative1PathView::NoHighlightRange)
1383                 startPos = d->highlightRangeStart;
1384             if (d->firstIndex >= 0) {
1385                 startPos = d->positionOfIndex(d->firstIndex);
1386                 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1387             }
1388             qreal pos = d->positionOfIndex(idx);
1389             while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1390     //            qDebug() << "append" << idx;
1391                 QDeclarativeItem *item = d->getItem(idx);
1392                 if (d->model->completePending())
1393                     item->setZValue(idx+1);
1394                 if (d->currentIndex == idx) {
1395                     item->setFocus(true);
1396                     if (QDeclarative1PathViewAttached *att = d->attached(item))
1397                         att->setIsCurrentItem(true);
1398                     currentVisible = true;
1399                     d->currentItemOffset = pos;
1400                     d->currentItem = item;
1401                 }
1402                 if (d->items.count() == 0)
1403                     d->firstIndex = idx;
1404                 d->items.append(item);
1405                 d->updateItem(item, pos);
1406                 if (d->model->completePending())
1407                     d->model->completeItem();
1408                 ++idx;
1409                 if (idx >= d->modelCount)
1410                     idx = 0;
1411                 pos = d->positionOfIndex(idx);
1412             }
1413
1414             idx = d->firstIndex - 1;
1415             if (idx < 0)
1416                 idx = d->modelCount - 1;
1417             pos = d->positionOfIndex(idx);
1418             while (pos >= 0.0 && pos < startPos) {
1419     //            qDebug() << "prepend" << idx;
1420                 QDeclarativeItem *item = d->getItem(idx);
1421                 if (d->model->completePending())
1422                     item->setZValue(idx+1);
1423                 if (d->currentIndex == idx) {
1424                     item->setFocus(true);
1425                     if (QDeclarative1PathViewAttached *att = d->attached(item))
1426                         att->setIsCurrentItem(true);
1427                     currentVisible = true;
1428                     d->currentItemOffset = pos;
1429                     d->currentItem = item;
1430                 }
1431                 d->items.prepend(item);
1432                 d->updateItem(item, pos);
1433                 if (d->model->completePending())
1434                     d->model->completeItem();
1435                 d->firstIndex = idx;
1436                 idx = d->firstIndex - 1;
1437                 if (idx < 0)
1438                     idx = d->modelCount - 1;
1439                 pos = d->positionOfIndex(idx);
1440             }
1441         }
1442     }
1443
1444     if (!currentVisible)
1445         d->currentItemOffset = 1.0;
1446
1447     if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QDeclarative1PathView::StrictlyEnforceRange) {
1448         d->updateItem(d->highlightItem, d->highlightRangeStart);
1449         if (QDeclarative1PathViewAttached *att = d->attached(d->highlightItem))
1450             att->setOnPath(true);
1451     } else if (d->highlightItem && d->moveReason != QDeclarative1PathViewPrivate::SetIndex) {
1452         d->updateItem(d->highlightItem, d->currentItemOffset);
1453         if (QDeclarative1PathViewAttached *att = d->attached(d->highlightItem))
1454             att->setOnPath(currentVisible);
1455     }
1456     while (d->itemCache.count())
1457         d->releaseItem(d->itemCache.takeLast());
1458 }
1459
1460 void QDeclarative1PathView::itemsInserted(int modelIndex, int count)
1461 {
1462     //XXX support animated insertion
1463     Q_D(QDeclarative1PathView);
1464     if (!d->isValid() || !isComponentComplete())
1465         return;
1466
1467     if (d->modelCount) {
1468         d->itemCache += d->items;
1469         d->items.clear();
1470         if (modelIndex <= d->currentIndex) {
1471             d->currentIndex += count;
1472             emit currentIndexChanged();
1473         } else if (d->offset != 0) {
1474             d->offset += count;
1475             d->offsetAdj += count;
1476         }
1477     }
1478     d->modelCount += count;
1479     if (d->flicking || d->moving) {
1480         d->regenerate();
1481         d->updateCurrent();
1482     } else {
1483         d->firstIndex = -1;
1484         d->updateMappedRange();
1485         d->scheduleLayout();
1486     }
1487     emit countChanged();
1488 }
1489
1490 void QDeclarative1PathView::itemsRemoved(int modelIndex, int count)
1491 {
1492     //XXX support animated removal
1493     Q_D(QDeclarative1PathView);
1494     if (!d->model || !d->modelCount || !d->model->isValid() || !d->path || !isComponentComplete())
1495         return;
1496
1497     // fix current
1498     bool currentChanged = false;
1499     if (d->currentIndex >= modelIndex + count) {
1500         d->currentIndex -= count;
1501         currentChanged = true;
1502     } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) {
1503         // current item has been removed.
1504         d->currentIndex = qMin(modelIndex, d->modelCount-count-1);
1505         if (d->currentItem) {
1506             if (QDeclarative1PathViewAttached *att = d->attached(d->currentItem))
1507                 att->setIsCurrentItem(true);
1508         }
1509         currentChanged = true;
1510     }
1511
1512     d->itemCache += d->items;
1513     d->items.clear();
1514
1515     bool changedOffset = false;
1516     if (modelIndex > d->currentIndex) {
1517         if (d->offset >= count) {
1518             changedOffset = true;
1519             d->offset -= count;
1520             d->offsetAdj -= count;
1521         }
1522     }
1523
1524     d->modelCount -= count;
1525     if (!d->modelCount) {
1526         while (d->itemCache.count())
1527             d->releaseItem(d->itemCache.takeLast());
1528         d->offset = 0;
1529         changedOffset = true;
1530         d->tl.reset(d->moveOffset);
1531         update();
1532     } else {
1533         d->regenerate();
1534         d->updateCurrent();
1535         if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QDeclarative1PathView::StrictlyEnforceRange)
1536             d->snapToCurrent();
1537     }
1538     if (changedOffset)
1539         emit offsetChanged();
1540     if (currentChanged)
1541         emit currentIndexChanged();
1542     emit countChanged();
1543 }
1544
1545 void QDeclarative1PathView::itemsMoved(int /*from*/, int /*to*/, int /*count*/)
1546 {
1547     Q_D(QDeclarative1PathView);
1548     if (!d->isValid() || !isComponentComplete())
1549         return;
1550
1551     QList<QDeclarativeItem *> removedItems = d->items;
1552     d->items.clear();
1553     d->regenerate();
1554     while (removedItems.count())
1555         d->releaseItem(removedItems.takeLast());
1556
1557     // Fix current index
1558     if (d->currentIndex >= 0 && d->currentItem) {
1559         int oldCurrent = d->currentIndex;
1560         d->currentIndex = d->model->indexOf(d->currentItem, this);
1561         if (oldCurrent != d->currentIndex)
1562             emit currentIndexChanged();
1563     }
1564     d->updateCurrent();
1565 }
1566
1567 void QDeclarative1PathView::modelReset()
1568 {
1569     Q_D(QDeclarative1PathView);
1570     d->modelCount = d->model->count();
1571     d->regenerate();
1572     emit countChanged();
1573 }
1574
1575 void QDeclarative1PathView::createdItem(int index, QDeclarativeItem *item)
1576 {
1577     Q_D(QDeclarative1PathView);
1578     if (d->requestedIndex != index) {
1579         if (!d->attType) {
1580             // pre-create one metatype to share with all attached objects
1581             d->attType = new QDeclarative1OpenMetaObjectType(&QDeclarative1PathViewAttached::staticMetaObject, qmlEngine(this));
1582             foreach(const QString &attr, d->path->attributes())
1583                 d->attType->createProperty(attr.toUtf8());
1584         }
1585         qPathViewAttachedType = d->attType;
1586         QDeclarative1PathViewAttached *att = static_cast<QDeclarative1PathViewAttached *>(qmlAttachedPropertiesObject<QDeclarative1PathView>(item));
1587         qPathViewAttachedType = 0;
1588         if (att) {
1589             att->m_view = this;
1590             att->setOnPath(false);
1591         }
1592         item->setParentItem(this);
1593         d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
1594     }
1595 }
1596
1597 void QDeclarative1PathView::destroyingItem(QDeclarativeItem *item)
1598 {
1599     Q_UNUSED(item);
1600 }
1601
1602 void QDeclarative1PathView::ticked()
1603 {
1604     Q_D(QDeclarative1PathView);
1605     d->updateCurrent();
1606 }
1607
1608 void QDeclarative1PathView::movementEnding()
1609 {
1610     Q_D(QDeclarative1PathView);
1611     if (d->flicking) {
1612         d->flicking = false;
1613         emit flickingChanged();
1614         emit flickEnded();
1615     }
1616     if (d->moving && !d->stealMouse) {
1617         d->moving = false;
1618         emit movingChanged();
1619         emit movementEnded();
1620     }
1621 }
1622
1623 // find the item closest to the snap position
1624 int QDeclarative1PathViewPrivate::calcCurrentIndex()
1625 {
1626     int current = -1;
1627     if (modelCount && model && items.count()) {
1628         offset = qmlMod(offset, modelCount);
1629         if (offset < 0)
1630             offset += modelCount;
1631         current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1632         current = current % modelCount;
1633     }
1634
1635     return current;
1636 }
1637
1638 void QDeclarative1PathViewPrivate::updateCurrent()
1639 {
1640     Q_Q(QDeclarative1PathView);
1641     if (moveReason != Mouse)
1642         return;
1643     if (!modelCount || !haveHighlightRange || highlightRangeMode != QDeclarative1PathView::StrictlyEnforceRange)
1644         return;
1645
1646     int idx = calcCurrentIndex();
1647     if (model && idx != currentIndex) {
1648         int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
1649         if (itemIndex < items.count()) {
1650             if (QDeclarativeItem *item = items.at(itemIndex)) {
1651                 if (QDeclarative1PathViewAttached *att = attached(item))
1652                     att->setIsCurrentItem(false);
1653             }
1654         }
1655         currentIndex = idx;
1656         currentItem = 0;
1657         itemIndex = (idx - firstIndex + modelCount) % modelCount;
1658         if (itemIndex < items.count()) {
1659             currentItem = items.at(itemIndex);
1660             currentItem->setFocus(true);
1661             if (QDeclarative1PathViewAttached *att = attached(currentItem))
1662                 att->setIsCurrentItem(true);
1663         }
1664         emit q->currentIndexChanged();
1665     }
1666 }
1667
1668 void QDeclarative1PathViewPrivate::fixOffsetCallback(void *d)
1669 {
1670     ((QDeclarative1PathViewPrivate *)d)->fixOffset();
1671 }
1672
1673 void QDeclarative1PathViewPrivate::fixOffset()
1674 {
1675     Q_Q(QDeclarative1PathView);
1676     if (model && items.count()) {
1677         if (haveHighlightRange && highlightRangeMode == QDeclarative1PathView::StrictlyEnforceRange) {
1678             int curr = calcCurrentIndex();
1679             if (curr != currentIndex)
1680                 q->setCurrentIndex(curr);
1681             else
1682                 snapToCurrent();
1683         }
1684     }
1685 }
1686
1687 void QDeclarative1PathViewPrivate::snapToCurrent()
1688 {
1689     if (!model || modelCount <= 0)
1690         return;
1691
1692     qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
1693
1694     moveReason = Other;
1695     offsetAdj = 0.0;
1696     tl.reset(moveOffset);
1697     moveOffset.setValue(offset);
1698
1699     const int duration = highlightMoveDuration;
1700
1701     if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
1702         qreal distance = modelCount - targetOffset + offset;
1703         if (targetOffset > moveOffset) {
1704             tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
1705             tl.set(moveOffset, modelCount);
1706             tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
1707         } else {
1708             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1709         }
1710     } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
1711         qreal distance = modelCount - offset + targetOffset;
1712         if (targetOffset < moveOffset) {
1713             tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
1714             tl.set(moveOffset, 0.0);
1715             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
1716         } else {
1717             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1718         }
1719     } else {
1720         tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1721     }
1722     moveDirection = Shortest;
1723 }
1724
1725 QDeclarative1PathViewAttached *QDeclarative1PathView::qmlAttachedProperties(QObject *obj)
1726 {
1727     return new QDeclarative1PathViewAttached(obj);
1728 }
1729
1730
1731
1732 QT_END_NAMESPACE
1733