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