1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qquickpathview_p.h"
43 #include "qquickpathview_p_p.h"
44 #include "qquickwindow.h"
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>
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>
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
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
70 // The default maximum velocity of a flick.
71 #ifndef QML_FLICK_DEFAULTMAXVELOCITY
72 #define QML_FLICK_DEFAULTMAXVELOCITY 2500
78 const qreal MinimumFlickVelocity = 75.0;
80 inline qreal qmlMod(qreal x, qreal y)
82 #ifdef QT_USE_MATH_H_FLOATS
83 if (sizeof(qreal) == sizeof(float))
84 return fmodf(float(x), float(y));
90 static QQmlOpenMetaObjectType *qPathViewAttachedType = 0;
92 QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
93 : QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
95 if (qPathViewAttachedType) {
96 m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
97 m_metaobject->setCached(true);
99 m_metaobject = new QQmlOpenMetaObject(this);
103 QQuickPathViewAttached::~QQuickPathViewAttached()
107 QVariant QQuickPathViewAttached::value(const QByteArray &name) const
109 return m_metaobject->value(name);
111 void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
113 m_metaobject->setValue(name, val);
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)
134 void QQuickPathViewPrivate::init()
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()))
144 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
145 q, QQuickPathView, SLOT(movementEnding()))
148 QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
151 requestedIndex = modelIndex;
152 requestedOnPath = onPath;
155 QQuickItem *item = model->item(modelIndex, false);
157 item->setParentItem(q);
159 qPathViewAttachedType = attType;
160 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
161 qPathViewAttachedType = 0;
163 att->setOnPath(onPath);
164 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
165 itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
171 void QQuickPathView::createdItem(int index, QQuickItem *item)
174 if (d->requestedIndex != index) {
175 qPathViewAttachedType = d->attachedType();
176 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
177 qPathViewAttachedType = 0;
180 att->setOnPath(false);
182 item->setParentItem(this);
183 d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
185 d->requestedIndex = -1;
191 void QQuickPathView::initItem(int index, QQuickItem *item)
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;
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);
206 att->setOnPath(d->requestedOnPath);
211 void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
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);
224 QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
226 return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
229 QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
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());
242 void QQuickPathViewPrivate::clear()
245 releaseItem(currentItem);
248 for (int i=0; i<items.count(); i++){
249 QQuickItem *p = items[i];
255 void QQuickPathViewPrivate::updateMappedRange()
257 if (model && pathItems != -1 && pathItems < modelCount)
258 mappedRange = qreal(pathItems)/modelCount;
263 qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
267 if (model && index >= 0 && index < modelCount) {
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;
280 pos = qmlMod(globalPos + start, 1.0);
287 void QQuickPathViewPrivate::createHighlight()
290 if (!q->isComponentComplete())
293 bool changed = false;
295 highlightItem->setParentItem(0);
296 highlightItem->deleteLater();
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);
308 QQml_setParent_noEvent(highlightContext, nobj);
309 item = qobject_cast<QQuickItem *>(nobj);
313 delete highlightContext;
316 item = new QQuickItem;
319 QQml_setParent_noEvent(item, q);
320 item->setParentItem(q);
321 highlightItem = item;
325 emit q->highlightItemChanged();
328 void QQuickPathViewPrivate::updateHighlight()
331 if (!q->isComponentComplete() || !isValid())
334 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
335 updateItem(highlightItem, highlightRangeStart);
337 qreal target = currentIndex;
340 tl.reset(moveHighlight);
341 moveHighlight.setValue(highlightPosition);
343 const int duration = highlightMoveDuration;
345 if (target - highlightPosition > modelCount/2) {
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) {
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));
358 highlightUp = highlightPosition - target < 0;
359 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
365 void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
367 if (pos != highlightPosition) {
370 if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
371 start = highlightRangeStart;
372 end = highlightRangeEnd;
375 qreal range = qreal(modelCount);
376 // calc normalized position of highlight relative to offset
377 qreal relativeHighlight = qmlMod(pos + offset, range) / range;
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);
387 highlightPosition = pos;
388 qreal pathPos = positionOfIndex(pos);
389 updateItem(highlightItem, pathPos);
390 if (QQuickPathViewAttached *att = attached(highlightItem))
391 att->setOnPath(pathPos != -1.0);
395 void QQuickPathView::pathUpdated()
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))
408 void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
410 if (QQuickPathViewAttached *att = attached(item)) {
411 if (qFuzzyCompare(att->m_percent, percent))
413 att->m_percent = percent;
414 foreach (const QString &attr, path->attributes())
415 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
417 QPointF pf = path->pointAt(percent);
418 item->setX(pf.x() - item->width()/2);
419 item->setY(pf.y() - item->height()/2);
422 void QQuickPathViewPrivate::regenerate()
425 if (!q->isComponentComplete())
438 void QQuickPathViewPrivate::setDragging(bool d)
446 emit q->dragStarted();
450 emit q->draggingChanged();
454 \qmlclass PathView QQuickPathView
455 \inqmlmodule QtQuick 2
456 \ingroup qtquick-paths
457 \ingroup qtquick-views
459 \brief Lays out model-provided items on a path
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
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.
470 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
472 \snippet qml/pathview/ContactModel.qml 0
474 This data can be represented as a PathView, like this:
476 \snippet qml/pathview/pathview.qml 0
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.)
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:
494 Keys.onLeftPressed: decrementCurrentIndex()
495 Keys.onRightPressed: incrementCurrentIndex()
499 The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
501 Delegates are instantiated as needed and may be destroyed at any time.
502 State should \e never be stored in a delegate.
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.
509 \snippet qml/pathview/pathview.qml 1
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
516 \sa Path, {quick/modelviews/pathview}{PathView example}
519 QQuickPathView::QQuickPathView(QQuickItem *parent)
520 : QQuickItem(*(new QQuickPathViewPrivate), parent)
526 QQuickPathView::~QQuickPathView()
531 d->attType->release();
537 \qmlattachedproperty PathView QtQuick2::PathView::view
538 This attached property holds the view that manages this delegate instance.
540 It is attached to each instance of the delegate.
544 \qmlattachedproperty bool QtQuick2::PathView::onPath
545 This attached property holds whether the item is currently on the path.
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:
554 visible: PathView.onPath
560 It is attached to each instance of the delegate.
564 \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
565 This attached property is true if this delegate is the current item; otherwise false.
567 It is attached to each instance of the delegate.
569 This property may be used to adjust the appearance of the current item.
571 \snippet qml/pathview/pathview.qml 1
575 \qmlproperty model QtQuick2::PathView::model
576 This property holds the model providing data for the view.
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.
582 \sa {qmlmodels}{Data Models}
584 QVariant QQuickPathView::model() const
586 Q_D(const QQuickPathView);
587 return d->modelVariant;
590 void QQuickPathView::setModel(const QVariant &model)
593 if (d->modelVariant == 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];
610 d->modelVariant = model;
611 QObject *object = qvariant_cast<QObject*>(model);
612 QQuickVisualModel *vim = 0;
613 if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
621 d->model = new QQuickVisualDataModel(qmlContext(this));
623 if (isComponentComplete())
624 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
626 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
627 dataModel->setModel(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()));
641 d->offset = d->model->count() + d->offset;
644 if (d->currentIndex < d->modelCount)
645 setOffset(qmlMod(d->modelCount - d->currentIndex, d->modelCount));
653 \qmlproperty int QtQuick2::PathView::count
654 This property holds the number of items in the model.
656 int QQuickPathView::count() const
658 Q_D(const QQuickPathView);
659 return d->model ? d->modelCount : 0;
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.
667 QQuickPath *QQuickPathView::path() const
669 Q_D(const QQuickPathView);
673 void QQuickPathView::setPath(QQuickPath *path)
679 qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
680 this, QQuickPathView, SLOT(pathUpdated()));
682 qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
683 this, QQuickPathView, SLOT(pathUpdated()));
684 if (d->isValid() && isComponentComplete()) {
687 d->attType->release();
696 \qmlproperty int QtQuick2::PathView::currentIndex
697 This property holds the index of the current item.
699 int QQuickPathView::currentIndex() const
701 Q_D(const QQuickPathView);
702 return d->currentIndex;
705 void QQuickPathView::setCurrentIndex(int idx)
709 ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
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);
717 int oldCurrentIdx = d->currentIndex;
718 QQuickItem *oldCurrentItem = d->currentItem;
720 d->moveReason = QQuickPathViewPrivate::SetIndex;
721 d->currentIndex = idx;
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();
729 if (oldCurrentIdx != d->currentIndex)
730 emit currentIndexChanged();
731 if (oldCurrentItem != d->currentItem)
732 emit currentItemChanged();
736 QQuickItem *QQuickPathView::currentItem() const
738 Q_D(const QQuickPathView);
739 return d->currentItem;
743 \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
745 Increments the current index.
747 \b Note: methods should only be called after the Component has completed.
749 void QQuickPathView::incrementCurrentIndex()
752 d->moveDirection = QQuickPathViewPrivate::Positive;
753 setCurrentIndex(currentIndex()+1);
757 \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
759 Decrements the current index.
761 \b Note: methods should only be called after the Component has completed.
763 void QQuickPathView::decrementCurrentIndex()
766 d->moveDirection = QQuickPathViewPrivate::Negative;
767 setCurrentIndex(currentIndex()-1);
771 \qmlproperty real QtQuick2::PathView::offset
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.
776 qreal QQuickPathView::offset() const
778 Q_D(const QQuickPathView);
782 void QQuickPathView::setOffset(qreal offset)
785 d->moveReason = QQuickPathViewPrivate::Other;
786 d->setOffset(offset);
790 void QQuickPathViewPrivate::setOffset(qreal o)
794 if (isValid() && q->isComponentComplete()) {
795 offset = qmlMod(o, qreal(modelCount));
797 offset += qreal(modelCount);
802 emit q->offsetChanged();
806 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
808 setOffset(o+offsetAdj);
812 \qmlproperty Component QtQuick2::PathView::highlight
813 This property holds the component to use as the highlight.
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.
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.
826 visible: PathView.onPath
832 \sa highlightItem, highlightRangeMode
835 QQmlComponent *QQuickPathView::highlight() const
837 Q_D(const QQuickPathView);
838 return d->highlightComponent;
841 void QQuickPathView::setHighlight(QQmlComponent *highlight)
844 if (highlight != d->highlightComponent) {
845 d->highlightComponent = highlight;
846 d->createHighlight();
847 d->updateHighlight();
848 emit highlightChanged();
853 \qmlproperty Item QtQuick2::PathView::highlightItem
855 \c highlightItem holds the highlight item, which was created
856 from the \l highlight component.
860 QQuickItem *QQuickPathView::highlightItem()
862 Q_D(const QQuickPathView);
863 return d->highlightItem;
866 \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
867 \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
868 \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
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.
873 If highlightRangeMode is set to \e PathView.NoHighlightRange
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.
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.
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.
895 The default value is \e PathView.StrictlyEnforceRange.
897 Note that a valid range requires preferredHighlightEnd to be greater
898 than or equal to preferredHighlightBegin.
900 qreal QQuickPathView::preferredHighlightBegin() const
902 Q_D(const QQuickPathView);
903 return d->highlightRangeStart;
906 void QQuickPathView::setPreferredHighlightBegin(qreal start)
909 if (d->highlightRangeStart == start || start < 0 || start > 1.0)
911 d->highlightRangeStart = start;
912 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
914 emit preferredHighlightBeginChanged();
917 qreal QQuickPathView::preferredHighlightEnd() const
919 Q_D(const QQuickPathView);
920 return d->highlightRangeEnd;
923 void QQuickPathView::setPreferredHighlightEnd(qreal end)
926 if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
928 d->highlightRangeEnd = end;
929 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
931 emit preferredHighlightEndChanged();
934 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
936 Q_D(const QQuickPathView);
937 return d->highlightRangeMode;
940 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
943 if (d->highlightRangeMode == mode)
945 d->highlightRangeMode = mode;
946 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
947 if (d->haveHighlightRange) {
949 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
951 d->snapToIndex(index);
953 emit highlightRangeModeChanged();
957 \qmlproperty int QtQuick2::PathView::highlightMoveDuration
958 This property holds the move animation duration of the highlight delegate.
960 If the highlightRangeMode is StrictlyEnforceRange then this property
961 determines the speed that the items move along the path.
963 The default value for the duration is 300ms.
965 int QQuickPathView::highlightMoveDuration() const
967 Q_D(const QQuickPathView);
968 return d->highlightMoveDuration;
971 void QQuickPathView::setHighlightMoveDuration(int duration)
974 if (d->highlightMoveDuration == duration)
976 d->highlightMoveDuration = duration;
977 emit highlightMoveDurationChanged();
981 \qmlproperty real QtQuick2::PathView::dragMargin
982 This property holds the maximum distance from the path that initiate mouse dragging.
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.
988 qreal QQuickPathView::dragMargin() const
990 Q_D(const QQuickPathView);
991 return d->dragMargin;
994 void QQuickPathView::setDragMargin(qreal dragMargin)
997 if (d->dragMargin == dragMargin)
999 d->dragMargin = dragMargin;
1000 emit dragMarginChanged();
1004 \qmlproperty real QtQuick2::PathView::flickDeceleration
1005 This property holds the rate at which a flick will decelerate.
1009 qreal QQuickPathView::flickDeceleration() const
1011 Q_D(const QQuickPathView);
1012 return d->deceleration;
1015 void QQuickPathView::setFlickDeceleration(qreal dec)
1017 Q_D(QQuickPathView);
1018 if (d->deceleration == dec)
1020 d->deceleration = dec;
1021 emit flickDecelerationChanged();
1025 \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1026 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1028 The default value is platform dependent.
1030 qreal QQuickPathView::maximumFlickVelocity() const
1032 Q_D(const QQuickPathView);
1033 return d->maximumFlickVelocity;
1036 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1038 Q_D(QQuickPathView);
1039 if (vel == d->maximumFlickVelocity)
1041 d->maximumFlickVelocity = vel;
1042 emit maximumFlickVelocityChanged();
1047 \qmlproperty bool QtQuick2::PathView::interactive
1049 A user cannot drag or flick a PathView that is not interactive.
1051 This property is useful for temporarily disabling flicking. This allows
1052 special interaction with PathView's children.
1054 bool QQuickPathView::isInteractive() const
1056 Q_D(const QQuickPathView);
1057 return d->interactive;
1060 void QQuickPathView::setInteractive(bool interactive)
1062 Q_D(QQuickPathView);
1063 if (interactive != d->interactive) {
1064 d->interactive = interactive;
1067 emit interactiveChanged();
1072 \qmlproperty bool QtQuick2::PathView::moving
1074 This property holds whether the view is currently moving
1075 due to the user either dragging or flicking the view.
1077 bool QQuickPathView::isMoving() const
1079 Q_D(const QQuickPathView);
1084 \qmlproperty bool QtQuick2::PathView::flicking
1086 This property holds whether the view is currently moving
1087 due to the user flicking the view.
1089 bool QQuickPathView::isFlicking() const
1091 Q_D(const QQuickPathView);
1096 \qmlproperty bool QtQuick2::PathView::dragging
1098 This property holds whether the view is currently moving
1099 due to the user dragging the view.
1101 bool QQuickPathView::isDragging() const
1103 Q_D(const QQuickPathView);
1108 \qmlsignal QtQuick2::PathView::onMovementStarted()
1110 This handler is called when the view begins moving due to user
1115 \qmlsignal QtQuick2::PathView::onMovementEnded()
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.
1125 \qmlsignal QtQuick2::PathView::onFlickStarted()
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.
1133 \qmlsignal QtQuick2::PathView::onFlickEnded()
1135 This handler is called when the view stops moving due to a flick.
1139 \qmlsignal QtQuick2::PathView::onDragStarted()
1141 This handler is called when the view starts to be dragged due to user
1146 \qmlsignal QtQuick2::PathView::onDragEnded()
1148 This handler is called when the user stops dragging the view.
1150 If the velocity of the drag is suffient at the time the
1151 touch/mouse button is released then a flick will start.
1155 \qmlproperty Component QtQuick2::PathView::delegate
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}.
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.
1166 Note that the PathView will layout the items based on the size of the root
1167 item in the delegate.
1169 Here is an example delegate:
1170 \snippet qml/pathview/pathview.qml 1
1172 QQmlComponent *QQuickPathView::delegate() const
1174 Q_D(const QQuickPathView);
1176 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1177 return dataModel->delegate();
1183 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1185 Q_D(QQuickPathView);
1186 if (delegate == this->delegate())
1189 d->model = new QQuickVisualDataModel(qmlContext(this));
1192 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1193 int oldCount = dataModel->count();
1194 dataModel->setDelegate(delegate);
1195 d->modelCount = dataModel->count();
1197 if (oldCount != dataModel->count())
1198 emit countChanged();
1199 emit delegateChanged();
1204 \qmlproperty int QtQuick2::PathView::pathItemCount
1205 This property holds the number of items visible on the path at any one time.
1207 int QQuickPathView::pathItemCount() const
1209 Q_D(const QQuickPathView);
1210 return d->pathItems;
1213 void QQuickPathView::setPathItemCount(int i)
1215 Q_D(QQuickPathView);
1216 if (i == d->pathItems)
1221 d->updateMappedRange();
1222 if (d->isValid() && isComponentComplete()) {
1225 emit pathItemCountChanged();
1229 \qmlproperty enumeration QtQuick2::PathView::snapMode
1231 This property determines how the items will settle following a drag or flick.
1232 The possible values are:
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.
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).
1246 \sa highlightRangeMode
1248 QQuickPathView::SnapMode QQuickPathView::snapMode() const
1250 Q_D(const QQuickPathView);
1254 void QQuickPathView::setSnapMode(SnapMode mode)
1256 Q_D(QQuickPathView);
1257 if (mode == d->snapMode)
1260 emit snapModeChanged();
1263 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1265 qreal samples = qMin(path->path().length()/5, qreal(500.0));
1266 qreal res = path->path().length()/samples;
1268 qreal mindist = 1e10; // big number
1269 QPointF nearPoint = path->pointAt(0);
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) {
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) {
1298 *nearPercent = nearPc / samples;
1303 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1305 velocityBuffer.append(v);
1306 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1307 velocityBuffer.remove(0);
1310 qreal QQuickPathViewPrivate::calcVelocity() const
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);
1324 qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
1326 if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
1327 return event->timestamp();
1330 return QQuickItemPrivate::elapsed(timer);
1333 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1335 Q_D(QQuickPathView);
1336 if (d->interactive) {
1337 d->handleMousePressEvent(event);
1340 QQuickItem::mousePressEvent(event);
1344 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1346 if (!interactive || !items.count() || !model || !modelCount)
1348 velocityBuffer.clear();
1350 for (; idx < items.count(); ++idx) {
1351 QQuickItem *item = items.at(idx);
1352 if (item->contains(item->mapFromScene(event->windowPos())))
1355 if (idx == items.count() && dragMargin == 0.) // didn't click on an item
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)
1366 if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1367 stealMouse = true; // If we've been flicked then steal the click.
1371 QQuickItemPrivate::start(timer);
1372 lastPosTime = computeCurrentTime(event);
1376 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1378 Q_D(QQuickPathView);
1379 if (d->interactive) {
1380 d->handleMouseMoveEvent(event);
1382 setKeepMouseGrab(true);
1385 QQuickItem::mouseMoveEvent(event);
1389 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1391 Q_Q(QQuickPathView);
1392 if (!interactive || !timer.isValid() || !model || !modelCount)
1395 qint64 currentTimestamp = computeCurrentTime(event);
1397 QPointF pathPoint = pointNear(event->localPos(), &newPc);
1399 QPointF delta = pathPoint - startPoint;
1400 if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1404 moveReason = QQuickPathViewPrivate::Mouse;
1405 qreal diff = (newPc - startPc)*modelCount*mappedRange;
1407 q->setOffset(offset + diff);
1409 if (diff > modelCount/2)
1411 else if (diff < -modelCount/2)
1414 qint64 elapsed = currentTimestamp - lastPosTime;
1416 addVelocitySample(diff / (qreal(elapsed) / 1000.));
1420 emit q->movingChanged();
1421 emit q->movementStarted();
1426 lastPosTime = currentTimestamp;
1429 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1431 Q_D(QQuickPathView);
1432 if (d->interactive) {
1433 d->handleMouseReleaseEvent(event);
1437 QQuickItem::mouseReleaseEvent(event);
1441 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1443 Q_Q(QQuickPathView);
1445 q->setKeepMouseGrab(false);
1447 if (!interactive || !timer.isValid() || !model || !modelCount) {
1450 q->movementEnding();
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) {
1460 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1461 velocity = maxVel / (path->path().length()/count);
1463 // Calculate the distance to be travelled
1464 qreal v2 = velocity*velocity;
1465 qreal accel = deceleration/10;
1467 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1468 || snapMode != QQuickPathView::NoSnap)) {
1469 if (snapMode == QQuickPathView::SnapOneItem) {
1470 // encourage snapping one item in direction of motion
1472 dist = qRound(0.5 + offset) - offset;
1474 dist = qRound(0.5 - offset) + offset;
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));
1479 // round to nearest item.
1481 dist = qRound(dist + offset) - offset;
1483 dist = qRound(dist - offset) + offset;
1485 // Calculate accel required to stop on item boundary
1490 accel = v2 / (2.0f * qAbs(dist));
1493 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1495 flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1497 moveOffset.setValue(offset);
1498 tl.accel(moveOffset, velocity, accel, dist);
1499 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1502 emit q->flickingChanged();
1503 emit q->flickStarted();
1511 q->movementEnding();
1514 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1516 Q_D(QQuickPathView);
1517 QPointF localPos = mapFromScene(event->windowPos());
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);
1527 switch (mouseEvent.type()) {
1528 case QEvent::MouseMove:
1529 d->handleMouseMoveEvent(&mouseEvent);
1531 case QEvent::MouseButtonPress:
1532 d->handleMousePressEvent(&mouseEvent);
1533 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1535 case QEvent::MouseButtonRelease:
1536 d->handleMouseReleaseEvent(&mouseEvent);
1541 grabber = c->mouseGrabberItem();
1542 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1545 return d->stealMouse;
1546 } else if (d->timer.isValid()) {
1547 d->timer.invalidate();
1550 if (event->type() == QEvent::MouseButtonRelease)
1551 d->stealMouse = false;
1555 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1557 Q_D(QQuickPathView);
1558 if (!isVisible() || !d->interactive)
1559 return QQuickItem::childMouseEventFilter(i, e);
1561 switch (e->type()) {
1562 case QEvent::MouseButtonPress:
1563 case QEvent::MouseMove:
1564 case QEvent::MouseButtonRelease:
1565 return sendMouseEvent(static_cast<QMouseEvent *>(e));
1570 return QQuickItem::childMouseEventFilter(i, e);
1573 void QQuickPathView::mouseUngrabEvent()
1575 Q_D(QQuickPathView);
1576 if (d->stealMouse) {
1577 // if our mouse grab has been removed (probably by a Flickable),
1579 d->stealMouse = false;
1580 setKeepMouseGrab(false);
1581 d->timer.invalidate();
1583 d->setDragging(false);
1584 if (!d->tl.isActive())
1589 void QQuickPathView::updatePolish()
1591 QQuickItem::updatePolish();
1595 void QQuickPathView::componentComplete()
1597 Q_D(QQuickPathView);
1598 if (d->model && d->ownModel)
1599 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1601 QQuickItem::componentComplete();
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();
1611 d->updateHighlight();
1615 emit countChanged();
1618 void QQuickPathView::refill()
1620 Q_D(QQuickPathView);
1622 d->layoutScheduled = false;
1624 if (!d->isValid() || !isComponentComplete())
1627 bool currentVisible = false;
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;
1636 d->updateItem(item, pos);
1637 if (idx == d->currentIndex) {
1638 currentVisible = true;
1639 d->currentItemOffset = pos;
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)
1650 it = d->items.erase(it);
1653 if (idx >= d->modelCount)
1656 if (!d->items.count())
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;
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);
1681 if (d->currentIndex == idx) {
1682 currentVisible = true;
1683 d->currentItemOffset = pos;
1685 if (d->items.count() == 0)
1686 d->firstIndex = idx;
1687 d->items.append(item);
1688 d->updateItem(item, pos);
1690 if (idx >= d->modelCount)
1692 pos = d->positionOfIndex(idx);
1695 idx = d->firstIndex - 1;
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);
1706 if (d->currentIndex == idx) {
1707 currentVisible = true;
1708 d->currentItemOffset = pos;
1710 d->items.prepend(item);
1711 d->updateItem(item, pos);
1712 d->firstIndex = idx;
1713 idx = d->firstIndex - 1;
1715 idx = d->modelCount - 1;
1716 pos = d->positionOfIndex(idx);
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);
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);
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);
1750 while (d->itemCache.count())
1751 d->releaseItem(d->itemCache.takeLast());
1754 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1756 Q_D(QQuickPathView);
1757 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1761 d->modelCount = d->model->count();
1763 emit countChanged();
1767 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1770 const int modelCount = d->modelCount;
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.
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);
1790 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
1791 currentChanged = true;
1794 if (r.index > d->currentIndex) {
1795 changedOffset = true;
1796 d->offset -= r.count;
1797 d->offsetAdj -= r.count;
1799 d->modelCount -= r.count;
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;
1807 if (moveId != -1 && moveId == i.moveId) {
1808 d->currentIndex = i.index + moveOffset;
1809 currentChanged = true;
1811 if (i.index > d->currentIndex) {
1812 d->offset += i.count;
1813 d->offsetAdj += i.count;
1814 changedOffset = true;
1818 d->modelCount += i.count;
1821 d->offset = qmlMod(d->offset, d->modelCount);
1823 d->offset += d->modelCount;
1824 if (d->currentIndex == -1)
1825 d->currentIndex = d->calcCurrentIndex();
1827 d->itemCache += d->items;
1830 if (!d->modelCount) {
1831 while (d->itemCache.count())
1832 d->releaseItem(d->itemCache.takeLast());
1834 changedOffset = true;
1835 d->tl.reset(d->moveOffset);
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;
1842 d->updateMappedRange();
1843 d->scheduleLayout();
1846 emit offsetChanged();
1848 emit currentIndexChanged();
1849 if (d->modelCount != modelCount)
1850 emit countChanged();
1853 void QQuickPathView::destroyingItem(QQuickItem *item)
1858 void QQuickPathView::ticked()
1860 Q_D(QQuickPathView);
1864 void QQuickPathView::movementEnding()
1866 Q_D(QQuickPathView);
1868 d->flicking = false;
1869 emit flickingChanged();
1872 if (d->moving && !d->stealMouse) {
1874 emit movingChanged();
1875 emit movementEnded();
1879 // find the item closest to the snap position
1880 int QQuickPathViewPrivate::calcCurrentIndex()
1883 if (modelCount && model && items.count()) {
1884 offset = qmlMod(offset, modelCount);
1886 offset += modelCount;
1887 current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1888 current = current % modelCount;
1894 void QQuickPathViewPrivate::createCurrentItem()
1896 if (requestedIndex != -1)
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);
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);
1914 void QQuickPathViewPrivate::updateCurrent()
1916 Q_Q(QQuickPathView);
1917 if (moveReason == SetIndex)
1919 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
1922 int idx = calcCurrentIndex();
1923 if (model && idx != currentIndex) {
1925 if (QQuickPathViewAttached *att = attached(currentItem))
1926 att->setIsCurrentItem(false);
1927 releaseItem(currentItem);
1931 createCurrentItem();
1932 emit q->currentIndexChanged();
1936 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
1938 ((QQuickPathViewPrivate *)d)->fixOffset();
1941 void QQuickPathViewPrivate::fixOffset()
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);
1956 void QQuickPathViewPrivate::snapToIndex(int index)
1958 if (!model || modelCount <= 0)
1961 qreal targetOffset = qmlMod(modelCount - index, modelCount);
1963 if (offset == targetOffset)
1968 tl.reset(moveOffset);
1969 moveOffset.setValue(offset);
1971 const int duration = highlightMoveDuration;
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));
1982 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
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));
1991 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1994 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1996 moveDirection = Shortest;
1999 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
2001 return new QQuickPathViewAttached(obj);