QQuickCanvas renames
[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 "qquickwindow.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), dragging(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     qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()),
142                       q, QQuickPathView, SLOT(ticked()))
143     timer.invalidate();
144     qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
145                       q, QQuickPathView, SLOT(movementEnding()))
146 }
147
148 QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
149 {
150     Q_Q(QQuickPathView);
151     requestedIndex = modelIndex;
152     requestedOnPath = onPath;
153     requestedZ = z;
154     inRequest = true;
155     QQuickItem *item = model->item(modelIndex, false);
156     if (item) {
157         item->setParentItem(q);
158         requestedIndex = -1;
159         qPathViewAttachedType = attType;
160         QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
161         qPathViewAttachedType = 0;
162         if (att)
163             att->setOnPath(onPath);
164         QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
165         itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
166     }
167     inRequest = false;
168     return item;
169 }
170
171 void QQuickPathView::createdItem(int index, QQuickItem *item)
172 {
173     Q_D(QQuickPathView);
174     if (d->requestedIndex != index) {
175         qPathViewAttachedType = d->attachedType();
176         QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
177         qPathViewAttachedType = 0;
178         if (att) {
179             att->m_view = this;
180             att->setOnPath(false);
181         }
182         item->setParentItem(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 void QQuickPathViewPrivate::setDragging(bool d)
439 {
440     Q_Q(QQuickPathView);
441     if (dragging == d)
442         return;
443
444     dragging = d;
445     if (dragging)
446         emit q->dragStarted();
447     else
448         emit q->dragEnded();
449
450     emit q->draggingChanged();
451 }
452
453 /*!
454     \qmlclass PathView QQuickPathView
455     \inqmlmodule QtQuick 2
456     \ingroup qtquick-paths
457     \ingroup qtquick-views
458     \inherits Item
459     \brief Lays out model-provided items on a path
460
461     A PathView displays data from models created from built-in QML types like ListModel
462     and XmlListModel, or custom model classes defined in C++ that inherit from
463     QAbstractListModel.
464
465     The view has a \l model, which defines the data to be displayed, and
466     a \l delegate, which defines how the data should be displayed.
467     The \l delegate is instantiated for each item on the \l path.
468     The items may be flicked to move them along the path.
469
470     For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
471
472     \snippet qml/pathview/ContactModel.qml 0
473
474     This data can be represented as a PathView, like this:
475
476     \snippet qml/pathview/pathview.qml 0
477
478     \image pathview.gif
479
480     (Note the above example uses PathAttribute to scale and modify the
481     opacity of the items as they rotate. This additional code can be seen in the
482     PathAttribute documentation.)
483
484     PathView does not automatically handle keyboard navigation.  This is because
485     the keys to use for navigation will depend upon the shape of the path.  Navigation
486     can be added quite simply by setting \c focus to \c true and calling
487     \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
488     using the left and right arrow keys:
489
490     \qml
491     PathView {
492         // ...
493         focus: true
494         Keys.onLeftPressed: decrementCurrentIndex()
495         Keys.onRightPressed: incrementCurrentIndex()
496     }
497     \endqml
498
499     The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
500
501     Delegates are instantiated as needed and may be destroyed at any time.
502     State should \e never be stored in a delegate.
503
504     PathView attaches a number of properties to the root item of the delegate, for example
505     \c {PathView.isCurrentItem}.  In the following example, the root delegate item can access
506     this attached property directly as \c PathView.isCurrentItem, while the child
507     \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
508
509     \snippet qml/pathview/pathview.qml 1
510
511     \b Note that views do not enable \e clip automatically.  If the view
512     is not clipped by another item or the screen, it will be necessary
513     to set \e {clip: true} in order to have the out of view items clipped
514     nicely.
515
516     \sa Path, {quick/modelviews/pathview}{PathView example}
517 */
518
519 QQuickPathView::QQuickPathView(QQuickItem *parent)
520   : QQuickItem(*(new QQuickPathViewPrivate), parent)
521 {
522     Q_D(QQuickPathView);
523     d->init();
524 }
525
526 QQuickPathView::~QQuickPathView()
527 {
528     Q_D(QQuickPathView);
529     d->clear();
530     if (d->attType)
531         d->attType->release();
532     if (d->ownModel)
533         delete d->model;
534 }
535
536 /*!
537     \qmlattachedproperty PathView QtQuick2::PathView::view
538     This attached property holds the view that manages this delegate instance.
539
540     It is attached to each instance of the delegate.
541 */
542
543 /*!
544     \qmlattachedproperty bool QtQuick2::PathView::onPath
545     This attached property holds whether the item is currently on the path.
546
547     If a pathItemCount has been set, it is possible that some items may
548     be instantiated, but not considered to be currently on the path.
549     Usually, these items would be set invisible, for example:
550
551     \qml
552     Component {
553         Rectangle {
554             visible: PathView.onPath
555             // ...
556         }
557     }
558     \endqml
559
560     It is attached to each instance of the delegate.
561 */
562
563 /*!
564     \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
565     This attached property is true if this delegate is the current item; otherwise false.
566
567     It is attached to each instance of the delegate.
568
569     This property may be used to adjust the appearance of the current item.
570
571     \snippet qml/pathview/pathview.qml 1
572 */
573
574 /*!
575     \qmlproperty model QtQuick2::PathView::model
576     This property holds the model providing data for the view.
577
578     The model provides a set of data that is used to create the items for the view.
579     For large or dynamic datasets the model is usually provided by a C++ model object.
580     Models can also be created directly in QML, using the ListModel type.
581
582     \sa {qmlmodels}{Data Models}
583 */
584 QVariant QQuickPathView::model() const
585 {
586     Q_D(const QQuickPathView);
587     return d->modelVariant;
588 }
589
590 void QQuickPathView::setModel(const QVariant &model)
591 {
592     Q_D(QQuickPathView);
593     if (d->modelVariant == model)
594         return;
595
596     if (d->model) {
597         qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
598                              this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
599         qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
600                              this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
601         qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
602                              this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
603         for (int i=0; i<d->items.count(); i++){
604             QQuickItem *p = d->items[i];
605             d->releaseItem(p);
606         }
607         d->items.clear();
608     }
609
610     d->modelVariant = model;
611     QObject *object = qvariant_cast<QObject*>(model);
612     QQuickVisualModel *vim = 0;
613     if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
614         if (d->ownModel) {
615             delete d->model;
616             d->ownModel = false;
617         }
618         d->model = vim;
619     } else {
620         if (!d->ownModel) {
621             d->model = new QQuickVisualDataModel(qmlContext(this));
622             d->ownModel = true;
623             if (isComponentComplete())
624                 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
625         }
626         if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
627             dataModel->setModel(model);
628     }
629     d->modelCount = 0;
630     if (d->model) {
631         qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
632                           this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
633         qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
634                           this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
635         qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
636                           this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
637         d->modelCount = d->model->count();
638         if (d->model->count())
639             d->offset = qmlMod(d->offset, qreal(d->model->count()));
640         if (d->offset < 0)
641             d->offset = d->model->count() + d->offset;
642 }
643     d->regenerate();
644     if (d->currentIndex < d->modelCount)
645         setOffset(qmlMod(d->modelCount - d->currentIndex, d->modelCount));
646     else
647         d->fixOffset();
648     emit countChanged();
649     emit modelChanged();
650 }
651
652 /*!
653     \qmlproperty int QtQuick2::PathView::count
654     This property holds the number of items in the model.
655 */
656 int QQuickPathView::count() const
657 {
658     Q_D(const QQuickPathView);
659     return d->model ? d->modelCount : 0;
660 }
661
662 /*!
663     \qmlproperty Path QtQuick2::PathView::path
664     This property holds the path used to lay out the items.
665     For more information see the \l Path documentation.
666 */
667 QQuickPath *QQuickPathView::path() const
668 {
669     Q_D(const QQuickPathView);
670     return d->path;
671 }
672
673 void QQuickPathView::setPath(QQuickPath *path)
674 {
675     Q_D(QQuickPathView);
676     if (d->path == path)
677         return;
678     if (d->path)
679         qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
680                              this, QQuickPathView, SLOT(pathUpdated()));
681     d->path = path;
682     qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
683                       this, QQuickPathView, SLOT(pathUpdated()));
684     if (d->isValid() && isComponentComplete()) {
685         d->clear();
686         if (d->attType) {
687             d->attType->release();
688             d->attType = 0;
689         }
690         d->regenerate();
691     }
692     emit pathChanged();
693 }
694
695 /*!
696     \qmlproperty int QtQuick2::PathView::currentIndex
697     This property holds the index of the current item.
698 */
699 int QQuickPathView::currentIndex() const
700 {
701     Q_D(const QQuickPathView);
702     return d->currentIndex;
703 }
704
705 void QQuickPathView::setCurrentIndex(int idx)
706 {
707     Q_D(QQuickPathView);
708     idx = d->modelCount
709         ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
710         : 0;
711     if (d->model && (idx != d->currentIndex || !d->currentItem)) {
712         if (d->currentItem) {
713             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
714                 att->setIsCurrentItem(false);
715             d->releaseItem(d->currentItem);
716         }
717         int oldCurrentIdx = d->currentIndex;
718         QQuickItem *oldCurrentItem = d->currentItem;
719         d->currentItem = 0;
720         d->moveReason = QQuickPathViewPrivate::SetIndex;
721         d->currentIndex = idx;
722         if (d->modelCount) {
723             d->createCurrentItem();
724             if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
725                 d->snapToIndex(d->currentIndex);
726             d->currentItemOffset = d->positionOfIndex(d->currentIndex);
727             d->updateHighlight();
728         }
729         if (oldCurrentIdx != d->currentIndex)
730             emit currentIndexChanged();
731         if (oldCurrentItem != d->currentItem)
732             emit currentItemChanged();
733     }
734 }
735
736 QQuickItem *QQuickPathView::currentItem() const
737 {
738     Q_D(const QQuickPathView);
739     return d->currentItem;
740 }
741
742 /*!
743     \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
744
745     Increments the current index.
746
747     \b Note: methods should only be called after the Component has completed.
748 */
749 void QQuickPathView::incrementCurrentIndex()
750 {
751     Q_D(QQuickPathView);
752     d->moveDirection = QQuickPathViewPrivate::Positive;
753     setCurrentIndex(currentIndex()+1);
754 }
755
756 /*!
757     \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
758
759     Decrements the current index.
760
761     \b Note: methods should only be called after the Component has completed.
762 */
763 void QQuickPathView::decrementCurrentIndex()
764 {
765     Q_D(QQuickPathView);
766     d->moveDirection = QQuickPathViewPrivate::Negative;
767     setCurrentIndex(currentIndex()-1);
768 }
769
770 /*!
771     \qmlproperty real QtQuick2::PathView::offset
772
773     The offset specifies how far along the path the items are from their initial positions.
774     This is a real number that ranges from 0.0 to the count of items in the model.
775 */
776 qreal QQuickPathView::offset() const
777 {
778     Q_D(const QQuickPathView);
779     return d->offset;
780 }
781
782 void QQuickPathView::setOffset(qreal offset)
783 {
784     Q_D(QQuickPathView);
785     d->moveReason = QQuickPathViewPrivate::Other;
786     d->setOffset(offset);
787     d->updateCurrent();
788 }
789
790 void QQuickPathViewPrivate::setOffset(qreal o)
791 {
792     Q_Q(QQuickPathView);
793     if (offset != o) {
794         if (isValid() && q->isComponentComplete()) {
795             offset = qmlMod(o, qreal(modelCount));
796             if (offset < 0)
797                 offset += qreal(modelCount);
798             q->refill();
799         } else {
800             offset = o;
801         }
802         emit q->offsetChanged();
803     }
804 }
805
806 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
807 {
808     setOffset(o+offsetAdj);
809 }
810
811 /*!
812     \qmlproperty Component QtQuick2::PathView::highlight
813     This property holds the component to use as the highlight.
814
815     An instance of the highlight component will be created for each view.
816     The geometry of the resultant component instance will be managed by the view
817     so as to stay with the current item.
818
819     The below example demonstrates how to make a simple highlight.  Note the use
820     of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
821     the highlight is hidden when flicked away from the path.
822
823     \qml
824     Component {
825         Rectangle {
826             visible: PathView.onPath
827             // ...
828         }
829     }
830     \endqml
831
832     \sa highlightItem, highlightRangeMode
833 */
834
835 QQmlComponent *QQuickPathView::highlight() const
836 {
837     Q_D(const QQuickPathView);
838     return d->highlightComponent;
839 }
840
841 void QQuickPathView::setHighlight(QQmlComponent *highlight)
842 {
843     Q_D(QQuickPathView);
844     if (highlight != d->highlightComponent) {
845         d->highlightComponent = highlight;
846         d->createHighlight();
847         d->updateHighlight();
848         emit highlightChanged();
849     }
850 }
851
852 /*!
853   \qmlproperty Item QtQuick2::PathView::highlightItem
854
855   \c highlightItem holds the highlight item, which was created
856   from the \l highlight component.
857
858   \sa highlight
859 */
860 QQuickItem *QQuickPathView::highlightItem()
861 {
862     Q_D(const QQuickPathView);
863     return d->highlightItem;
864 }
865 /*!
866     \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
867     \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
868     \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
869
870     These properties set the preferred range of the highlight (current item)
871     within the view.  The preferred values must be in the range 0.0-1.0.
872
873     If highlightRangeMode is set to \e PathView.NoHighlightRange
874
875     If highlightRangeMode is set to \e PathView.ApplyRange the view will
876     attempt to maintain the highlight within the range, however
877     the highlight can move outside of the range at the ends of the path
878     or due to a mouse interaction.
879
880     If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
881     move outside of the range.  This means that the current item will change
882     if a keyboard or mouse action would cause the highlight to move
883     outside of the range.
884
885     Note that this is the correct way to influence where the
886     current item ends up when the view moves. For example, if you want the
887     currently selected item to be in the middle of the path, then set the
888     highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
889     Then, when the path scrolls,
890     the currently selected item will be the item at that position. This also applies to
891     when the currently selected item changes - it will scroll to within the preferred
892     highlight range. Furthermore, the behaviour of the current item index will occur
893     whether or not a highlight exists.
894
895     The default value is \e PathView.StrictlyEnforceRange.
896
897     Note that a valid range requires preferredHighlightEnd to be greater
898     than or equal to preferredHighlightBegin.
899 */
900 qreal QQuickPathView::preferredHighlightBegin() const
901 {
902     Q_D(const QQuickPathView);
903     return d->highlightRangeStart;
904 }
905
906 void QQuickPathView::setPreferredHighlightBegin(qreal start)
907 {
908     Q_D(QQuickPathView);
909     if (d->highlightRangeStart == start || start < 0 || start > 1.0)
910         return;
911     d->highlightRangeStart = start;
912     d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
913     refill();
914     emit preferredHighlightBeginChanged();
915 }
916
917 qreal QQuickPathView::preferredHighlightEnd() const
918 {
919     Q_D(const QQuickPathView);
920     return d->highlightRangeEnd;
921 }
922
923 void QQuickPathView::setPreferredHighlightEnd(qreal end)
924 {
925     Q_D(QQuickPathView);
926     if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
927         return;
928     d->highlightRangeEnd = end;
929     d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
930     refill();
931     emit preferredHighlightEndChanged();
932 }
933
934 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
935 {
936     Q_D(const QQuickPathView);
937     return d->highlightRangeMode;
938 }
939
940 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
941 {
942     Q_D(QQuickPathView);
943     if (d->highlightRangeMode == mode)
944         return;
945     d->highlightRangeMode = mode;
946     d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
947     if (d->haveHighlightRange) {
948         d->regenerate();
949         int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
950         if (index >= 0)
951             d->snapToIndex(index);
952     }
953     emit highlightRangeModeChanged();
954 }
955
956 /*!
957     \qmlproperty int QtQuick2::PathView::highlightMoveDuration
958     This property holds the move animation duration of the highlight delegate.
959
960     If the highlightRangeMode is StrictlyEnforceRange then this property
961     determines the speed that the items move along the path.
962
963     The default value for the duration is 300ms.
964 */
965 int QQuickPathView::highlightMoveDuration() const
966 {
967     Q_D(const QQuickPathView);
968     return d->highlightMoveDuration;
969 }
970
971 void QQuickPathView::setHighlightMoveDuration(int duration)
972 {
973     Q_D(QQuickPathView);
974     if (d->highlightMoveDuration == duration)
975         return;
976     d->highlightMoveDuration = duration;
977     emit highlightMoveDurationChanged();
978 }
979
980 /*!
981     \qmlproperty real QtQuick2::PathView::dragMargin
982     This property holds the maximum distance from the path that initiate mouse dragging.
983
984     By default the path can only be dragged by clicking on an item.  If
985     dragMargin is greater than zero, a drag can be initiated by clicking
986     within dragMargin pixels of the path.
987 */
988 qreal QQuickPathView::dragMargin() const
989 {
990     Q_D(const QQuickPathView);
991     return d->dragMargin;
992 }
993
994 void QQuickPathView::setDragMargin(qreal dragMargin)
995 {
996     Q_D(QQuickPathView);
997     if (d->dragMargin == dragMargin)
998         return;
999     d->dragMargin = dragMargin;
1000     emit dragMarginChanged();
1001 }
1002
1003 /*!
1004     \qmlproperty real QtQuick2::PathView::flickDeceleration
1005     This property holds the rate at which a flick will decelerate.
1006
1007     The default is 100.
1008 */
1009 qreal QQuickPathView::flickDeceleration() const
1010 {
1011     Q_D(const QQuickPathView);
1012     return d->deceleration;
1013 }
1014
1015 void QQuickPathView::setFlickDeceleration(qreal dec)
1016 {
1017     Q_D(QQuickPathView);
1018     if (d->deceleration == dec)
1019         return;
1020     d->deceleration = dec;
1021     emit flickDecelerationChanged();
1022 }
1023
1024 /*!
1025     \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1026     This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1027
1028     The default value is platform dependent.
1029 */
1030 qreal QQuickPathView::maximumFlickVelocity() const
1031 {
1032     Q_D(const QQuickPathView);
1033     return d->maximumFlickVelocity;
1034 }
1035
1036 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1037 {
1038     Q_D(QQuickPathView);
1039     if (vel == d->maximumFlickVelocity)
1040         return;
1041     d->maximumFlickVelocity = vel;
1042     emit maximumFlickVelocityChanged();
1043 }
1044
1045
1046 /*!
1047     \qmlproperty bool QtQuick2::PathView::interactive
1048
1049     A user cannot drag or flick a PathView that is not interactive.
1050
1051     This property is useful for temporarily disabling flicking. This allows
1052     special interaction with PathView's children.
1053 */
1054 bool QQuickPathView::isInteractive() const
1055 {
1056     Q_D(const QQuickPathView);
1057     return d->interactive;
1058 }
1059
1060 void QQuickPathView::setInteractive(bool interactive)
1061 {
1062     Q_D(QQuickPathView);
1063     if (interactive != d->interactive) {
1064         d->interactive = interactive;
1065         if (!interactive)
1066             d->tl.clear();
1067         emit interactiveChanged();
1068     }
1069 }
1070
1071 /*!
1072     \qmlproperty bool QtQuick2::PathView::moving
1073
1074     This property holds whether the view is currently moving
1075     due to the user either dragging or flicking the view.
1076 */
1077 bool QQuickPathView::isMoving() const
1078 {
1079     Q_D(const QQuickPathView);
1080     return d->moving;
1081 }
1082
1083 /*!
1084     \qmlproperty bool QtQuick2::PathView::flicking
1085
1086     This property holds whether the view is currently moving
1087     due to the user flicking the view.
1088 */
1089 bool QQuickPathView::isFlicking() const
1090 {
1091     Q_D(const QQuickPathView);
1092     return d->flicking;
1093 }
1094
1095 /*!
1096     \qmlproperty bool QtQuick2::PathView::dragging
1097
1098     This property holds whether the view is currently moving
1099     due to the user dragging the view.
1100 */
1101 bool QQuickPathView::isDragging() const
1102 {
1103     Q_D(const QQuickPathView);
1104     return d->dragging;
1105 }
1106
1107 /*!
1108     \qmlsignal QtQuick2::PathView::onMovementStarted()
1109
1110     This handler is called when the view begins moving due to user
1111     interaction.
1112 */
1113
1114 /*!
1115     \qmlsignal QtQuick2::PathView::onMovementEnded()
1116
1117     This handler is called when the view stops moving due to user
1118     interaction.  If a flick was generated, this handler will
1119     be triggered once the flick stops.  If a flick was not
1120     generated, the handler will be triggered when the
1121     user stops dragging - i.e. a mouse or touch release.
1122 */
1123
1124 /*!
1125     \qmlsignal QtQuick2::PathView::onFlickStarted()
1126
1127     This handler is called when the view is flicked.  A flick
1128     starts from the point that the mouse or touch is released,
1129     while still in motion.
1130 */
1131
1132 /*!
1133     \qmlsignal QtQuick2::PathView::onFlickEnded()
1134
1135     This handler is called when the view stops moving due to a flick.
1136 */
1137
1138 /*!
1139     \qmlsignal QtQuick2::PathView::onDragStarted()
1140
1141     This handler is called when the view starts to be dragged due to user
1142     interaction.
1143 */
1144
1145 /*!
1146     \qmlsignal QtQuick2::PathView::onDragEnded()
1147
1148     This handler is called when the user stops dragging the view.
1149
1150     If the velocity of the drag is suffient at the time the
1151     touch/mouse button is released then a flick will start.
1152 */
1153
1154 /*!
1155     \qmlproperty Component QtQuick2::PathView::delegate
1156
1157     The delegate provides a template defining each item instantiated by the view.
1158     The index is exposed as an accessible \c index property.  Properties of the
1159     model are also available depending upon the type of \l {qmlmodels}{Data Model}.
1160
1161     The number of objects and bindings in the delegate has a direct effect on the
1162     flicking performance of the view when pathItemCount is specified.  If at all possible, place functionality
1163     that is not needed for the normal display of the delegate in a \l Loader which
1164     can load additional components when needed.
1165
1166     Note that the PathView will layout the items based on the size of the root
1167     item in the delegate.
1168
1169     Here is an example delegate:
1170     \snippet qml/pathview/pathview.qml 1
1171 */
1172 QQmlComponent *QQuickPathView::delegate() const
1173 {
1174     Q_D(const QQuickPathView);
1175      if (d->model) {
1176         if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1177             return dataModel->delegate();
1178     }
1179
1180     return 0;
1181 }
1182
1183 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1184 {
1185     Q_D(QQuickPathView);
1186     if (delegate == this->delegate())
1187         return;
1188     if (!d->ownModel) {
1189         d->model = new QQuickVisualDataModel(qmlContext(this));
1190         d->ownModel = true;
1191     }
1192     if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1193         int oldCount = dataModel->count();
1194         dataModel->setDelegate(delegate);
1195         d->modelCount = dataModel->count();
1196         d->regenerate();
1197         if (oldCount != dataModel->count())
1198             emit countChanged();
1199         emit delegateChanged();
1200     }
1201 }
1202
1203 /*!
1204   \qmlproperty int QtQuick2::PathView::pathItemCount
1205   This property holds the number of items visible on the path at any one time.
1206 */
1207 int QQuickPathView::pathItemCount() const
1208 {
1209     Q_D(const QQuickPathView);
1210     return d->pathItems;
1211 }
1212
1213 void QQuickPathView::setPathItemCount(int i)
1214 {
1215     Q_D(QQuickPathView);
1216     if (i == d->pathItems)
1217         return;
1218     if (i < 1)
1219         i = 1;
1220     d->pathItems = i;
1221     d->updateMappedRange();
1222     if (d->isValid() && isComponentComplete()) {
1223         d->regenerate();
1224     }
1225     emit pathItemCountChanged();
1226 }
1227
1228 /*!
1229     \qmlproperty enumeration QtQuick2::PathView::snapMode
1230
1231     This property determines how the items will settle following a drag or flick.
1232     The possible values are:
1233
1234     \list
1235     \li PathView.NoSnap (default) - the items stop anywhere along the path.
1236     \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
1237     \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
1238         \l preferredHighlightBegin at the time the press is released.  This mode is particularly
1239         useful for moving one page at a time.
1240     \endlist
1241
1242     \c snapMode does not affect the \l currentIndex.  To update the
1243     \l currentIndex as the view is moved, set \l highlightRangeMode
1244     to \c PathView.StrictlyEnforceRange (default for PathView).
1245
1246     \sa highlightRangeMode
1247 */
1248 QQuickPathView::SnapMode QQuickPathView::snapMode() const
1249 {
1250     Q_D(const QQuickPathView);
1251     return d->snapMode;
1252 }
1253
1254 void QQuickPathView::setSnapMode(SnapMode mode)
1255 {
1256     Q_D(QQuickPathView);
1257     if (mode == d->snapMode)
1258         return;
1259     d->snapMode = mode;
1260     emit snapModeChanged();
1261 }
1262
1263 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1264 {
1265     qreal samples = qMin(path->path().length()/5, qreal(500.0));
1266     qreal res = path->path().length()/samples;
1267
1268     qreal mindist = 1e10; // big number
1269     QPointF nearPoint = path->pointAt(0);
1270     qreal nearPc = 0;
1271
1272     // get rough pos
1273     for (qreal i=1; i < samples; i++) {
1274         QPointF pt = path->pointAt(i/samples);
1275         QPointF diff = pt - point;
1276         qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1277         if (dist < mindist) {
1278             nearPoint = pt;
1279             nearPc = i;
1280             mindist = dist;
1281         }
1282     }
1283
1284     // now refine
1285     qreal approxPc = nearPc;
1286     for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
1287         QPointF pt = path->pointAt(i/samples);
1288         QPointF diff = pt - point;
1289         qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1290         if (dist < mindist) {
1291             nearPoint = pt;
1292             nearPc = i;
1293             mindist = dist;
1294         }
1295     }
1296
1297     if (nearPercent)
1298         *nearPercent = nearPc / samples;
1299
1300     return nearPoint;
1301 }
1302
1303 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1304 {
1305     velocityBuffer.append(v);
1306     if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1307         velocityBuffer.remove(0);
1308 }
1309
1310 qreal QQuickPathViewPrivate::calcVelocity() const
1311 {
1312     qreal velocity = 0;
1313     if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1314         int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1315         for (int i = 0; i < count; ++i) {
1316             qreal v = velocityBuffer.at(i);
1317             velocity += v;
1318         }
1319         velocity /= count;
1320     }
1321     return velocity;
1322 }
1323
1324 qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
1325 {
1326     if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
1327         return event->timestamp();
1328     }
1329
1330     return QQuickItemPrivate::elapsed(timer);
1331 }
1332
1333 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1334 {
1335     Q_D(QQuickPathView);
1336     if (d->interactive) {
1337         d->handleMousePressEvent(event);
1338         event->accept();
1339     } else {
1340         QQuickItem::mousePressEvent(event);
1341     }
1342 }
1343
1344 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1345 {
1346     if (!interactive || !items.count() || !model || !modelCount)
1347         return;
1348     velocityBuffer.clear();
1349     int idx = 0;
1350     for (; idx < items.count(); ++idx) {
1351         QQuickItem *item = items.at(idx);
1352         if (item->contains(item->mapFromScene(event->windowPos())))
1353             break;
1354     }
1355     if (idx == items.count() && dragMargin == 0.)  // didn't click on an item
1356         return;
1357
1358     startPoint = pointNear(event->localPos(), &startPc);
1359     if (idx == items.count()) {
1360         qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y());
1361         if (distance > dragMargin)
1362             return;
1363     }
1364
1365
1366     if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1367         stealMouse = true; // If we've been flicked then steal the click.
1368     else
1369         stealMouse = false;
1370
1371     QQuickItemPrivate::start(timer);
1372     lastPosTime = computeCurrentTime(event);
1373     tl.clear();
1374 }
1375
1376 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1377 {
1378     Q_D(QQuickPathView);
1379     if (d->interactive) {
1380         d->handleMouseMoveEvent(event);
1381         if (d->stealMouse)
1382             setKeepMouseGrab(true);
1383         event->accept();
1384     } else {
1385         QQuickItem::mouseMoveEvent(event);
1386     }
1387 }
1388
1389 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1390 {
1391     Q_Q(QQuickPathView);
1392     if (!interactive || !timer.isValid() || !model || !modelCount)
1393         return;
1394
1395     qint64 currentTimestamp = computeCurrentTime(event);
1396     qreal newPc;
1397     QPointF pathPoint = pointNear(event->localPos(), &newPc);
1398     if (!stealMouse) {
1399         QPointF delta = pathPoint - startPoint;
1400         if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1401             stealMouse = true;
1402         }
1403     } else {
1404         moveReason = QQuickPathViewPrivate::Mouse;
1405         qreal diff = (newPc - startPc)*modelCount*mappedRange;
1406         if (diff) {
1407             q->setOffset(offset + diff);
1408
1409             if (diff > modelCount/2)
1410                 diff -= modelCount;
1411             else if (diff < -modelCount/2)
1412                 diff += modelCount;
1413
1414             qint64 elapsed = currentTimestamp - lastPosTime;
1415             if (elapsed > 0)
1416                 addVelocitySample(diff / (qreal(elapsed) / 1000.));
1417         }
1418         if (!moving) {
1419             moving = true;
1420             emit q->movingChanged();
1421             emit q->movementStarted();
1422         }
1423         setDragging(true);
1424     }
1425     startPc = newPc;
1426     lastPosTime = currentTimestamp;
1427 }
1428
1429 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1430 {
1431     Q_D(QQuickPathView);
1432     if (d->interactive) {
1433         d->handleMouseReleaseEvent(event);
1434         event->accept();
1435         ungrabMouse();
1436     } else {
1437         QQuickItem::mouseReleaseEvent(event);
1438     }
1439 }
1440
1441 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1442 {
1443     Q_Q(QQuickPathView);
1444     stealMouse = false;
1445     q->setKeepMouseGrab(false);
1446     setDragging(false);
1447     if (!interactive || !timer.isValid() || !model || !modelCount) {
1448         timer.invalidate();
1449         if (!tl.isActive())
1450             q->movementEnding();
1451         return;
1452     }
1453
1454     qreal velocity = calcVelocity();
1455     qreal count = modelCount*mappedRange;
1456     qreal pixelVelocity = (path->path().length()/count) * velocity;
1457     if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
1458         if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
1459             // limit velocity
1460             qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1461             velocity = maxVel / (path->path().length()/count);
1462         }
1463         // Calculate the distance to be travelled
1464         qreal v2 = velocity*velocity;
1465         qreal accel = deceleration/10;
1466         qreal dist = 0;
1467         if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1468                 || snapMode != QQuickPathView::NoSnap)) {
1469             if (snapMode == QQuickPathView::SnapOneItem) {
1470                 // encourage snapping one item in direction of motion
1471                 if (velocity > 0.)
1472                     dist = qRound(0.5 + offset) - offset;
1473                 else
1474                     dist = qRound(0.5 - offset) + offset;
1475             } else {
1476                 // + 0.25 to encourage moving at least one item in the flick direction
1477                 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1478
1479                 // round to nearest item.
1480                 if (velocity > 0.)
1481                     dist = qRound(dist + offset) - offset;
1482                 else
1483                     dist = qRound(dist - offset) + offset;
1484             }
1485             // Calculate accel required to stop on item boundary
1486             if (dist <= 0.) {
1487                 dist = 0.;
1488                 accel = 0.;
1489             } else {
1490                 accel = v2 / (2.0f * qAbs(dist));
1491             }
1492         } else {
1493             dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1494         }
1495         flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1496         offsetAdj = 0.0;
1497         moveOffset.setValue(offset);
1498         tl.accel(moveOffset, velocity, accel, dist);
1499         tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1500         if (!flicking) {
1501             flicking = true;
1502             emit q->flickingChanged();
1503             emit q->flickStarted();
1504         }
1505     } else {
1506         fixOffset();
1507     }
1508
1509     timer.invalidate();
1510     if (!tl.isActive())
1511         q->movementEnding();
1512 }
1513
1514 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1515 {
1516     Q_D(QQuickPathView);
1517     QPointF localPos = mapFromScene(event->windowPos());
1518
1519     QQuickWindow *c = window();
1520     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
1521     bool stealThisEvent = d->stealMouse;
1522     if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
1523         QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
1524                                event->button(), event->buttons(), event->modifiers());
1525         mouseEvent.setAccepted(false);
1526
1527         switch (mouseEvent.type()) {
1528         case QEvent::MouseMove:
1529             d->handleMouseMoveEvent(&mouseEvent);
1530             break;
1531         case QEvent::MouseButtonPress:
1532             d->handleMousePressEvent(&mouseEvent);
1533             stealThisEvent = d->stealMouse;   // Update stealThisEvent in case changed by function call above
1534             break;
1535         case QEvent::MouseButtonRelease:
1536             d->handleMouseReleaseEvent(&mouseEvent);
1537             break;
1538         default:
1539             break;
1540         }
1541         grabber = c->mouseGrabberItem();
1542         if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1543             grabMouse();
1544
1545         return d->stealMouse;
1546     } else if (d->timer.isValid()) {
1547         d->timer.invalidate();
1548         d->fixOffset();
1549     }
1550     if (event->type() == QEvent::MouseButtonRelease)
1551         d->stealMouse = false;
1552     return false;
1553 }
1554
1555 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1556 {
1557     Q_D(QQuickPathView);
1558     if (!isVisible() || !d->interactive)
1559         return QQuickItem::childMouseEventFilter(i, e);
1560
1561     switch (e->type()) {
1562     case QEvent::MouseButtonPress:
1563     case QEvent::MouseMove:
1564     case QEvent::MouseButtonRelease:
1565         return sendMouseEvent(static_cast<QMouseEvent *>(e));
1566     default:
1567         break;
1568     }
1569
1570     return QQuickItem::childMouseEventFilter(i, e);
1571 }
1572
1573 void QQuickPathView::mouseUngrabEvent()
1574 {
1575     Q_D(QQuickPathView);
1576     if (d->stealMouse) {
1577         // if our mouse grab has been removed (probably by a Flickable),
1578         // fix our state
1579         d->stealMouse = false;
1580         setKeepMouseGrab(false);
1581         d->timer.invalidate();
1582         d->fixOffset();
1583         d->setDragging(false);
1584         if (!d->tl.isActive())
1585             movementEnding();
1586     }
1587 }
1588
1589 void QQuickPathView::updatePolish()
1590 {
1591     QQuickItem::updatePolish();
1592     refill();
1593 }
1594
1595 void QQuickPathView::componentComplete()
1596 {
1597     Q_D(QQuickPathView);
1598     if (d->model && d->ownModel)
1599         static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1600
1601     QQuickItem::componentComplete();
1602
1603     d->createHighlight();
1604     // It is possible that a refill has already happended to to Path
1605     // bindings being handled in the componentComplete().  If so
1606     // don't do it again.
1607     if (d->items.count() == 0 && d->model) {
1608         d->modelCount = d->model->count();
1609         d->regenerate();
1610     }
1611     d->updateHighlight();
1612     d->updateCurrent();
1613
1614     if (d->modelCount)
1615         emit countChanged();
1616 }
1617
1618 void QQuickPathView::refill()
1619 {
1620     Q_D(QQuickPathView);
1621
1622     d->layoutScheduled = false;
1623
1624     if (!d->isValid() || !isComponentComplete())
1625         return;
1626
1627     bool currentVisible = false;
1628
1629     // first move existing items and remove items off path
1630     int idx = d->firstIndex;
1631     QList<QQuickItem*>::iterator it = d->items.begin();
1632     while (it != d->items.end()) {
1633         qreal pos = d->positionOfIndex(idx);
1634         QQuickItem *item = *it;
1635         if (pos >= 0.0) {
1636             d->updateItem(item, pos);
1637             if (idx == d->currentIndex) {
1638                 currentVisible = true;
1639                 d->currentItemOffset = pos;
1640             }
1641             ++it;
1642         } else {
1643             // qDebug() << "release";
1644             d->updateItem(item, 1.0);
1645             d->releaseItem(item);
1646             if (it == d->items.begin()) {
1647                 if (++d->firstIndex >= d->modelCount)
1648                     d->firstIndex = 0;
1649             }
1650             it = d->items.erase(it);
1651         }
1652         ++idx;
1653         if (idx >= d->modelCount)
1654             idx = 0;
1655     }
1656     if (!d->items.count())
1657         d->firstIndex = -1;
1658
1659     bool waiting = false;
1660     if (d->modelCount) {
1661         // add items to beginning and end
1662         int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1663         if (d->items.count() < count) {
1664             int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1665             qreal startPos = 0.0;
1666             if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1667                                           || d->snapMode != QQuickPathView::NoSnap))
1668                 startPos = d->highlightRangeStart;
1669             if (d->firstIndex >= 0) {
1670                 startPos = d->positionOfIndex(d->firstIndex);
1671                 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1672             }
1673             qreal pos = d->positionOfIndex(idx);
1674             while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1675 //                qDebug() << "append" << idx;
1676                 QQuickItem *item = d->getItem(idx, idx+1);
1677                 if (!item) {
1678                     waiting = true;
1679                     break;
1680                 }
1681                 if (d->currentIndex == idx) {
1682                     currentVisible = true;
1683                     d->currentItemOffset = pos;
1684                 }
1685                 if (d->items.count() == 0)
1686                     d->firstIndex = idx;
1687                 d->items.append(item);
1688                 d->updateItem(item, pos);
1689                 ++idx;
1690                 if (idx >= d->modelCount)
1691                     idx = 0;
1692                 pos = d->positionOfIndex(idx);
1693             }
1694
1695             idx = d->firstIndex - 1;
1696             if (idx < 0)
1697                 idx = d->modelCount - 1;
1698             pos = d->positionOfIndex(idx);
1699             while (!waiting && (pos >= 0.0 && pos < startPos) && d->items.count() < count) {
1700 //                 qDebug() << "prepend" << idx;
1701                 QQuickItem *item = d->getItem(idx, idx+1);
1702                 if (!item) {
1703                     waiting = true;
1704                     break;
1705                 }
1706                 if (d->currentIndex == idx) {
1707                     currentVisible = true;
1708                     d->currentItemOffset = pos;
1709                 }
1710                 d->items.prepend(item);
1711                 d->updateItem(item, pos);
1712                 d->firstIndex = idx;
1713                 idx = d->firstIndex - 1;
1714                 if (idx < 0)
1715                     idx = d->modelCount - 1;
1716                 pos = d->positionOfIndex(idx);
1717             }
1718         }
1719     }
1720
1721     if (!currentVisible) {
1722         d->currentItemOffset = 1.0;
1723         if (d->currentItem) {
1724             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1725                 att->setOnPath(false);
1726         } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
1727             if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
1728                 d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
1729                 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1730                     att->setIsCurrentItem(true);
1731             }
1732         }
1733     } else if (!waiting && !d->currentItem) {
1734         if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
1735             d->currentItem->setFocus(true);
1736             if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1737                 att->setIsCurrentItem(true);
1738         }
1739     }
1740
1741     if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1742         d->updateItem(d->highlightItem, d->highlightRangeStart);
1743         if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1744             att->setOnPath(true);
1745     } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
1746         d->updateItem(d->highlightItem, d->currentItemOffset);
1747         if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1748             att->setOnPath(currentVisible);
1749     }
1750     while (d->itemCache.count())
1751         d->releaseItem(d->itemCache.takeLast());
1752 }
1753
1754 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1755 {
1756     Q_D(QQuickPathView);
1757     if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1758         return;
1759
1760     if (reset) {
1761         d->modelCount = d->model->count();
1762         d->regenerate();
1763         emit countChanged();
1764         return;
1765     }
1766
1767     if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1768         return;
1769
1770     const int modelCount = d->modelCount;
1771     int moveId = -1;
1772     int moveOffset;
1773     bool currentChanged = false;
1774     bool changedOffset = false;
1775     foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
1776         if (moveId == -1 && d->currentIndex >= r.index + r.count) {
1777             d->currentIndex -= r.count;
1778             currentChanged = true;
1779         } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
1780             // current item has been removed.
1781             if (r.isMove()) {
1782                 moveId = r.moveId;
1783                 moveOffset = d->currentIndex - r.index;
1784             } else if (d->currentItem) {
1785                 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1786                     att->setIsCurrentItem(true);
1787                 d->releaseItem(d->currentItem);
1788                 d->currentItem = 0;
1789             }
1790             d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
1791             currentChanged = true;
1792         }
1793
1794         if (r.index > d->currentIndex) {
1795             changedOffset = true;
1796             d->offset -= r.count;
1797             d->offsetAdj -= r.count;
1798         }
1799         d->modelCount -= r.count;
1800     }
1801     foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
1802         if (d->modelCount) {
1803             if (moveId == -1 && i.index <= d->currentIndex) {
1804                 d->currentIndex += i.count;
1805                 currentChanged = true;
1806             } else {
1807                 if (moveId != -1 && moveId == i.moveId) {
1808                     d->currentIndex = i.index + moveOffset;
1809                     currentChanged = true;
1810                 }
1811                 if (i.index > d->currentIndex) {
1812                     d->offset += i.count;
1813                     d->offsetAdj += i.count;
1814                     changedOffset = true;
1815                 }
1816             }
1817         }
1818         d->modelCount += i.count;
1819     }
1820
1821     d->offset = qmlMod(d->offset, d->modelCount);
1822     if (d->offset < 0)
1823         d->offset += d->modelCount;
1824     if (d->currentIndex == -1)
1825         d->currentIndex = d->calcCurrentIndex();
1826
1827     d->itemCache += d->items;
1828     d->items.clear();
1829
1830     if (!d->modelCount) {
1831         while (d->itemCache.count())
1832             d->releaseItem(d->itemCache.takeLast());
1833         d->offset = 0;
1834         changedOffset = true;
1835         d->tl.reset(d->moveOffset);
1836     } else {
1837         if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1838             d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
1839             changedOffset = true;
1840         }
1841         d->firstIndex = -1;
1842         d->updateMappedRange();
1843         d->scheduleLayout();
1844     }
1845     if (changedOffset)
1846         emit offsetChanged();
1847     if (currentChanged)
1848         emit currentIndexChanged();
1849     if (d->modelCount != modelCount)
1850         emit countChanged();
1851 }
1852
1853 void QQuickPathView::destroyingItem(QQuickItem *item)
1854 {
1855     Q_UNUSED(item);
1856 }
1857
1858 void QQuickPathView::ticked()
1859 {
1860     Q_D(QQuickPathView);
1861     d->updateCurrent();
1862 }
1863
1864 void QQuickPathView::movementEnding()
1865 {
1866     Q_D(QQuickPathView);
1867     if (d->flicking) {
1868         d->flicking = false;
1869         emit flickingChanged();
1870         emit flickEnded();
1871     }
1872     if (d->moving && !d->stealMouse) {
1873         d->moving = false;
1874         emit movingChanged();
1875         emit movementEnded();
1876     }
1877 }
1878
1879 // find the item closest to the snap position
1880 int QQuickPathViewPrivate::calcCurrentIndex()
1881 {
1882     int current = 0;
1883     if (modelCount && model && items.count()) {
1884         offset = qmlMod(offset, modelCount);
1885         if (offset < 0)
1886             offset += modelCount;
1887         current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1888         current = current % modelCount;
1889     }
1890
1891     return current;
1892 }
1893
1894 void QQuickPathViewPrivate::createCurrentItem()
1895 {
1896     if (requestedIndex != -1)
1897         return;
1898     int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
1899     if (itemIndex < items.count()) {
1900         if ((currentItem = getItem(currentIndex, currentIndex, true))) {
1901             currentItem->setFocus(true);
1902             if (QQuickPathViewAttached *att = attached(currentItem))
1903                 att->setIsCurrentItem(true);
1904         }
1905     } else if (currentIndex >= 0 && currentIndex < modelCount) {
1906         if ((currentItem = getItem(currentIndex, currentIndex, false))) {
1907             updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
1908             if (QQuickPathViewAttached *att = attached(currentItem))
1909                 att->setIsCurrentItem(true);
1910         }
1911     }
1912 }
1913
1914 void QQuickPathViewPrivate::updateCurrent()
1915 {
1916     Q_Q(QQuickPathView);
1917     if (moveReason == SetIndex)
1918         return;
1919     if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
1920         return;
1921
1922     int idx = calcCurrentIndex();
1923     if (model && idx != currentIndex) {
1924         if (currentItem) {
1925             if (QQuickPathViewAttached *att = attached(currentItem))
1926                 att->setIsCurrentItem(false);
1927             releaseItem(currentItem);
1928         }
1929         currentIndex = idx;
1930         currentItem = 0;
1931         createCurrentItem();
1932         emit q->currentIndexChanged();
1933     }
1934 }
1935
1936 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
1937 {
1938     ((QQuickPathViewPrivate *)d)->fixOffset();
1939 }
1940
1941 void QQuickPathViewPrivate::fixOffset()
1942 {
1943     Q_Q(QQuickPathView);
1944     if (model && items.count()) {
1945         if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1946                 || snapMode != QQuickPathView::NoSnap)) {
1947             int curr = calcCurrentIndex();
1948             if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
1949                 q->setCurrentIndex(curr);
1950             else
1951                 snapToIndex(curr);
1952         }
1953     }
1954 }
1955
1956 void QQuickPathViewPrivate::snapToIndex(int index)
1957 {
1958     if (!model || modelCount <= 0)
1959         return;
1960
1961     qreal targetOffset = qmlMod(modelCount - index, modelCount);
1962
1963     if (offset == targetOffset)
1964         return;
1965
1966     moveReason = Other;
1967     offsetAdj = 0.0;
1968     tl.reset(moveOffset);
1969     moveOffset.setValue(offset);
1970
1971     const int duration = highlightMoveDuration;
1972
1973     if (!duration) {
1974         tl.set(moveOffset, targetOffset);
1975     } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2.0)) {
1976         qreal distance = modelCount - targetOffset + offset;
1977         if (targetOffset > moveOffset) {
1978             tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
1979             tl.set(moveOffset, modelCount);
1980             tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
1981         } else {
1982             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1983         }
1984     } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2.0) {
1985         qreal distance = modelCount - offset + targetOffset;
1986         if (targetOffset < moveOffset) {
1987             tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
1988             tl.set(moveOffset, 0.0);
1989             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
1990         } else {
1991             tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1992         }
1993     } else {
1994         tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1995     }
1996     moveDirection = Shortest;
1997 }
1998
1999 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
2000 {
2001     return new QQuickPathViewAttached(obj);
2002 }
2003
2004 QT_END_NAMESPACE
2005