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