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 "qquickcanvas.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), 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 FAST_CONNECT(&tl, SIGNAL(updated()), q, SLOT(ticked()))
143 FAST_CONNECT(&tl, SIGNAL(completed()), q, SLOT(movementEnding()))
146 QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
149 requestedIndex = modelIndex;
150 requestedOnPath = onPath;
153 QQuickItem *item = model->item(modelIndex, false);
155 QQml_setParent_noEvent(item, q);
156 item->setParentItem(q);
158 qPathViewAttachedType = attType;
159 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
160 qPathViewAttachedType = 0;
162 att->setOnPath(onPath);
163 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
164 itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
170 void QQuickPathView::createdItem(int index, QQuickItem *item)
173 if (d->requestedIndex != index) {
174 qPathViewAttachedType = d->attachedType();
175 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
176 qPathViewAttachedType = 0;
179 att->setOnPath(false);
181 item->setParentItem(this);
182 QQml_setParent_noEvent(item, 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())
439 \qmlclass PathView QQuickPathView
440 \inqmlmodule QtQuick 2
441 \ingroup qml-view-elements
442 \brief The PathView element lays out model-provided items on a path.
445 A PathView displays data from models created from built-in QML elements like ListModel
446 and XmlListModel, or custom model classes defined in C++ that inherit from
449 The view has a \l model, which defines the data to be displayed, and
450 a \l delegate, which defines how the data should be displayed.
451 The \l delegate is instantiated for each item on the \l path.
452 The items may be flicked to move them along the path.
454 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
456 \snippet doc/src/snippets/qml/pathview/ContactModel.qml 0
458 This data can be represented as a PathView, like this:
460 \snippet doc/src/snippets/qml/pathview/pathview.qml 0
464 (Note the above example uses PathAttribute to scale and modify the
465 opacity of the items as they rotate. This additional code can be seen in the
466 PathAttribute documentation.)
468 PathView does not automatically handle keyboard navigation. This is because
469 the keys to use for navigation will depend upon the shape of the path. Navigation
470 can be added quite simply by setting \c focus to \c true and calling
471 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
472 using the left and right arrow keys:
478 Keys.onLeftPressed: decrementCurrentIndex()
479 Keys.onRightPressed: incrementCurrentIndex()
483 The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
485 Delegates are instantiated as needed and may be destroyed at any time.
486 State should \e never be stored in a delegate.
488 PathView attaches a number of properties to the root item of the delegate, for example
489 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
490 this attached property directly as \c PathView.isCurrentItem, while the child
491 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
493 \snippet doc/src/snippets/qml/pathview/pathview.qml 1
495 \b Note that views do not enable \e clip automatically. If the view
496 is not clipped by another item or the screen, it will be necessary
497 to set \e {clip: true} in order to have the out of view items clipped
500 \sa Path, {declarative/modelviews/pathview}{PathView example}
503 QQuickPathView::QQuickPathView(QQuickItem *parent)
504 : QQuickItem(*(new QQuickPathViewPrivate), parent)
510 QQuickPathView::~QQuickPathView()
515 d->attType->release();
521 \qmlattachedproperty PathView QtQuick2::PathView::view
522 This attached property holds the view that manages this delegate instance.
524 It is attached to each instance of the delegate.
528 \qmlattachedproperty bool QtQuick2::PathView::onPath
529 This attached property holds whether the item is currently on the path.
531 If a pathItemCount has been set, it is possible that some items may
532 be instantiated, but not considered to be currently on the path.
533 Usually, these items would be set invisible, for example:
538 visible: PathView.onPath
544 It is attached to each instance of the delegate.
548 \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
549 This attached property is true if this delegate is the current item; otherwise false.
551 It is attached to each instance of the delegate.
553 This property may be used to adjust the appearance of the current item.
555 \snippet doc/src/snippets/qml/pathview/pathview.qml 1
559 \qmlproperty model QtQuick2::PathView::model
560 This property holds the model providing data for the view.
562 The model provides a set of data that is used to create the items for the view.
563 For large or dynamic datasets the model is usually provided by a C++ model object.
564 Models can also be created directly in QML, using the ListModel element.
566 \sa {qmlmodels}{Data Models}
568 QVariant QQuickPathView::model() const
570 Q_D(const QQuickPathView);
571 return d->modelVariant;
574 void QQuickPathView::setModel(const QVariant &model)
577 if (d->modelVariant == model)
581 disconnect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
582 this, SLOT(modelUpdated(QQuickChangeSet,bool)));
583 disconnect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
584 disconnect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
585 for (int i=0; i<d->items.count(); i++){
586 QQuickItem *p = d->items[i];
592 d->modelVariant = model;
593 QObject *object = qvariant_cast<QObject*>(model);
594 QQuickVisualModel *vim = 0;
595 if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
603 d->model = new QQuickVisualDataModel(qmlContext(this));
605 if (isComponentComplete())
606 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
608 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
609 dataModel->setModel(model);
613 connect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
614 this, SLOT(modelUpdated(QQuickChangeSet,bool)));
615 connect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
616 connect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
617 d->modelCount = d->model->count();
618 if (d->model->count())
619 d->offset = qmlMod(d->offset, qreal(d->model->count()));
621 d->offset = d->model->count() + d->offset;
624 if (d->currentIndex < d->modelCount)
625 setOffset(qmlMod(d->modelCount - d->currentIndex, d->modelCount));
633 \qmlproperty int QtQuick2::PathView::count
634 This property holds the number of items in the model.
636 int QQuickPathView::count() const
638 Q_D(const QQuickPathView);
639 return d->model ? d->modelCount : 0;
643 \qmlproperty Path QtQuick2::PathView::path
644 This property holds the path used to lay out the items.
645 For more information see the \l Path documentation.
647 QQuickPath *QQuickPathView::path() const
649 Q_D(const QQuickPathView);
653 void QQuickPathView::setPath(QQuickPath *path)
659 disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
661 connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
662 if (d->isValid() && isComponentComplete()) {
665 d->attType->release();
674 \qmlproperty int QtQuick2::PathView::currentIndex
675 This property holds the index of the current item.
677 int QQuickPathView::currentIndex() const
679 Q_D(const QQuickPathView);
680 return d->currentIndex;
683 void QQuickPathView::setCurrentIndex(int idx)
686 if (d->model && d->modelCount)
687 idx = qAbs(idx % d->modelCount);
688 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
689 if (d->currentItem) {
690 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
691 att->setIsCurrentItem(false);
692 d->releaseItem(d->currentItem);
694 int oldCurrentIdx = d->currentIndex;
695 QQuickItem *oldCurrentItem = d->currentItem;
697 d->moveReason = QQuickPathViewPrivate::SetIndex;
698 d->currentIndex = idx;
700 d->createCurrentItem();
701 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
702 d->snapToIndex(d->currentIndex);
703 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
704 d->updateHighlight();
706 if (oldCurrentIdx != d->currentIndex)
707 emit currentIndexChanged();
708 if (oldCurrentItem != d->currentItem)
709 emit currentItemChanged();
713 QQuickItem *QQuickPathView::currentItem() const
715 Q_D(const QQuickPathView);
716 return d->currentItem;
720 \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
722 Increments the current index.
724 \b Note: methods should only be called after the Component has completed.
726 void QQuickPathView::incrementCurrentIndex()
729 d->moveDirection = QQuickPathViewPrivate::Positive;
730 setCurrentIndex(currentIndex()+1);
734 \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
736 Decrements the current index.
738 \b Note: methods should only be called after the Component has completed.
740 void QQuickPathView::decrementCurrentIndex()
743 if (d->model && d->modelCount) {
744 int idx = currentIndex()-1;
746 idx = d->modelCount - 1;
747 d->moveDirection = QQuickPathViewPrivate::Negative;
748 setCurrentIndex(idx);
753 \qmlproperty real QtQuick2::PathView::offset
755 The offset specifies how far along the path the items are from their initial positions.
756 This is a real number that ranges from 0.0 to the count of items in the model.
758 qreal QQuickPathView::offset() const
760 Q_D(const QQuickPathView);
764 void QQuickPathView::setOffset(qreal offset)
767 d->setOffset(offset);
771 void QQuickPathViewPrivate::setOffset(qreal o)
775 if (isValid() && q->isComponentComplete()) {
776 offset = qmlMod(o, qreal(modelCount));
778 offset += qreal(modelCount);
783 emit q->offsetChanged();
787 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
789 setOffset(o+offsetAdj);
793 \qmlproperty Component QtQuick2::PathView::highlight
794 This property holds the component to use as the highlight.
796 An instance of the highlight component will be created for each view.
797 The geometry of the resultant component instance will be managed by the view
798 so as to stay with the current item.
800 The below example demonstrates how to make a simple highlight. Note the use
801 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
802 the highlight is hidden when flicked away from the path.
807 visible: PathView.onPath
813 \sa highlightItem, highlightRangeMode
816 QQmlComponent *QQuickPathView::highlight() const
818 Q_D(const QQuickPathView);
819 return d->highlightComponent;
822 void QQuickPathView::setHighlight(QQmlComponent *highlight)
825 if (highlight != d->highlightComponent) {
826 d->highlightComponent = highlight;
827 d->createHighlight();
828 d->updateHighlight();
829 emit highlightChanged();
834 \qmlproperty Item QtQuick2::PathView::highlightItem
836 \c highlightItem holds the highlight item, which was created
837 from the \l highlight component.
841 QQuickItem *QQuickPathView::highlightItem()
843 Q_D(const QQuickPathView);
844 return d->highlightItem;
847 \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
848 \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
849 \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
851 These properties set the preferred range of the highlight (current item)
852 within the view. The preferred values must be in the range 0.0-1.0.
854 If highlightRangeMode is set to \e PathView.NoHighlightRange
856 If highlightRangeMode is set to \e PathView.ApplyRange the view will
857 attempt to maintain the highlight within the range, however
858 the highlight can move outside of the range at the ends of the path
859 or due to a mouse interaction.
861 If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
862 move outside of the range. This means that the current item will change
863 if a keyboard or mouse action would cause the highlight to move
864 outside of the range.
866 Note that this is the correct way to influence where the
867 current item ends up when the view moves. For example, if you want the
868 currently selected item to be in the middle of the path, then set the
869 highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
870 Then, when the path scrolls,
871 the currently selected item will be the item at that position. This also applies to
872 when the currently selected item changes - it will scroll to within the preferred
873 highlight range. Furthermore, the behaviour of the current item index will occur
874 whether or not a highlight exists.
876 The default value is \e PathView.StrictlyEnforceRange.
878 Note that a valid range requires preferredHighlightEnd to be greater
879 than or equal to preferredHighlightBegin.
881 qreal QQuickPathView::preferredHighlightBegin() const
883 Q_D(const QQuickPathView);
884 return d->highlightRangeStart;
887 void QQuickPathView::setPreferredHighlightBegin(qreal start)
890 if (d->highlightRangeStart == start || start < 0 || start > 1.0)
892 d->highlightRangeStart = start;
893 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
895 emit preferredHighlightBeginChanged();
898 qreal QQuickPathView::preferredHighlightEnd() const
900 Q_D(const QQuickPathView);
901 return d->highlightRangeEnd;
904 void QQuickPathView::setPreferredHighlightEnd(qreal end)
907 if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
909 d->highlightRangeEnd = end;
910 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
912 emit preferredHighlightEndChanged();
915 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
917 Q_D(const QQuickPathView);
918 return d->highlightRangeMode;
921 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
924 if (d->highlightRangeMode == mode)
926 d->highlightRangeMode = mode;
927 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
928 if (d->haveHighlightRange) {
930 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
932 d->snapToIndex(index);
934 emit highlightRangeModeChanged();
938 \qmlproperty int QtQuick2::PathView::highlightMoveDuration
939 This property holds the move animation duration of the highlight delegate.
941 If the highlightRangeMode is StrictlyEnforceRange then this property
942 determines the speed that the items move along the path.
944 The default value for the duration is 300ms.
946 int QQuickPathView::highlightMoveDuration() const
948 Q_D(const QQuickPathView);
949 return d->highlightMoveDuration;
952 void QQuickPathView::setHighlightMoveDuration(int duration)
955 if (d->highlightMoveDuration == duration)
957 d->highlightMoveDuration = duration;
958 emit highlightMoveDurationChanged();
962 \qmlproperty real QtQuick2::PathView::dragMargin
963 This property holds the maximum distance from the path that initiate mouse dragging.
965 By default the path can only be dragged by clicking on an item. If
966 dragMargin is greater than zero, a drag can be initiated by clicking
967 within dragMargin pixels of the path.
969 qreal QQuickPathView::dragMargin() const
971 Q_D(const QQuickPathView);
972 return d->dragMargin;
975 void QQuickPathView::setDragMargin(qreal dragMargin)
978 if (d->dragMargin == dragMargin)
980 d->dragMargin = dragMargin;
981 emit dragMarginChanged();
985 \qmlproperty real QtQuick2::PathView::flickDeceleration
986 This property holds the rate at which a flick will decelerate.
990 qreal QQuickPathView::flickDeceleration() const
992 Q_D(const QQuickPathView);
993 return d->deceleration;
996 void QQuickPathView::setFlickDeceleration(qreal dec)
999 if (d->deceleration == dec)
1001 d->deceleration = dec;
1002 emit flickDecelerationChanged();
1006 \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1007 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1009 The default value is platform dependent.
1011 qreal QQuickPathView::maximumFlickVelocity() const
1013 Q_D(const QQuickPathView);
1014 return d->maximumFlickVelocity;
1017 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1019 Q_D(QQuickPathView);
1020 if (vel == d->maximumFlickVelocity)
1022 d->maximumFlickVelocity = vel;
1023 emit maximumFlickVelocityChanged();
1028 \qmlproperty bool QtQuick2::PathView::interactive
1030 A user cannot drag or flick a PathView that is not interactive.
1032 This property is useful for temporarily disabling flicking. This allows
1033 special interaction with PathView's children.
1035 bool QQuickPathView::isInteractive() const
1037 Q_D(const QQuickPathView);
1038 return d->interactive;
1041 void QQuickPathView::setInteractive(bool interactive)
1043 Q_D(QQuickPathView);
1044 if (interactive != d->interactive) {
1045 d->interactive = interactive;
1048 emit interactiveChanged();
1053 \qmlproperty bool QtQuick2::PathView::moving
1055 This property holds whether the view is currently moving
1056 due to the user either dragging or flicking the view.
1058 bool QQuickPathView::isMoving() const
1060 Q_D(const QQuickPathView);
1065 \qmlproperty bool QtQuick2::PathView::flicking
1067 This property holds whether the view is currently moving
1068 due to the user flicking the view.
1070 bool QQuickPathView::isFlicking() const
1072 Q_D(const QQuickPathView);
1077 \qmlsignal QtQuick2::PathView::onMovementStarted()
1079 This handler is called when the view begins moving due to user
1084 \qmlsignal QtQuick2::PathView::onMovementEnded()
1086 This handler is called when the view stops moving due to user
1087 interaction. If a flick was generated, this handler will
1088 be triggered once the flick stops. If a flick was not
1089 generated, the handler will be triggered when the
1090 user stops dragging - i.e. a mouse or touch release.
1094 \qmlsignal QtQuick2::PathView::onFlickStarted()
1096 This handler is called when the view is flicked. A flick
1097 starts from the point that the mouse or touch is released,
1098 while still in motion.
1102 \qmlsignal QtQuick2::PathView::onFlickEnded()
1104 This handler is called when the view stops moving due to a flick.
1108 \qmlproperty Component QtQuick2::PathView::delegate
1110 The delegate provides a template defining each item instantiated by the view.
1111 The index is exposed as an accessible \c index property. Properties of the
1112 model are also available depending upon the type of \l {qmlmodels}{Data Model}.
1114 The number of elements in the delegate has a direct effect on the
1115 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
1116 that is not needed for the normal display of the delegate in a \l Loader which
1117 can load additional elements when needed.
1119 Note that the PathView will layout the items based on the size of the root
1120 item in the delegate.
1122 Here is an example delegate:
1123 \snippet doc/src/snippets/qml/pathview/pathview.qml 1
1125 QQmlComponent *QQuickPathView::delegate() const
1127 Q_D(const QQuickPathView);
1129 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1130 return dataModel->delegate();
1136 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1138 Q_D(QQuickPathView);
1139 if (delegate == this->delegate())
1142 d->model = new QQuickVisualDataModel(qmlContext(this));
1145 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1146 int oldCount = dataModel->count();
1147 dataModel->setDelegate(delegate);
1148 d->modelCount = dataModel->count();
1150 if (oldCount != dataModel->count())
1151 emit countChanged();
1152 emit delegateChanged();
1157 \qmlproperty int QtQuick2::PathView::pathItemCount
1158 This property holds the number of items visible on the path at any one time.
1160 int QQuickPathView::pathItemCount() const
1162 Q_D(const QQuickPathView);
1163 return d->pathItems;
1166 void QQuickPathView::setPathItemCount(int i)
1168 Q_D(QQuickPathView);
1169 if (i == d->pathItems)
1174 d->updateMappedRange();
1175 if (d->isValid() && isComponentComplete()) {
1178 emit pathItemCountChanged();
1182 \qmlproperty enumeration QtQuick2::PathView::snapMode
1184 This property determines how the items will settle following a drag or flick.
1185 The possible values are:
1188 \li PathView.NoSnap (default) - the items stop anywhere along the path.
1189 \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
1190 \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
1191 \l preferredHighlightBegin at the time the press is released. This mode is particularly
1192 useful for moving one page at a time.
1195 \c snapMode does not affect the \l currentIndex. To update the
1196 \l currentIndex as the view is moved, set \l highlightRangeMode
1197 to \c PathView.StrictlyEnforceRange (default for PathView).
1199 \sa highlightRangeMode
1201 QQuickPathView::SnapMode QQuickPathView::snapMode() const
1203 Q_D(const QQuickPathView);
1207 void QQuickPathView::setSnapMode(SnapMode mode)
1209 Q_D(QQuickPathView);
1210 if (mode == d->snapMode)
1213 emit snapModeChanged();
1216 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1218 qreal samples = qMin(path->path().length()/5, qreal(500.0));
1219 qreal res = path->path().length()/samples;
1221 qreal mindist = 1e10; // big number
1222 QPointF nearPoint = path->pointAt(0);
1226 for (qreal i=1; i < samples; i++) {
1227 QPointF pt = path->pointAt(i/samples);
1228 QPointF diff = pt - point;
1229 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1230 if (dist < mindist) {
1238 qreal approxPc = nearPc;
1239 for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
1240 QPointF pt = path->pointAt(i/samples);
1241 QPointF diff = pt - point;
1242 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1243 if (dist < mindist) {
1251 *nearPercent = nearPc / samples;
1256 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1258 velocityBuffer.append(v);
1259 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1260 velocityBuffer.remove(0);
1263 qreal QQuickPathViewPrivate::calcVelocity() const
1266 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1267 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1268 for (int i = 0; i < count; ++i) {
1269 qreal v = velocityBuffer.at(i);
1277 qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
1279 if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
1280 return event->timestamp();
1283 return QQuickItemPrivate::elapsed(timer);
1286 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1288 Q_D(QQuickPathView);
1289 if (d->interactive) {
1290 d->handleMousePressEvent(event);
1293 QQuickItem::mousePressEvent(event);
1297 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1299 Q_Q(QQuickPathView);
1300 if (!interactive || !items.count() || !model || !modelCount)
1302 velocityBuffer.clear();
1304 for (; idx < items.count(); ++idx) {
1305 QQuickItem *item = items.at(idx);
1306 if (item->contains(item->mapFromScene(event->windowPos())))
1309 if (idx == items.count() && dragMargin == 0.) // didn't click on an item
1312 startPoint = pointNear(event->localPos(), &startPc);
1313 if (idx == items.count()) {
1314 qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y());
1315 if (distance > dragMargin)
1320 if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1321 stealMouse = true; // If we've been flicked then steal the click.
1325 QQuickItemPrivate::start(timer);
1326 lastPosTime = computeCurrentTime(event);
1330 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1332 Q_D(QQuickPathView);
1333 if (d->interactive) {
1334 d->handleMouseMoveEvent(event);
1336 setKeepMouseGrab(true);
1339 QQuickItem::mouseMoveEvent(event);
1343 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1345 Q_Q(QQuickPathView);
1346 if (!interactive || !timer.isValid() || !model || !modelCount)
1350 QPointF pathPoint = pointNear(event->localPos(), &newPc);
1352 QPointF delta = pathPoint - startPoint;
1353 if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1358 qint64 currentTimestamp = computeCurrentTime(event);
1360 moveReason = QQuickPathViewPrivate::Mouse;
1361 qreal diff = (newPc - startPc)*modelCount*mappedRange;
1363 q->setOffset(offset + diff);
1365 if (diff > modelCount/2)
1367 else if (diff < -modelCount/2)
1370 qint64 elapsed = currentTimestamp - lastPosTime;
1372 addVelocitySample(diff / (qreal(elapsed) / 1000.));
1376 emit q->movingChanged();
1377 emit q->movementStarted();
1381 lastPosTime = currentTimestamp;
1384 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1386 Q_D(QQuickPathView);
1387 if (d->interactive) {
1388 d->handleMouseReleaseEvent(event);
1392 QQuickItem::mouseReleaseEvent(event);
1396 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1398 Q_Q(QQuickPathView);
1400 q->setKeepMouseGrab(false);
1401 if (!interactive || !timer.isValid() || !model || !modelCount) {
1404 q->movementEnding();
1408 qreal velocity = calcVelocity();
1409 qreal count = modelCount*mappedRange;
1410 qreal pixelVelocity = (path->path().length()/count) * velocity;
1411 if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
1412 if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
1414 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1415 velocity = maxVel / (path->path().length()/count);
1417 // Calculate the distance to be travelled
1418 qreal v2 = velocity*velocity;
1419 qreal accel = deceleration/10;
1421 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1422 || snapMode != QQuickPathView::NoSnap)) {
1423 if (snapMode == QQuickPathView::SnapOneItem) {
1424 // encourage snapping one item in direction of motion
1426 dist = qRound(0.5 + offset) - offset;
1428 dist = qRound(0.5 - offset) + offset;
1430 // + 0.25 to encourage moving at least one item in the flick direction
1431 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1433 // round to nearest item.
1435 dist = qRound(dist + offset) - offset;
1437 dist = qRound(dist - offset) + offset;
1439 // Calculate accel required to stop on item boundary
1444 accel = v2 / (2.0f * qAbs(dist));
1447 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1449 flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1451 moveOffset.setValue(offset);
1452 tl.accel(moveOffset, velocity, accel, dist);
1453 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1456 emit q->flickingChanged();
1457 emit q->flickStarted();
1465 q->movementEnding();
1468 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1470 Q_D(QQuickPathView);
1471 QPointF localPos = mapFromScene(event->windowPos());
1473 QQuickCanvas *c = canvas();
1474 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
1475 bool stealThisEvent = d->stealMouse;
1476 if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
1477 QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
1478 event->button(), event->buttons(), event->modifiers());
1479 mouseEvent.setAccepted(false);
1481 switch (mouseEvent.type()) {
1482 case QEvent::MouseMove:
1483 d->handleMouseMoveEvent(&mouseEvent);
1485 case QEvent::MouseButtonPress:
1486 d->handleMousePressEvent(&mouseEvent);
1487 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1489 case QEvent::MouseButtonRelease:
1490 d->handleMouseReleaseEvent(&mouseEvent);
1495 grabber = c->mouseGrabberItem();
1496 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1499 return d->stealMouse;
1500 } else if (d->timer.isValid()) {
1501 d->timer.invalidate();
1504 if (event->type() == QEvent::MouseButtonRelease)
1505 d->stealMouse = false;
1509 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1511 Q_D(QQuickPathView);
1512 if (!isVisible() || !d->interactive)
1513 return QQuickItem::childMouseEventFilter(i, e);
1515 switch (e->type()) {
1516 case QEvent::MouseButtonPress:
1517 case QEvent::MouseMove:
1518 case QEvent::MouseButtonRelease:
1519 return sendMouseEvent(static_cast<QMouseEvent *>(e));
1524 return QQuickItem::childMouseEventFilter(i, e);
1527 void QQuickPathView::mouseUngrabEvent()
1529 Q_D(QQuickPathView);
1530 if (d->stealMouse) {
1531 // if our mouse grab has been removed (probably by a Flickable),
1533 d->stealMouse = false;
1534 setKeepMouseGrab(false);
1535 d->timer.invalidate();
1537 if (!d->tl.isActive())
1542 void QQuickPathView::updatePolish()
1544 QQuickItem::updatePolish();
1548 void QQuickPathView::componentComplete()
1550 Q_D(QQuickPathView);
1551 if (d->model && d->ownModel)
1552 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1554 QQuickItem::componentComplete();
1556 d->createHighlight();
1557 // It is possible that a refill has already happended to to Path
1558 // bindings being handled in the componentComplete(). If so
1559 // don't do it again.
1560 if (d->items.count() == 0 && d->model) {
1561 d->modelCount = d->model->count();
1564 d->updateHighlight();
1567 emit countChanged();
1570 void QQuickPathView::refill()
1572 Q_D(QQuickPathView);
1573 if (!d->isValid() || !isComponentComplete())
1576 d->layoutScheduled = false;
1577 bool currentVisible = false;
1579 // first move existing items and remove items off path
1580 int idx = d->firstIndex;
1581 QList<QQuickItem*>::iterator it = d->items.begin();
1582 while (it != d->items.end()) {
1583 qreal pos = d->positionOfIndex(idx);
1584 QQuickItem *item = *it;
1586 d->updateItem(item, pos);
1587 if (idx == d->currentIndex) {
1588 currentVisible = true;
1589 d->currentItemOffset = pos;
1593 // qDebug() << "release";
1594 d->updateItem(item, 1.0);
1595 d->releaseItem(item);
1596 if (it == d->items.begin()) {
1597 if (++d->firstIndex >= d->modelCount)
1600 it = d->items.erase(it);
1603 if (idx >= d->modelCount)
1606 if (!d->items.count())
1609 bool waiting = false;
1610 if (d->modelCount) {
1611 // add items to beginning and end
1612 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1613 if (d->items.count() < count) {
1614 int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1615 qreal startPos = 0.0;
1616 if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1617 || d->snapMode != QQuickPathView::NoSnap))
1618 startPos = d->highlightRangeStart;
1619 if (d->firstIndex >= 0) {
1620 startPos = d->positionOfIndex(d->firstIndex);
1621 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1623 qreal pos = d->positionOfIndex(idx);
1624 while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1625 // qDebug() << "append" << idx;
1626 QQuickItem *item = d->getItem(idx, idx+1);
1631 if (d->currentIndex == idx) {
1632 currentVisible = true;
1633 d->currentItemOffset = pos;
1635 if (d->items.count() == 0)
1636 d->firstIndex = idx;
1637 d->items.append(item);
1638 d->updateItem(item, pos);
1640 if (idx >= d->modelCount)
1642 pos = d->positionOfIndex(idx);
1645 idx = d->firstIndex - 1;
1647 idx = d->modelCount - 1;
1648 pos = d->positionOfIndex(idx);
1649 while (!waiting && (pos >= 0.0 && pos < startPos) && d->items.count() < count) {
1650 // qDebug() << "prepend" << idx;
1651 QQuickItem *item = d->getItem(idx, idx+1);
1656 if (d->currentIndex == idx) {
1657 currentVisible = true;
1658 d->currentItemOffset = pos;
1660 d->items.prepend(item);
1661 d->updateItem(item, pos);
1662 d->firstIndex = idx;
1663 idx = d->firstIndex - 1;
1665 idx = d->modelCount - 1;
1666 pos = d->positionOfIndex(idx);
1671 if (!currentVisible) {
1672 d->currentItemOffset = 1.0;
1673 if (d->currentItem) {
1674 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1675 att->setOnPath(false);
1676 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
1677 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
1678 d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
1679 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1680 att->setIsCurrentItem(true);
1683 } else if (!waiting && !d->currentItem) {
1684 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
1685 d->currentItem->setFocus(true);
1686 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1687 att->setIsCurrentItem(true);
1691 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1692 d->updateItem(d->highlightItem, d->highlightRangeStart);
1693 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1694 att->setOnPath(true);
1695 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
1696 d->updateItem(d->highlightItem, d->currentItemOffset);
1697 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1698 att->setOnPath(currentVisible);
1700 while (d->itemCache.count())
1701 d->releaseItem(d->itemCache.takeLast());
1704 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1706 Q_D(QQuickPathView);
1707 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1711 d->modelCount = d->model->count();
1713 emit countChanged();
1717 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1720 const int modelCount = d->modelCount;
1723 bool currentChanged = false;
1724 bool changedOffset = false;
1725 foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
1726 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
1727 d->currentIndex -= r.count;
1728 currentChanged = true;
1729 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
1730 // current item has been removed.
1731 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
1734 moveOffset = d->currentIndex - r.index;
1735 } else if (d->currentItem) {
1736 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1737 att->setIsCurrentItem(true);
1738 d->releaseItem(d->currentItem);
1741 currentChanged = true;
1744 if (r.index > d->currentIndex) {
1745 changedOffset = true;
1746 d->offset -= r.count;
1747 d->offsetAdj -= r.count;
1749 d->modelCount -= r.count;
1751 foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
1752 if (d->modelCount) {
1753 if (moveId == -1 && i.index <= d->currentIndex) {
1754 d->currentIndex += i.count;
1755 currentChanged = true;
1757 if (moveId != -1 && moveId == i.moveId) {
1758 d->currentIndex = i.index + moveOffset;
1759 currentChanged = true;
1761 if (i.index > d->currentIndex) {
1762 d->offset += i.count;
1763 d->offsetAdj += i.count;
1764 changedOffset = true;
1768 d->modelCount += i.count;
1771 d->offset = qmlMod(d->offset, d->modelCount);
1773 d->offset += d->modelCount;
1775 d->itemCache += d->items;
1778 if (!d->modelCount) {
1779 while (d->itemCache.count())
1780 d->releaseItem(d->itemCache.takeLast());
1782 changedOffset = true;
1783 d->tl.reset(d->moveOffset);
1785 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1786 d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
1787 changedOffset = true;
1790 d->updateMappedRange();
1791 d->scheduleLayout();
1794 emit offsetChanged();
1796 emit currentIndexChanged();
1797 if (d->modelCount != modelCount)
1798 emit countChanged();
1801 void QQuickPathView::destroyingItem(QQuickItem *item)
1806 void QQuickPathView::ticked()
1808 Q_D(QQuickPathView);
1812 void QQuickPathView::movementEnding()
1814 Q_D(QQuickPathView);
1816 d->flicking = false;
1817 emit flickingChanged();
1820 if (d->moving && !d->stealMouse) {
1822 emit movingChanged();
1823 emit movementEnded();
1827 // find the item closest to the snap position
1828 int QQuickPathViewPrivate::calcCurrentIndex()
1831 if (modelCount && model && items.count()) {
1832 offset = qmlMod(offset, modelCount);
1834 offset += modelCount;
1835 current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1836 current = current % modelCount;
1842 void QQuickPathViewPrivate::createCurrentItem()
1844 if (requestedIndex != -1)
1846 int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
1847 if (itemIndex < items.count()) {
1848 if ((currentItem = getItem(currentIndex, currentIndex, true))) {
1849 currentItem->setFocus(true);
1850 if (QQuickPathViewAttached *att = attached(currentItem))
1851 att->setIsCurrentItem(true);
1853 } else if (currentIndex >= 0 && currentIndex < modelCount) {
1854 if ((currentItem = getItem(currentIndex, currentIndex, false))) {
1855 updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
1856 if (QQuickPathViewAttached *att = attached(currentItem))
1857 att->setIsCurrentItem(true);
1862 void QQuickPathViewPrivate::updateCurrent()
1864 Q_Q(QQuickPathView);
1865 if (moveReason != Mouse)
1867 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
1870 int idx = calcCurrentIndex();
1871 if (model && idx != currentIndex) {
1873 if (QQuickPathViewAttached *att = attached(currentItem))
1874 att->setIsCurrentItem(false);
1875 releaseItem(currentItem);
1879 createCurrentItem();
1880 emit q->currentIndexChanged();
1884 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
1886 ((QQuickPathViewPrivate *)d)->fixOffset();
1889 void QQuickPathViewPrivate::fixOffset()
1891 Q_Q(QQuickPathView);
1892 if (model && items.count()) {
1893 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1894 || snapMode != QQuickPathView::NoSnap)) {
1895 int curr = calcCurrentIndex();
1896 if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
1897 q->setCurrentIndex(curr);
1904 void QQuickPathViewPrivate::snapToIndex(int index)
1906 if (!model || modelCount <= 0)
1909 qreal targetOffset = qmlMod(modelCount - index, modelCount);
1911 if (offset == targetOffset)
1916 tl.reset(moveOffset);
1917 moveOffset.setValue(offset);
1919 const int duration = highlightMoveDuration;
1922 tl.set(moveOffset, targetOffset);
1923 } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
1924 qreal distance = modelCount - targetOffset + offset;
1925 if (targetOffset > moveOffset) {
1926 tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
1927 tl.set(moveOffset, modelCount);
1928 tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
1930 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1932 } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
1933 qreal distance = modelCount - offset + targetOffset;
1934 if (targetOffset < moveOffset) {
1935 tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
1936 tl.set(moveOffset, 0.0);
1937 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
1939 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1942 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1944 moveDirection = Shortest;
1947 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
1949 return new QQuickPathViewAttached(obj);