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 3
64 // The number of samples to discard when calculating the flick velocity.
65 // Touch panels often produce inaccurate results as the finger is lifted.
66 #ifndef QML_FLICK_DISCARDSAMPLES
67 #define QML_FLICK_DISCARDSAMPLES 1
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), lastDist(0)
118 , lastElapsed(0), 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)
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()))
142 lastPosTime.invalidate();
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 start = highlightRangeStart;
271 qreal globalPos = index + offset;
272 globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
273 if (pathItems != -1 && pathItems < modelCount) {
274 globalPos += start * mappedRange;
275 globalPos = qmlMod(globalPos, 1.0);
276 if (globalPos < mappedRange)
277 pos = globalPos / mappedRange;
279 pos = qmlMod(globalPos + start, 1.0);
286 void QQuickPathViewPrivate::createHighlight()
289 if (!q->isComponentComplete())
292 bool changed = false;
294 highlightItem->setParentItem(0);
295 highlightItem->deleteLater();
300 QQuickItem *item = 0;
301 if (highlightComponent) {
302 QQmlContext *creationContext = highlightComponent->creationContext();
303 QQmlContext *highlightContext = new QQmlContext(
304 creationContext ? creationContext : qmlContext(q));
305 QObject *nobj = highlightComponent->create(highlightContext);
307 QQml_setParent_noEvent(highlightContext, nobj);
308 item = qobject_cast<QQuickItem *>(nobj);
312 delete highlightContext;
315 item = new QQuickItem;
318 QQml_setParent_noEvent(item, q);
319 item->setParentItem(q);
320 highlightItem = item;
324 emit q->highlightItemChanged();
327 void QQuickPathViewPrivate::updateHighlight()
330 if (!q->isComponentComplete() || !isValid())
333 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
334 updateItem(highlightItem, highlightRangeStart);
336 qreal target = currentIndex;
339 tl.reset(moveHighlight);
340 moveHighlight.setValue(highlightPosition);
342 const int duration = highlightMoveDuration;
344 if (target - highlightPosition > modelCount/2) {
346 qreal distance = modelCount - target + highlightPosition;
347 tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
348 tl.set(moveHighlight, modelCount-0.01);
349 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
350 } else if (target - highlightPosition <= -modelCount/2) {
352 qreal distance = modelCount - highlightPosition + target;
353 tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
354 tl.set(moveHighlight, 0.0);
355 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
357 highlightUp = highlightPosition - target < 0;
358 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
364 void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
366 if (pos != highlightPosition) {
369 if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
370 start = highlightRangeStart;
371 end = highlightRangeEnd;
374 qreal range = qreal(modelCount);
375 // calc normalized position of highlight relative to offset
376 qreal relativeHighlight = qmlMod(pos + offset, range) / range;
378 if (!highlightUp && relativeHighlight > end * mappedRange) {
379 qreal diff = 1.0 - relativeHighlight;
380 setOffset(offset + diff * range);
381 } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) {
382 qreal diff = relativeHighlight - (end - start) * mappedRange;
383 setOffset(offset - diff * range - 0.00001);
386 highlightPosition = pos;
387 qreal pathPos = positionOfIndex(pos);
388 updateItem(highlightItem, pathPos);
389 if (QQuickPathViewAttached *att = attached(highlightItem))
390 att->setOnPath(pathPos != -1.0);
394 void QQuickPathView::pathUpdated()
397 QList<QQuickItem*>::iterator it = d->items.begin();
398 while (it != d->items.end()) {
399 QQuickItem *item = *it;
400 if (QQuickPathViewAttached *att = d->attached(item))
407 void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
409 if (QQuickPathViewAttached *att = attached(item)) {
410 if (qFuzzyCompare(att->m_percent, percent))
412 att->m_percent = percent;
413 foreach (const QString &attr, path->attributes())
414 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
416 QPointF pf = path->pointAt(percent);
417 item->setX(pf.x() - item->width()/2);
418 item->setY(pf.y() - item->height()/2);
421 void QQuickPathViewPrivate::regenerate()
424 if (!q->isComponentComplete())
438 \qmlclass PathView QQuickPathView
439 \inqmlmodule QtQuick 2
440 \ingroup qml-view-elements
441 \brief The PathView element lays out model-provided items on a path.
444 A PathView displays data from models created from built-in QML elements like ListModel
445 and XmlListModel, or custom model classes defined in C++ that inherit from
448 The view has a \l model, which defines the data to be displayed, and
449 a \l delegate, which defines how the data should be displayed.
450 The \l delegate is instantiated for each item on the \l path.
451 The items may be flicked to move them along the path.
453 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
455 \snippet doc/src/snippets/qml/pathview/ContactModel.qml 0
457 This data can be represented as a PathView, like this:
459 \snippet doc/src/snippets/qml/pathview/pathview.qml 0
463 (Note the above example uses PathAttribute to scale and modify the
464 opacity of the items as they rotate. This additional code can be seen in the
465 PathAttribute documentation.)
467 PathView does not automatically handle keyboard navigation. This is because
468 the keys to use for navigation will depend upon the shape of the path. Navigation
469 can be added quite simply by setting \c focus to \c true and calling
470 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
471 using the left and right arrow keys:
477 Keys.onLeftPressed: decrementCurrentIndex()
478 Keys.onRightPressed: incrementCurrentIndex()
482 The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
484 Delegates are instantiated as needed and may be destroyed at any time.
485 State should \e never be stored in a delegate.
487 PathView attaches a number of properties to the root item of the delegate, for example
488 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
489 this attached property directly as \c PathView.isCurrentItem, while the child
490 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
492 \snippet doc/src/snippets/qml/pathview/pathview.qml 1
494 \b Note that views do not enable \e clip automatically. If the view
495 is not clipped by another item or the screen, it will be necessary
496 to set \e {clip: true} in order to have the out of view items clipped
499 \sa Path, {declarative/modelviews/pathview}{PathView example}
502 QQuickPathView::QQuickPathView(QQuickItem *parent)
503 : QQuickItem(*(new QQuickPathViewPrivate), parent)
509 QQuickPathView::~QQuickPathView()
514 d->attType->release();
520 \qmlattachedproperty PathView QtQuick2::PathView::view
521 This attached property holds the view that manages this delegate instance.
523 It is attached to each instance of the delegate.
527 \qmlattachedproperty bool QtQuick2::PathView::onPath
528 This attached property holds whether the item is currently on the path.
530 If a pathItemCount has been set, it is possible that some items may
531 be instantiated, but not considered to be currently on the path.
532 Usually, these items would be set invisible, for example:
537 visible: PathView.onPath
543 It is attached to each instance of the delegate.
547 \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
548 This attached property is true if this delegate is the current item; otherwise false.
550 It is attached to each instance of the delegate.
552 This property may be used to adjust the appearance of the current item.
554 \snippet doc/src/snippets/qml/pathview/pathview.qml 1
558 \qmlproperty model QtQuick2::PathView::model
559 This property holds the model providing data for the view.
561 The model provides a set of data that is used to create the items for the view.
562 For large or dynamic datasets the model is usually provided by a C++ model object.
563 Models can also be created directly in QML, using the ListModel element.
565 \sa {qmlmodels}{Data Models}
567 QVariant QQuickPathView::model() const
569 Q_D(const QQuickPathView);
570 return d->modelVariant;
573 void QQuickPathView::setModel(const QVariant &model)
576 if (d->modelVariant == model)
580 disconnect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
581 this, SLOT(modelUpdated(QQuickChangeSet,bool)));
582 disconnect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
583 disconnect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
584 for (int i=0; i<d->items.count(); i++){
585 QQuickItem *p = d->items[i];
591 d->modelVariant = model;
592 QObject *object = qvariant_cast<QObject*>(model);
593 QQuickVisualModel *vim = 0;
594 if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
602 d->model = new QQuickVisualDataModel(qmlContext(this));
604 if (isComponentComplete())
605 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
607 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
608 dataModel->setModel(model);
612 connect(d->model, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
613 this, SLOT(modelUpdated(QQuickChangeSet,bool)));
614 connect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
615 connect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
616 d->modelCount = d->model->count();
617 if (d->model->count())
618 d->offset = qmlMod(d->offset, qreal(d->model->count()));
620 d->offset = d->model->count() + d->offset;
623 if (d->currentIndex < d->modelCount)
624 setOffset(qmlMod(d->modelCount - d->currentIndex, d->modelCount));
632 \qmlproperty int QtQuick2::PathView::count
633 This property holds the number of items in the model.
635 int QQuickPathView::count() const
637 Q_D(const QQuickPathView);
638 return d->model ? d->modelCount : 0;
642 \qmlproperty Path QtQuick2::PathView::path
643 This property holds the path used to lay out the items.
644 For more information see the \l Path documentation.
646 QQuickPath *QQuickPathView::path() const
648 Q_D(const QQuickPathView);
652 void QQuickPathView::setPath(QQuickPath *path)
658 disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
660 connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
661 if (d->isValid() && isComponentComplete()) {
664 d->attType->release();
673 \qmlproperty int QtQuick2::PathView::currentIndex
674 This property holds the index of the current item.
676 int QQuickPathView::currentIndex() const
678 Q_D(const QQuickPathView);
679 return d->currentIndex;
682 void QQuickPathView::setCurrentIndex(int idx)
685 if (d->model && d->modelCount)
686 idx = qAbs(idx % d->modelCount);
687 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
688 if (d->currentItem) {
689 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
690 att->setIsCurrentItem(false);
691 d->releaseItem(d->currentItem);
693 int oldCurrentIdx = d->currentIndex;
694 QQuickItem *oldCurrentItem = d->currentItem;
696 d->moveReason = QQuickPathViewPrivate::SetIndex;
697 d->currentIndex = idx;
699 d->createCurrentItem();
700 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
702 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
703 d->updateHighlight();
705 if (oldCurrentIdx != d->currentIndex)
706 emit currentIndexChanged();
707 if (oldCurrentItem != d->currentItem)
708 emit currentItemChanged();
712 QQuickItem *QQuickPathView::currentItem() const
714 Q_D(const QQuickPathView);
715 return d->currentItem;
719 \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
721 Increments the current index.
723 \b Note: methods should only be called after the Component has completed.
725 void QQuickPathView::incrementCurrentIndex()
728 d->moveDirection = QQuickPathViewPrivate::Positive;
729 setCurrentIndex(currentIndex()+1);
733 \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
735 Decrements the current index.
737 \b Note: methods should only be called after the Component has completed.
739 void QQuickPathView::decrementCurrentIndex()
742 if (d->model && d->modelCount) {
743 int idx = currentIndex()-1;
745 idx = d->modelCount - 1;
746 d->moveDirection = QQuickPathViewPrivate::Negative;
747 setCurrentIndex(idx);
752 \qmlproperty real QtQuick2::PathView::offset
754 The offset specifies how far along the path the items are from their initial positions.
755 This is a real number that ranges from 0.0 to the count of items in the model.
757 qreal QQuickPathView::offset() const
759 Q_D(const QQuickPathView);
763 void QQuickPathView::setOffset(qreal offset)
766 d->setOffset(offset);
770 void QQuickPathViewPrivate::setOffset(qreal o)
774 if (isValid() && q->isComponentComplete()) {
775 offset = qmlMod(o, qreal(modelCount));
777 offset += qreal(modelCount);
782 emit q->offsetChanged();
786 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
788 setOffset(o+offsetAdj);
792 \qmlproperty Component QtQuick2::PathView::highlight
793 This property holds the component to use as the highlight.
795 An instance of the highlight component will be created for each view.
796 The geometry of the resultant component instance will be managed by the view
797 so as to stay with the current item.
799 The below example demonstrates how to make a simple highlight. Note the use
800 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
801 the highlight is hidden when flicked away from the path.
806 visible: PathView.onPath
812 \sa highlightItem, highlightRangeMode
815 QQmlComponent *QQuickPathView::highlight() const
817 Q_D(const QQuickPathView);
818 return d->highlightComponent;
821 void QQuickPathView::setHighlight(QQmlComponent *highlight)
824 if (highlight != d->highlightComponent) {
825 d->highlightComponent = highlight;
826 d->createHighlight();
827 d->updateHighlight();
828 emit highlightChanged();
833 \qmlproperty Item QtQuick2::PathView::highlightItem
835 \c highlightItem holds the highlight item, which was created
836 from the \l highlight component.
840 QQuickItem *QQuickPathView::highlightItem()
842 Q_D(const QQuickPathView);
843 return d->highlightItem;
846 \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
847 \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
848 \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
850 These properties set the preferred range of the highlight (current item)
851 within the view. The preferred values must be in the range 0.0-1.0.
853 If highlightRangeMode is set to \e PathView.NoHighlightRange
855 If highlightRangeMode is set to \e PathView.ApplyRange the view will
856 attempt to maintain the highlight within the range, however
857 the highlight can move outside of the range at the ends of the path
858 or due to a mouse interaction.
860 If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
861 move outside of the range. This means that the current item will change
862 if a keyboard or mouse action would cause the highlight to move
863 outside of the range.
865 Note that this is the correct way to influence where the
866 current item ends up when the view moves. For example, if you want the
867 currently selected item to be in the middle of the path, then set the
868 highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
869 Then, when the path scrolls,
870 the currently selected item will be the item at that position. This also applies to
871 when the currently selected item changes - it will scroll to within the preferred
872 highlight range. Furthermore, the behaviour of the current item index will occur
873 whether or not a highlight exists.
875 The default value is \e PathView.StrictlyEnforceRange.
877 Note that a valid range requires preferredHighlightEnd to be greater
878 than or equal to preferredHighlightBegin.
880 qreal QQuickPathView::preferredHighlightBegin() const
882 Q_D(const QQuickPathView);
883 return d->highlightRangeStart;
886 void QQuickPathView::setPreferredHighlightBegin(qreal start)
889 if (d->highlightRangeStart == start || start < 0 || start > 1.0)
891 d->highlightRangeStart = start;
892 d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
894 emit preferredHighlightBeginChanged();
897 qreal QQuickPathView::preferredHighlightEnd() const
899 Q_D(const QQuickPathView);
900 return d->highlightRangeEnd;
903 void QQuickPathView::setPreferredHighlightEnd(qreal end)
906 if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
908 d->highlightRangeEnd = end;
909 d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
911 emit preferredHighlightEndChanged();
914 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
916 Q_D(const QQuickPathView);
917 return d->highlightRangeMode;
920 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
923 if (d->highlightRangeMode == mode)
925 d->highlightRangeMode = mode;
926 d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
927 if (d->haveHighlightRange) {
931 emit highlightRangeModeChanged();
935 \qmlproperty int QtQuick2::PathView::highlightMoveDuration
936 This property holds the move animation duration of the highlight delegate.
938 If the highlightRangeMode is StrictlyEnforceRange then this property
939 determines the speed that the items move along the path.
941 The default value for the duration is 300ms.
943 int QQuickPathView::highlightMoveDuration() const
945 Q_D(const QQuickPathView);
946 return d->highlightMoveDuration;
949 void QQuickPathView::setHighlightMoveDuration(int duration)
952 if (d->highlightMoveDuration == duration)
954 d->highlightMoveDuration = duration;
955 emit highlightMoveDurationChanged();
959 \qmlproperty real QtQuick2::PathView::dragMargin
960 This property holds the maximum distance from the path that initiate mouse dragging.
962 By default the path can only be dragged by clicking on an item. If
963 dragMargin is greater than zero, a drag can be initiated by clicking
964 within dragMargin pixels of the path.
966 qreal QQuickPathView::dragMargin() const
968 Q_D(const QQuickPathView);
969 return d->dragMargin;
972 void QQuickPathView::setDragMargin(qreal dragMargin)
975 if (d->dragMargin == dragMargin)
977 d->dragMargin = dragMargin;
978 emit dragMarginChanged();
982 \qmlproperty real QtQuick2::PathView::flickDeceleration
983 This property holds the rate at which a flick will decelerate.
987 qreal QQuickPathView::flickDeceleration() const
989 Q_D(const QQuickPathView);
990 return d->deceleration;
993 void QQuickPathView::setFlickDeceleration(qreal dec)
996 if (d->deceleration == dec)
998 d->deceleration = dec;
999 emit flickDecelerationChanged();
1003 \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1004 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1006 The default value is platform dependent.
1008 qreal QQuickPathView::maximumFlickVelocity() const
1010 Q_D(const QQuickPathView);
1011 return d->maximumFlickVelocity;
1014 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1016 Q_D(QQuickPathView);
1017 if (vel == d->maximumFlickVelocity)
1019 d->maximumFlickVelocity = vel;
1020 emit maximumFlickVelocityChanged();
1025 \qmlproperty bool QtQuick2::PathView::interactive
1027 A user cannot drag or flick a PathView that is not interactive.
1029 This property is useful for temporarily disabling flicking. This allows
1030 special interaction with PathView's children.
1032 bool QQuickPathView::isInteractive() const
1034 Q_D(const QQuickPathView);
1035 return d->interactive;
1038 void QQuickPathView::setInteractive(bool interactive)
1040 Q_D(QQuickPathView);
1041 if (interactive != d->interactive) {
1042 d->interactive = interactive;
1045 emit interactiveChanged();
1050 \qmlproperty bool QtQuick2::PathView::moving
1052 This property holds whether the view is currently moving
1053 due to the user either dragging or flicking the view.
1055 bool QQuickPathView::isMoving() const
1057 Q_D(const QQuickPathView);
1062 \qmlproperty bool QtQuick2::PathView::flicking
1064 This property holds whether the view is currently moving
1065 due to the user flicking the view.
1067 bool QQuickPathView::isFlicking() const
1069 Q_D(const QQuickPathView);
1074 \qmlsignal QtQuick2::PathView::onMovementStarted()
1076 This handler is called when the view begins moving due to user
1081 \qmlsignal QtQuick2::PathView::onMovementEnded()
1083 This handler is called when the view stops moving due to user
1084 interaction. If a flick was generated, this handler will
1085 be triggered once the flick stops. If a flick was not
1086 generated, the handler will be triggered when the
1087 user stops dragging - i.e. a mouse or touch release.
1091 \qmlsignal QtQuick2::PathView::onFlickStarted()
1093 This handler is called when the view is flicked. A flick
1094 starts from the point that the mouse or touch is released,
1095 while still in motion.
1099 \qmlsignal QtQuick2::PathView::onFlickEnded()
1101 This handler is called when the view stops moving due to a flick.
1105 \qmlproperty Component QtQuick2::PathView::delegate
1107 The delegate provides a template defining each item instantiated by the view.
1108 The index is exposed as an accessible \c index property. Properties of the
1109 model are also available depending upon the type of \l {qmlmodels}{Data Model}.
1111 The number of elements in the delegate has a direct effect on the
1112 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
1113 that is not needed for the normal display of the delegate in a \l Loader which
1114 can load additional elements when needed.
1116 Note that the PathView will layout the items based on the size of the root
1117 item in the delegate.
1119 Here is an example delegate:
1120 \snippet doc/src/snippets/qml/pathview/pathview.qml 1
1122 QQmlComponent *QQuickPathView::delegate() const
1124 Q_D(const QQuickPathView);
1126 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1127 return dataModel->delegate();
1133 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1135 Q_D(QQuickPathView);
1136 if (delegate == this->delegate())
1139 d->model = new QQuickVisualDataModel(qmlContext(this));
1142 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1143 int oldCount = dataModel->count();
1144 dataModel->setDelegate(delegate);
1145 d->modelCount = dataModel->count();
1147 if (oldCount != dataModel->count())
1148 emit countChanged();
1149 emit delegateChanged();
1154 \qmlproperty int QtQuick2::PathView::pathItemCount
1155 This property holds the number of items visible on the path at any one time.
1157 int QQuickPathView::pathItemCount() const
1159 Q_D(const QQuickPathView);
1160 return d->pathItems;
1163 void QQuickPathView::setPathItemCount(int i)
1165 Q_D(QQuickPathView);
1166 if (i == d->pathItems)
1171 d->updateMappedRange();
1172 if (d->isValid() && isComponentComplete()) {
1175 emit pathItemCountChanged();
1178 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1180 qreal samples = qMin(path->path().length()/5, qreal(500.0));
1181 qreal res = path->path().length()/samples;
1183 qreal mindist = 1e10; // big number
1184 QPointF nearPoint = path->pointAt(0);
1188 for (qreal i=1; i < samples; i++) {
1189 QPointF pt = path->pointAt(i/samples);
1190 QPointF diff = pt - point;
1191 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1192 if (dist < mindist) {
1200 qreal approxPc = nearPc;
1201 for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
1202 QPointF pt = path->pointAt(i/samples);
1203 QPointF diff = pt - point;
1204 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1205 if (dist < mindist) {
1213 *nearPercent = nearPc / samples;
1218 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1220 velocityBuffer.append(v);
1221 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1222 velocityBuffer.remove(0);
1225 qreal QQuickPathViewPrivate::calcVelocity() const
1228 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1229 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1230 for (int i = 0; i < count; ++i) {
1231 qreal v = velocityBuffer.at(i);
1239 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1241 Q_D(QQuickPathView);
1242 if (d->interactive) {
1243 d->handleMousePressEvent(event);
1246 QQuickItem::mousePressEvent(event);
1250 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1252 Q_Q(QQuickPathView);
1253 if (!interactive || !items.count() || !model || !modelCount)
1255 velocityBuffer.clear();
1256 QPointF scenePoint = q->mapToScene(event->localPos());
1258 for (; idx < items.count(); ++idx) {
1259 QRectF rect = items.at(idx)->boundingRect();
1260 rect = items.at(idx)->mapRectToScene(rect);
1261 if (rect.contains(scenePoint))
1264 if (idx == items.count() && dragMargin == 0.) // didn't click on an item
1267 startPoint = pointNear(event->localPos(), &startPc);
1268 if (idx == items.count()) {
1269 qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y());
1270 if (distance > dragMargin)
1275 if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1276 stealMouse = true; // If we've been flicked then steal the click.
1282 QQuickItemPrivate::start(lastPosTime);
1286 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1288 Q_D(QQuickPathView);
1289 if (d->interactive) {
1290 d->handleMouseMoveEvent(event);
1292 setKeepMouseGrab(true);
1295 QQuickItem::mouseMoveEvent(event);
1299 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1301 Q_Q(QQuickPathView);
1302 if (!interactive || !lastPosTime.isValid() || !model || !modelCount)
1306 QPointF pathPoint = pointNear(event->localPos(), &newPc);
1308 QPointF delta = pathPoint - startPoint;
1309 if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1316 moveReason = QQuickPathViewPrivate::Mouse;
1317 qreal diff = (newPc - startPc)*modelCount*mappedRange;
1319 q->setOffset(offset + diff);
1321 if (diff > modelCount/2)
1323 else if (diff < -modelCount/2)
1326 lastElapsed = QQuickItemPrivate::restart(lastPosTime);
1329 addVelocitySample(diff / (qreal(lastElapsed) / 1000.));
1333 emit q->movingChanged();
1334 emit q->movementStarted();
1339 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1341 Q_D(QQuickPathView);
1342 if (d->interactive) {
1343 d->handleMouseReleaseEvent(event);
1347 QQuickItem::mouseReleaseEvent(event);
1351 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1353 Q_Q(QQuickPathView);
1355 q->setKeepMouseGrab(false);
1356 if (!interactive || !lastPosTime.isValid() || !model || !modelCount) {
1357 lastPosTime.invalidate();
1359 q->movementEnding();
1363 qreal velocity = calcVelocity();
1364 qreal count = modelCount*mappedRange;
1365 qreal pixelVelocity = (path->path().length()/count) * velocity;
1366 if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
1367 if (qAbs(pixelVelocity) > maximumFlickVelocity) {
1369 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1370 velocity = maxVel / (path->path().length()/count);
1372 // Calculate the distance to be travelled
1373 qreal v2 = velocity*velocity;
1374 qreal accel = deceleration/10;
1376 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1377 // + 0.25 to encourage moving at least one item in the flick direction
1378 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1379 // round to nearest item.
1381 dist = qRound(dist + offset) - offset;
1383 dist = qRound(dist - offset) + offset;
1384 // Calculate accel required to stop on item boundary
1389 accel = v2 / (2.0f * qAbs(dist));
1392 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1394 flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1396 moveOffset.setValue(offset);
1397 tl.accel(moveOffset, velocity, accel, dist);
1398 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1401 emit q->flickingChanged();
1402 emit q->flickStarted();
1408 lastPosTime.invalidate();
1410 q->movementEnding();
1413 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1415 Q_D(QQuickPathView);
1416 QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
1417 QQuickCanvas *c = canvas();
1418 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
1419 bool stealThisEvent = d->stealMouse;
1420 if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
1421 QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
1422 event->button(), event->buttons(), event->modifiers());
1423 mouseEvent.setAccepted(false);
1425 switch (mouseEvent.type()) {
1426 case QEvent::MouseMove:
1427 d->handleMouseMoveEvent(&mouseEvent);
1429 case QEvent::MouseButtonPress:
1430 d->handleMousePressEvent(&mouseEvent);
1431 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1433 case QEvent::MouseButtonRelease:
1434 d->handleMouseReleaseEvent(&mouseEvent);
1439 grabber = c->mouseGrabberItem();
1440 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1443 return d->stealMouse;
1444 } else if (d->lastPosTime.isValid()) {
1445 d->lastPosTime.invalidate();
1448 if (event->type() == QEvent::MouseButtonRelease)
1449 d->stealMouse = false;
1453 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1455 Q_D(QQuickPathView);
1456 if (!isVisible() || !d->interactive)
1457 return QQuickItem::childMouseEventFilter(i, e);
1459 switch (e->type()) {
1460 case QEvent::MouseButtonPress:
1461 case QEvent::MouseMove:
1462 case QEvent::MouseButtonRelease:
1463 return sendMouseEvent(static_cast<QMouseEvent *>(e));
1468 return QQuickItem::childMouseEventFilter(i, e);
1471 void QQuickPathView::mouseUngrabEvent()
1473 Q_D(QQuickPathView);
1474 if (d->stealMouse) {
1475 // if our mouse grab has been removed (probably by a Flickable),
1477 d->stealMouse = false;
1478 setKeepMouseGrab(false);
1479 d->lastPosTime.invalidate();
1481 if (!d->tl.isActive())
1486 void QQuickPathView::updatePolish()
1488 QQuickItem::updatePolish();
1492 void QQuickPathView::componentComplete()
1494 Q_D(QQuickPathView);
1495 if (d->model && d->ownModel)
1496 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1498 QQuickItem::componentComplete();
1500 d->createHighlight();
1501 // It is possible that a refill has already happended to to Path
1502 // bindings being handled in the componentComplete(). If so
1503 // don't do it again.
1504 if (d->items.count() == 0 && d->model) {
1505 d->modelCount = d->model->count();
1508 d->updateHighlight();
1511 emit countChanged();
1514 void QQuickPathView::refill()
1516 Q_D(QQuickPathView);
1517 if (!d->isValid() || !isComponentComplete())
1520 d->layoutScheduled = false;
1521 bool currentVisible = false;
1523 // first move existing items and remove items off path
1524 int idx = d->firstIndex;
1525 QList<QQuickItem*>::iterator it = d->items.begin();
1526 while (it != d->items.end()) {
1527 qreal pos = d->positionOfIndex(idx);
1528 QQuickItem *item = *it;
1530 d->updateItem(item, pos);
1531 if (idx == d->currentIndex) {
1532 currentVisible = true;
1533 d->currentItemOffset = pos;
1537 // qDebug() << "release";
1538 d->updateItem(item, 1.0);
1539 d->releaseItem(item);
1540 if (it == d->items.begin()) {
1541 if (++d->firstIndex >= d->modelCount)
1544 it = d->items.erase(it);
1547 if (idx >= d->modelCount)
1550 if (!d->items.count())
1553 bool waiting = false;
1554 if (d->modelCount) {
1555 // add items to beginning and end
1556 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1557 if (d->items.count() < count) {
1558 int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1559 qreal startPos = 0.0;
1560 if (d->haveHighlightRange && d->highlightRangeMode != QQuickPathView::NoHighlightRange)
1561 startPos = d->highlightRangeStart;
1562 if (d->firstIndex >= 0) {
1563 startPos = d->positionOfIndex(d->firstIndex);
1564 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1566 qreal pos = d->positionOfIndex(idx);
1567 while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1568 // qDebug() << "append" << idx;
1569 QQuickItem *item = d->getItem(idx, idx+1);
1574 if (d->currentIndex == idx) {
1575 currentVisible = true;
1576 d->currentItemOffset = pos;
1578 if (d->items.count() == 0)
1579 d->firstIndex = idx;
1580 d->items.append(item);
1581 d->updateItem(item, pos);
1583 if (idx >= d->modelCount)
1585 pos = d->positionOfIndex(idx);
1588 idx = d->firstIndex - 1;
1590 idx = d->modelCount - 1;
1591 pos = d->positionOfIndex(idx);
1592 while (!waiting && (pos >= 0.0 && pos < startPos) && d->items.count() < count) {
1593 // qDebug() << "prepend" << idx;
1594 QQuickItem *item = d->getItem(idx, idx+1);
1599 if (d->currentIndex == idx) {
1600 currentVisible = true;
1601 d->currentItemOffset = pos;
1603 d->items.prepend(item);
1604 d->updateItem(item, pos);
1605 d->firstIndex = idx;
1606 idx = d->firstIndex - 1;
1608 idx = d->modelCount - 1;
1609 pos = d->positionOfIndex(idx);
1614 if (!currentVisible) {
1615 d->currentItemOffset = 1.0;
1616 if (d->currentItem) {
1617 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1618 att->setOnPath(false);
1619 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
1620 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
1621 d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
1622 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1623 att->setIsCurrentItem(true);
1626 } else if (!waiting && !d->currentItem) {
1627 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
1628 d->currentItem->setFocus(true);
1629 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1630 att->setIsCurrentItem(true);
1634 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1635 d->updateItem(d->highlightItem, d->highlightRangeStart);
1636 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1637 att->setOnPath(true);
1638 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
1639 d->updateItem(d->highlightItem, d->currentItemOffset);
1640 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1641 att->setOnPath(currentVisible);
1643 while (d->itemCache.count())
1644 d->releaseItem(d->itemCache.takeLast());
1647 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1649 Q_D(QQuickPathView);
1650 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1654 d->modelCount = d->model->count();
1656 emit countChanged();
1660 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1663 const int modelCount = d->modelCount;
1666 bool currentChanged = false;
1667 bool changedOffset = false;
1668 foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
1669 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
1670 d->currentIndex -= r.count;
1671 currentChanged = true;
1672 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
1673 // current item has been removed.
1674 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
1677 moveOffset = d->currentIndex - r.index;
1678 } else if (d->currentItem) {
1679 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1680 att->setIsCurrentItem(true);
1681 d->releaseItem(d->currentItem);
1684 currentChanged = true;
1687 if (r.index > d->currentIndex) {
1688 changedOffset = true;
1689 d->offset -= r.count;
1690 d->offsetAdj -= r.count;
1692 d->modelCount -= r.count;
1694 foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
1695 if (d->modelCount) {
1696 if (moveId == -1 && i.index <= d->currentIndex) {
1697 d->currentIndex += i.count;
1698 currentChanged = true;
1700 if (moveId != -1 && moveId == i.moveId) {
1701 d->currentIndex = i.index + moveOffset;
1702 currentChanged = true;
1704 if (i.index > d->currentIndex) {
1705 d->offset += i.count;
1706 d->offsetAdj += i.count;
1707 changedOffset = true;
1711 d->modelCount += i.count;
1714 d->offset = qmlMod(d->offset, d->modelCount);
1716 d->offset += d->modelCount;
1718 d->itemCache += d->items;
1721 if (!d->modelCount) {
1722 while (d->itemCache.count())
1723 d->releaseItem(d->itemCache.takeLast());
1725 changedOffset = true;
1726 d->tl.reset(d->moveOffset);
1728 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1729 d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
1730 changedOffset = true;
1733 d->updateMappedRange();
1734 d->scheduleLayout();
1737 emit offsetChanged();
1739 emit currentIndexChanged();
1740 if (d->modelCount != modelCount)
1741 emit countChanged();
1744 void QQuickPathView::destroyingItem(QQuickItem *item)
1749 void QQuickPathView::ticked()
1751 Q_D(QQuickPathView);
1755 void QQuickPathView::movementEnding()
1757 Q_D(QQuickPathView);
1759 d->flicking = false;
1760 emit flickingChanged();
1763 if (d->moving && !d->stealMouse) {
1765 emit movingChanged();
1766 emit movementEnded();
1770 // find the item closest to the snap position
1771 int QQuickPathViewPrivate::calcCurrentIndex()
1774 if (modelCount && model && items.count()) {
1775 offset = qmlMod(offset, modelCount);
1777 offset += modelCount;
1778 current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1779 current = current % modelCount;
1785 void QQuickPathViewPrivate::createCurrentItem()
1787 if (requestedIndex != -1)
1789 int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
1790 if (itemIndex < items.count()) {
1791 if ((currentItem = getItem(currentIndex, currentIndex, true))) {
1792 currentItem->setFocus(true);
1793 if (QQuickPathViewAttached *att = attached(currentItem))
1794 att->setIsCurrentItem(true);
1796 } else if (currentIndex >= 0 && currentIndex < modelCount) {
1797 if ((currentItem = getItem(currentIndex, currentIndex, false))) {
1798 updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
1799 if (QQuickPathViewAttached *att = attached(currentItem))
1800 att->setIsCurrentItem(true);
1805 void QQuickPathViewPrivate::updateCurrent()
1807 Q_Q(QQuickPathView);
1808 if (moveReason != Mouse)
1810 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
1813 int idx = calcCurrentIndex();
1814 if (model && idx != currentIndex) {
1816 if (QQuickPathViewAttached *att = attached(currentItem))
1817 att->setIsCurrentItem(false);
1818 releaseItem(currentItem);
1822 createCurrentItem();
1823 emit q->currentIndexChanged();
1827 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
1829 ((QQuickPathViewPrivate *)d)->fixOffset();
1832 void QQuickPathViewPrivate::fixOffset()
1834 Q_Q(QQuickPathView);
1835 if (model && items.count()) {
1836 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1837 int curr = calcCurrentIndex();
1838 if (curr != currentIndex)
1839 q->setCurrentIndex(curr);
1846 void QQuickPathViewPrivate::snapToCurrent()
1848 if (!model || modelCount <= 0)
1851 qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
1853 if (offset == targetOffset)
1858 tl.reset(moveOffset);
1859 moveOffset.setValue(offset);
1861 const int duration = highlightMoveDuration;
1864 tl.set(moveOffset, targetOffset);
1865 } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
1866 qreal distance = modelCount - targetOffset + offset;
1867 if (targetOffset > moveOffset) {
1868 tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
1869 tl.set(moveOffset, modelCount);
1870 tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
1872 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1874 } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
1875 qreal distance = modelCount - offset + targetOffset;
1876 if (targetOffset < moveOffset) {
1877 tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
1878 tl.set(moveOffset, 0.0);
1879 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
1881 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1884 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1886 moveDirection = Shortest;
1889 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
1891 return new QQuickPathViewAttached(obj);