1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickpathview_p.h"
43 #include "qquickpathview_p_p.h"
44 #include "qquickwindow.h"
46 #include <QtQuick/private/qquickstate_p.h>
47 #include <private/qqmlglobal_p.h>
48 #include <private/qqmlopenmetaobject_p.h>
49 #include <private/qquickchangeset_p.h>
51 #include <QtGui/qevent.h>
52 #include <QtGui/qevent.h>
53 #include <QtGui/qguiapplication.h>
54 #include <QtGui/qstylehints.h>
55 #include <QtCore/qmath.h>
58 // The number of samples to use in calculating the velocity of a flick
59 #ifndef QML_FLICK_SAMPLEBUFFER
60 #define QML_FLICK_SAMPLEBUFFER 1
63 // The number of samples to discard when calculating the flick velocity.
64 // Touch panels often produce inaccurate results as the finger is lifted.
65 #ifndef QML_FLICK_DISCARDSAMPLES
66 #define QML_FLICK_DISCARDSAMPLES 0
69 // The default maximum velocity of a flick.
70 #ifndef QML_FLICK_DEFAULTMAXVELOCITY
71 #define QML_FLICK_DEFAULTMAXVELOCITY 2500
77 const qreal MinimumFlickVelocity = 75.0;
79 inline qreal qmlMod(qreal x, qreal y)
81 #ifdef QT_USE_MATH_H_FLOATS
82 if (sizeof(qreal) == sizeof(float))
83 return fmodf(float(x), float(y));
89 static QQmlOpenMetaObjectType *qPathViewAttachedType = 0;
91 QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
92 : QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
94 if (qPathViewAttachedType) {
95 m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
96 m_metaobject->setCached(true);
98 m_metaobject = new QQmlOpenMetaObject(this);
102 QQuickPathViewAttached::~QQuickPathViewAttached()
106 QVariant QQuickPathViewAttached::value(const QByteArray &name) const
108 return m_metaobject->value(name);
110 void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
112 m_metaobject->setValue(name, val);
115 QQuickPathViewPrivate::QQuickPathViewPrivate()
116 : path(0), currentIndex(0), currentItemOffset(0.0), startPc(0)
117 , offset(0.0), offsetAdj(0.0), mappedRange(1.0)
118 , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
119 , autoHighlight(true), highlightUp(false), layoutScheduled(false)
120 , moving(false), flicking(false), dragging(false), requestedOnPath(false), inRequest(false)
121 , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY)
122 , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
123 , firstIndex(-1), pathItems(-1), requestedIndex(-1), requestedZ(0)
124 , moveReason(Other), moveDirection(Shortest), attType(0), highlightComponent(0), highlightItem(0)
125 , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
126 , highlightPosition(0)
127 , highlightRangeStart(0), highlightRangeEnd(0)
128 , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
129 , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
133 void QQuickPathViewPrivate::init()
137 q->setAcceptedMouseButtons(Qt::LeftButton);
138 q->setFlag(QQuickItem::ItemIsFocusScope);
139 q->setFiltersChildMouseEvents(true);
140 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()),
141 q, QQuickPathView, SLOT(ticked()))
143 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
144 q, QQuickPathView, SLOT(movementEnding()))
147 QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool onPath)
150 requestedIndex = modelIndex;
151 requestedOnPath = onPath;
154 QQuickItem *item = model->item(modelIndex, false);
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 d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
184 d->requestedIndex = -1;
190 void QQuickPathView::initItem(int index, QQuickItem *item)
193 if (d->requestedIndex == index) {
194 item->setParentItem(this);
195 qPathViewAttachedType = d->attachedType();
196 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
197 qPathViewAttachedType = 0;
200 qreal percent = d->positionOfIndex(index);
201 foreach (const QString &attr, d->path->attributes())
202 att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
203 item->setZ(d->requestedZ);
205 att->setOnPath(d->requestedOnPath);
210 void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
214 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
215 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
216 if (model->release(item) == 0) {
217 // item was not destroyed, and we no longer reference it.
218 if (QQuickPathViewAttached *att = attached(item))
219 att->setOnPath(false);
223 QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
225 return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
228 QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
232 // pre-create one metatype to share with all attached objects
233 attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
234 foreach (const QString &attr, path->attributes())
235 attType->createProperty(attr.toUtf8());
241 void QQuickPathViewPrivate::clear()
244 releaseItem(currentItem);
247 for (int i=0; i<items.count(); i++){
248 QQuickItem *p = items[i];
255 void QQuickPathViewPrivate::updateMappedRange()
257 if (model && pathItems != -1 && pathItems < modelCount)
258 mappedRange = qreal(pathItems)/modelCount;
263 qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
267 if (model && index >= 0 && index < modelCount) {
269 if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
270 || snapMode != QQuickPathView::NoSnap))
271 start = highlightRangeStart;
272 qreal globalPos = index + offset;
273 globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
274 if (pathItems != -1 && pathItems < modelCount) {
275 globalPos += start * mappedRange;
276 globalPos = qmlMod(globalPos, 1.0);
277 if (globalPos < mappedRange)
278 pos = globalPos / mappedRange;
280 pos = qmlMod(globalPos + start, 1.0);
287 void QQuickPathViewPrivate::createHighlight()
290 if (!q->isComponentComplete())
293 bool changed = false;
295 highlightItem->setParentItem(0);
296 highlightItem->deleteLater();
301 QQuickItem *item = 0;
302 if (highlightComponent) {
303 QQmlContext *creationContext = highlightComponent->creationContext();
304 QQmlContext *highlightContext = new QQmlContext(
305 creationContext ? creationContext : qmlContext(q));
306 QObject *nobj = highlightComponent->create(highlightContext);
308 QQml_setParent_noEvent(highlightContext, nobj);
309 item = qobject_cast<QQuickItem *>(nobj);
313 delete highlightContext;
316 item = new QQuickItem;
319 QQml_setParent_noEvent(item, q);
320 item->setParentItem(q);
321 highlightItem = item;
325 emit q->highlightItemChanged();
328 void QQuickPathViewPrivate::updateHighlight()
331 if (!q->isComponentComplete() || !isValid())
334 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
335 updateItem(highlightItem, highlightRangeStart);
337 qreal target = currentIndex;
340 tl.reset(moveHighlight);
341 moveHighlight.setValue(highlightPosition);
343 const int duration = highlightMoveDuration;
345 if (target - highlightPosition > modelCount/2) {
347 qreal distance = modelCount - target + highlightPosition;
348 tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
349 tl.set(moveHighlight, modelCount-0.01);
350 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
351 } else if (target - highlightPosition <= -modelCount/2) {
353 qreal distance = modelCount - highlightPosition + target;
354 tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
355 tl.set(moveHighlight, 0.0);
356 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
358 highlightUp = highlightPosition - target < 0;
359 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
365 void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
367 if (pos != highlightPosition) {
370 if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
371 start = highlightRangeStart;
372 end = highlightRangeEnd;
375 qreal range = qreal(modelCount);
376 // calc normalized position of highlight relative to offset
377 qreal relativeHighlight = qmlMod(pos + offset, range) / range;
379 if (!highlightUp && relativeHighlight > end * mappedRange) {
380 qreal diff = 1.0 - relativeHighlight;
381 setOffset(offset + diff * range);
382 } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) {
383 qreal diff = relativeHighlight - (end - start) * mappedRange;
384 setOffset(offset - diff * range - 0.00001);
387 highlightPosition = pos;
388 qreal pathPos = positionOfIndex(pos);
389 updateItem(highlightItem, pathPos);
390 if (QQuickPathViewAttached *att = attached(highlightItem))
391 att->setOnPath(pathPos != -1.0);
395 void QQuickPathView::pathUpdated()
398 QList<QQuickItem*>::iterator it = d->items.begin();
399 while (it != d->items.end()) {
400 QQuickItem *item = *it;
401 if (QQuickPathViewAttached *att = d->attached(item))
408 void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
410 if (QQuickPathViewAttached *att = attached(item)) {
411 if (qFuzzyCompare(att->m_percent, percent))
413 att->m_percent = percent;
414 foreach (const QString &attr, path->attributes())
415 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
417 QPointF pf = path->pointAt(percent);
418 item->setX(pf.x() - item->width()/2);
419 item->setY(pf.y() - item->height()/2);
422 void QQuickPathViewPrivate::regenerate()
425 if (!q->isComponentComplete())
438 void QQuickPathViewPrivate::setDragging(bool d)
446 emit q->dragStarted();
450 emit q->draggingChanged();
455 \instantiates QQuickPathView
456 \inqmlmodule QtQuick 2
457 \ingroup qtquick-paths
458 \ingroup qtquick-views
460 \brief Lays out model-provided items on a path
462 A PathView displays data from models created from built-in QML types like ListModel
463 and XmlListModel, or custom model classes defined in C++ that inherit from
466 The view has a \l model, which defines the data to be displayed, and
467 a \l delegate, which defines how the data should be displayed.
468 The \l delegate is instantiated for each item on the \l path.
469 The items may be flicked to move them along the path.
471 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
473 \snippet qml/pathview/ContactModel.qml 0
475 This data can be represented as a PathView, like this:
477 \snippet qml/pathview/pathview.qml 0
481 (Note the above example uses PathAttribute to scale and modify the
482 opacity of the items as they rotate. This additional code can be seen in the
483 PathAttribute documentation.)
485 PathView does not automatically handle keyboard navigation. This is because
486 the keys to use for navigation will depend upon the shape of the path. Navigation
487 can be added quite simply by setting \c focus to \c true and calling
488 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
489 using the left and right arrow keys:
495 Keys.onLeftPressed: decrementCurrentIndex()
496 Keys.onRightPressed: incrementCurrentIndex()
500 The path view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
502 Delegates are instantiated as needed and may be destroyed at any time.
503 State should \e never be stored in a delegate.
505 PathView attaches a number of properties to the root item of the delegate, for example
506 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
507 this attached property directly as \c PathView.isCurrentItem, while the child
508 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
510 \snippet qml/pathview/pathview.qml 1
512 \b Note that views do not enable \e clip automatically. If the view
513 is not clipped by another item or the screen, it will be necessary
514 to set \e {clip: true} in order to have the out of view items clipped
517 \sa Path, {quick/modelviews/pathview}{PathView example}
520 QQuickPathView::QQuickPathView(QQuickItem *parent)
521 : QQuickItem(*(new QQuickPathViewPrivate), parent)
527 QQuickPathView::~QQuickPathView()
532 d->attType->release();
538 \qmlattachedproperty PathView QtQuick2::PathView::view
539 This attached property holds the view that manages this delegate instance.
541 It is attached to each instance of the delegate.
545 \qmlattachedproperty bool QtQuick2::PathView::onPath
546 This attached property holds whether the item is currently on the path.
548 If a pathItemCount has been set, it is possible that some items may
549 be instantiated, but not considered to be currently on the path.
550 Usually, these items would be set invisible, for example:
555 visible: PathView.onPath
561 It is attached to each instance of the delegate.
565 \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
566 This attached property is true if this delegate is the current item; otherwise false.
568 It is attached to each instance of the delegate.
570 This property may be used to adjust the appearance of the current item.
572 \snippet qml/pathview/pathview.qml 1
576 \qmlproperty model QtQuick2::PathView::model
577 This property holds the model providing data for the view.
579 The model provides a set of data that is used to create the items for the view.
580 For large or dynamic datasets the model is usually provided by a C++ model object.
581 Models can also be created directly in QML, using the ListModel type.
583 \note changing the model will reset the offset and currentIndex to 0.
585 \sa {qml-data-models}{Data Models}
587 QVariant QQuickPathView::model() const
589 Q_D(const QQuickPathView);
590 return d->modelVariant;
593 void QQuickPathView::setModel(const QVariant &model)
596 if (d->modelVariant == model)
600 qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
601 this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
602 qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
603 this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
604 qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
605 this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
609 d->modelVariant = model;
610 QObject *object = qvariant_cast<QObject*>(model);
611 QQuickVisualModel *vim = 0;
612 if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
620 d->model = new QQuickVisualDataModel(qmlContext(this));
622 if (isComponentComplete())
623 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
625 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
626 dataModel->setModel(model);
628 int oldModelCount = d->modelCount;
631 qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
632 this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
633 qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
634 this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
635 qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
636 this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
637 d->modelCount = d->model->count();
639 if (isComponentComplete()) {
640 if (d->currentIndex != 0) {
642 emit currentIndexChanged();
644 if (d->offset != 0.0) {
646 emit offsetChanged();
650 if (d->modelCount != oldModelCount)
656 \qmlproperty int QtQuick2::PathView::count
657 This property holds the number of items in the model.
659 int QQuickPathView::count() const
661 Q_D(const QQuickPathView);
662 return d->model ? d->modelCount : 0;
666 \qmlproperty Path QtQuick2::PathView::path
667 This property holds the path used to lay out the items.
668 For more information see the \l Path documentation.
670 QQuickPath *QQuickPathView::path() const
672 Q_D(const QQuickPathView);
676 void QQuickPathView::setPath(QQuickPath *path)
682 qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
683 this, QQuickPathView, SLOT(pathUpdated()));
685 qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
686 this, QQuickPathView, SLOT(pathUpdated()));
687 if (d->isValid() && isComponentComplete()) {
690 d->attType->release();
699 \qmlproperty int QtQuick2::PathView::currentIndex
700 This property holds the index of the current item.
702 int QQuickPathView::currentIndex() const
704 Q_D(const QQuickPathView);
705 return d->currentIndex;
709 \qmlproperty int QtQuick2::PathView::currentItem
710 This property holds the current item in the view.
712 void QQuickPathView::setCurrentIndex(int idx)
715 if (!isComponentComplete()) {
716 if (idx != d->currentIndex) {
717 d->currentIndex = idx;
718 emit currentIndexChanged();
724 ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
726 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
727 if (d->currentItem) {
728 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
729 att->setIsCurrentItem(false);
730 d->releaseItem(d->currentItem);
732 int oldCurrentIdx = d->currentIndex;
733 QQuickItem *oldCurrentItem = d->currentItem;
735 d->moveReason = QQuickPathViewPrivate::SetIndex;
736 d->currentIndex = idx;
738 d->createCurrentItem();
739 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
740 d->snapToIndex(d->currentIndex);
741 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
742 d->updateHighlight();
744 if (oldCurrentIdx != d->currentIndex)
745 emit currentIndexChanged();
746 if (oldCurrentItem != d->currentItem)
747 emit currentItemChanged();
751 QQuickItem *QQuickPathView::currentItem() const
753 Q_D(const QQuickPathView);
754 return d->currentItem;
758 \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
760 Increments the current index.
762 \b Note: methods should only be called after the Component has completed.
764 void QQuickPathView::incrementCurrentIndex()
767 d->moveDirection = QQuickPathViewPrivate::Positive;
768 setCurrentIndex(currentIndex()+1);
772 \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
774 Decrements the current index.
776 \b Note: methods should only be called after the Component has completed.
778 void QQuickPathView::decrementCurrentIndex()
781 d->moveDirection = QQuickPathViewPrivate::Negative;
782 setCurrentIndex(currentIndex()-1);
786 \qmlproperty real QtQuick2::PathView::offset
788 The offset specifies how far along the path the items are from their initial positions.
789 This is a real number that ranges from 0.0 to the count of items in the model.
791 qreal QQuickPathView::offset() const
793 Q_D(const QQuickPathView);
797 void QQuickPathView::setOffset(qreal offset)
800 d->moveReason = QQuickPathViewPrivate::Other;
801 d->setOffset(offset);
805 void QQuickPathViewPrivate::setOffset(qreal o)
809 if (isValid() && q->isComponentComplete()) {
810 offset = qmlMod(o, qreal(modelCount));
812 offset += qreal(modelCount);
817 emit q->offsetChanged();
821 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
823 setOffset(o+offsetAdj);
827 \qmlproperty Component QtQuick2::PathView::highlight
828 This property holds the component to use as the highlight.
830 An instance of the highlight component will be created for each view.
831 The geometry of the resultant component instance will be managed by the view
832 so as to stay with the current item.
834 The below example demonstrates how to make a simple highlight. Note the use
835 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
836 the highlight is hidden when flicked away from the path.
841 visible: PathView.onPath
847 \sa highlightItem, highlightRangeMode
850 QQmlComponent *QQuickPathView::highlight() const
852 Q_D(const QQuickPathView);
853 return d->highlightComponent;
856 void QQuickPathView::setHighlight(QQmlComponent *highlight)
859 if (highlight != d->highlightComponent) {
860 d->highlightComponent = highlight;
861 d->createHighlight();
862 d->updateHighlight();
863 emit highlightChanged();
868 \qmlproperty Item QtQuick2::PathView::highlightItem
870 \c highlightItem holds the highlight item, which was created
871 from the \l highlight component.
875 QQuickItem *QQuickPathView::highlightItem()
877 Q_D(const QQuickPathView);
878 return d->highlightItem;
881 \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
882 \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
883 \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
885 These properties set the preferred range of the highlight (current item)
886 within the view. The preferred values must be in the range 0.0-1.0.
888 If highlightRangeMode is set to \e PathView.NoHighlightRange
890 If highlightRangeMode is set to \e PathView.ApplyRange the view will
891 attempt to maintain the highlight within the range, however
892 the highlight can move outside of the range at the ends of the path
893 or due to a mouse interaction.
895 If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
896 move outside of the range. This means that the current item will change
897 if a keyboard or mouse action would cause the highlight to move
898 outside of the range.
900 Note that this is the correct way to influence where the
901 current item ends up when the view moves. For example, if you want the
902 currently selected item to be in the middle of the path, then set the
903 highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
904 Then, when the path scrolls,
905 the currently selected item will be the item at that position. This also applies to
906 when the currently selected item changes - it will scroll to within the preferred
907 highlight range. Furthermore, the behaviour of the current item index will occur
908 whether or not a highlight exists.
910 The default value is \e PathView.StrictlyEnforceRange.
912 Note that a valid range requires preferredHighlightEnd to be greater
913 than or equal to preferredHighlightBegin.
915 qreal QQuickPathView::preferredHighlightBegin() const
917 Q_D(const QQuickPathView);
918 return d->highlightRangeStart;
921 void QQuickPathView::setPreferredHighlightBegin(qreal start)
924 if (d->highlightRangeStart == start || start < 0 || start > 1.0)
926 d->highlightRangeStart = start;
927 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
929 emit preferredHighlightBeginChanged();
932 qreal QQuickPathView::preferredHighlightEnd() const
934 Q_D(const QQuickPathView);
935 return d->highlightRangeEnd;
938 void QQuickPathView::setPreferredHighlightEnd(qreal end)
941 if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
943 d->highlightRangeEnd = end;
944 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
946 emit preferredHighlightEndChanged();
949 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
951 Q_D(const QQuickPathView);
952 return d->highlightRangeMode;
955 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
958 if (d->highlightRangeMode == mode)
960 d->highlightRangeMode = mode;
961 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
962 if (d->haveHighlightRange) {
964 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
966 d->snapToIndex(index);
968 emit highlightRangeModeChanged();
972 \qmlproperty int QtQuick2::PathView::highlightMoveDuration
973 This property holds the move animation duration of the highlight delegate.
975 If the highlightRangeMode is StrictlyEnforceRange then this property
976 determines the speed that the items move along the path.
978 The default value for the duration is 300ms.
980 int QQuickPathView::highlightMoveDuration() const
982 Q_D(const QQuickPathView);
983 return d->highlightMoveDuration;
986 void QQuickPathView::setHighlightMoveDuration(int duration)
989 if (d->highlightMoveDuration == duration)
991 d->highlightMoveDuration = duration;
992 emit highlightMoveDurationChanged();
996 \qmlproperty real QtQuick2::PathView::dragMargin
997 This property holds the maximum distance from the path that initiate mouse dragging.
999 By default the path can only be dragged by clicking on an item. If
1000 dragMargin is greater than zero, a drag can be initiated by clicking
1001 within dragMargin pixels of the path.
1003 qreal QQuickPathView::dragMargin() const
1005 Q_D(const QQuickPathView);
1006 return d->dragMargin;
1009 void QQuickPathView::setDragMargin(qreal dragMargin)
1011 Q_D(QQuickPathView);
1012 if (d->dragMargin == dragMargin)
1014 d->dragMargin = dragMargin;
1015 emit dragMarginChanged();
1019 \qmlproperty real QtQuick2::PathView::flickDeceleration
1020 This property holds the rate at which a flick will decelerate.
1024 qreal QQuickPathView::flickDeceleration() const
1026 Q_D(const QQuickPathView);
1027 return d->deceleration;
1030 void QQuickPathView::setFlickDeceleration(qreal dec)
1032 Q_D(QQuickPathView);
1033 if (d->deceleration == dec)
1035 d->deceleration = dec;
1036 emit flickDecelerationChanged();
1040 \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1041 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1043 The default value is platform dependent.
1045 qreal QQuickPathView::maximumFlickVelocity() const
1047 Q_D(const QQuickPathView);
1048 return d->maximumFlickVelocity;
1051 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1053 Q_D(QQuickPathView);
1054 if (vel == d->maximumFlickVelocity)
1056 d->maximumFlickVelocity = vel;
1057 emit maximumFlickVelocityChanged();
1062 \qmlproperty bool QtQuick2::PathView::interactive
1064 A user cannot drag or flick a PathView that is not interactive.
1066 This property is useful for temporarily disabling flicking. This allows
1067 special interaction with PathView's children.
1069 bool QQuickPathView::isInteractive() const
1071 Q_D(const QQuickPathView);
1072 return d->interactive;
1075 void QQuickPathView::setInteractive(bool interactive)
1077 Q_D(QQuickPathView);
1078 if (interactive != d->interactive) {
1079 d->interactive = interactive;
1082 emit interactiveChanged();
1087 \qmlproperty bool QtQuick2::PathView::moving
1089 This property holds whether the view is currently moving
1090 due to the user either dragging or flicking the view.
1092 bool QQuickPathView::isMoving() const
1094 Q_D(const QQuickPathView);
1099 \qmlproperty bool QtQuick2::PathView::flicking
1101 This property holds whether the view is currently moving
1102 due to the user flicking the view.
1104 bool QQuickPathView::isFlicking() const
1106 Q_D(const QQuickPathView);
1111 \qmlproperty bool QtQuick2::PathView::dragging
1113 This property holds whether the view is currently moving
1114 due to the user dragging the view.
1116 bool QQuickPathView::isDragging() const
1118 Q_D(const QQuickPathView);
1123 \qmlsignal QtQuick2::PathView::onMovementStarted()
1125 This handler is called when the view begins moving due to user
1130 \qmlsignal QtQuick2::PathView::onMovementEnded()
1132 This handler is called when the view stops moving due to user
1133 interaction. If a flick was generated, this handler will
1134 be triggered once the flick stops. If a flick was not
1135 generated, the handler will be triggered when the
1136 user stops dragging - i.e. a mouse or touch release.
1140 \qmlsignal QtQuick2::PathView::onFlickStarted()
1142 This handler is called when the view is flicked. A flick
1143 starts from the point that the mouse or touch is released,
1144 while still in motion.
1148 \qmlsignal QtQuick2::PathView::onFlickEnded()
1150 This handler is called when the view stops moving due to a flick.
1154 \qmlsignal QtQuick2::PathView::onDragStarted()
1156 This handler is called when the view starts to be dragged due to user
1161 \qmlsignal QtQuick2::PathView::onDragEnded()
1163 This handler is called when the user stops dragging the view.
1165 If the velocity of the drag is suffient at the time the
1166 touch/mouse button is released then a flick will start.
1170 \qmlproperty Component QtQuick2::PathView::delegate
1172 The delegate provides a template defining each item instantiated by the view.
1173 The index is exposed as an accessible \c index property. Properties of the
1174 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
1176 The number of objects and bindings in the delegate has a direct effect on the
1177 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
1178 that is not needed for the normal display of the delegate in a \l Loader which
1179 can load additional components when needed.
1181 Note that the PathView will layout the items based on the size of the root
1182 item in the delegate.
1184 Here is an example delegate:
1185 \snippet qml/pathview/pathview.qml 1
1187 QQmlComponent *QQuickPathView::delegate() const
1189 Q_D(const QQuickPathView);
1191 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1192 return dataModel->delegate();
1198 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1200 Q_D(QQuickPathView);
1201 if (delegate == this->delegate())
1204 d->model = new QQuickVisualDataModel(qmlContext(this));
1207 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1208 int oldCount = dataModel->count();
1209 dataModel->setDelegate(delegate);
1210 d->modelCount = dataModel->count();
1212 if (oldCount != dataModel->count())
1213 emit countChanged();
1214 emit delegateChanged();
1219 \qmlproperty int QtQuick2::PathView::pathItemCount
1220 This property holds the number of items visible on the path at any one time.
1222 Setting pathItemCount to undefined will show all items on the path.
1224 int QQuickPathView::pathItemCount() const
1226 Q_D(const QQuickPathView);
1227 return d->pathItems;
1230 void QQuickPathView::setPathItemCount(int i)
1232 Q_D(QQuickPathView);
1233 if (i == d->pathItems)
1238 d->updateMappedRange();
1239 if (d->isValid() && isComponentComplete()) {
1242 emit pathItemCountChanged();
1245 void QQuickPathView::resetPathItemCount()
1247 Q_D(QQuickPathView);
1248 if (-1 == d->pathItems)
1251 d->updateMappedRange();
1252 if (d->isValid() && isComponentComplete())
1254 emit pathItemCountChanged();
1258 \qmlproperty enumeration QtQuick2::PathView::snapMode
1260 This property determines how the items will settle following a drag or flick.
1261 The possible values are:
1264 \li PathView.NoSnap (default) - the items stop anywhere along the path.
1265 \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
1266 \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
1267 \l preferredHighlightBegin at the time the press is released. This mode is particularly
1268 useful for moving one page at a time.
1271 \c snapMode does not affect the \l currentIndex. To update the
1272 \l currentIndex as the view is moved, set \l highlightRangeMode
1273 to \c PathView.StrictlyEnforceRange (default for PathView).
1275 \sa highlightRangeMode
1277 QQuickPathView::SnapMode QQuickPathView::snapMode() const
1279 Q_D(const QQuickPathView);
1283 void QQuickPathView::setSnapMode(SnapMode mode)
1285 Q_D(QQuickPathView);
1286 if (mode == d->snapMode)
1289 emit snapModeChanged();
1293 \qmlmethod QtQuick2::PathView::positionViewAtIndex(int index, PositionMode mode)
1295 Positions the view such that the \a index is at the position specified by
1299 \li PathView.Beginning - position item at the beginning of the path.
1300 \li PathView.Center - position item in the center of the path.
1301 \li PathView.End - position item at the end of the path.
1302 \li PathView.Contain - ensure the item is positioned on the path.
1303 \li PathView.SnapPosition - position the item at \l preferredHighlightBegin. This mode
1304 is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
1308 \b Note: methods should only be called after the Component has completed. To position
1309 the view at startup, this method should be called by Component.onCompleted. For
1310 example, to position the view at the end:
1313 Component.onCompleted: positionViewAtIndex(count - 1, PathView.End)
1316 void QQuickPathView::positionViewAtIndex(int index, int mode)
1318 Q_D(QQuickPathView);
1321 if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView
1324 if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems))
1327 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1328 int idx = (index+d->modelCount) % d->modelCount;
1329 bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1330 || d->snapMode != QQuickPathView::NoSnap);
1335 beginOffset = d->modelCount - idx - qFloor(count * d->highlightRangeStart);
1336 endOffset = beginOffset + count - 1;
1338 beginOffset = d->modelCount - idx;
1339 // Small offset since the last point coincides with the first and
1340 // this the only "end" position that gives the expected visual result.
1341 qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001;
1342 endOffset = qmlMod(beginOffset + count, d->modelCount) - adj;
1344 qreal offset = d->offset;
1347 offset = beginOffset;
1353 if (beginOffset < endOffset)
1354 offset = (beginOffset + endOffset)/2;
1356 offset = (beginOffset + (endOffset + d->modelCount))/2;
1358 offset = qRound(offset);
1361 if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset))
1362 || (d->offset < beginOffset && d->offset > endOffset)) {
1363 qreal diff1 = qmlMod(beginOffset - d->offset + d->modelCount, d->modelCount);
1364 qreal diff2 = qmlMod(d->offset - endOffset + d->modelCount, d->modelCount);
1366 offset = beginOffset;
1372 offset = d->modelCount - idx;
1381 \qmlmethod int QtQuick2::PathView::indexAt(int x, int y)
1383 Returns the index of the item containing the point \a x, \a y in content
1384 coordinates. If there is no item at the point specified, -1 is returned.
1386 \b Note: methods should only be called after the Component has completed.
1388 int QQuickPathView::indexAt(qreal x, qreal y) const
1390 Q_D(const QQuickPathView);
1394 for (int idx = 0; idx < d->items.count(); ++idx) {
1395 QQuickItem *item = d->items.at(idx);
1396 QPointF p = item->mapFromItem(this, QPointF(x, y));
1397 if (item->contains(p))
1398 return (d->firstIndex + idx) % d->modelCount;
1405 \qmlmethod Item QtQuick2::PathView::itemAt(int x, int y)
1407 Returns the item containing the point \a x, \a y in content
1408 coordinates. If there is no item at the point specified, null is returned.
1410 \b Note: methods should only be called after the Component has completed.
1412 QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const
1414 Q_D(const QQuickPathView);
1418 for (int idx = 0; idx < d->items.count(); ++idx) {
1419 QQuickItem *item = d->items.at(idx);
1420 QPointF p = item->mapFromItem(this, QPointF(x, y));
1421 if (item->contains(p))
1428 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1430 qreal samples = qMin(path->path().length()/5, qreal(500.0));
1431 qreal res = path->path().length()/samples;
1433 qreal mindist = 1e10; // big number
1434 QPointF nearPoint = path->pointAt(0);
1438 for (qreal i=1; i < samples; i++) {
1439 QPointF pt = path->pointAt(i/samples);
1440 QPointF diff = pt - point;
1441 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1442 if (dist < mindist) {
1450 qreal approxPc = nearPc;
1451 for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
1452 QPointF pt = path->pointAt(i/samples);
1453 QPointF diff = pt - point;
1454 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1455 if (dist < mindist) {
1463 *nearPercent = nearPc / samples;
1468 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1470 velocityBuffer.append(v);
1471 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1472 velocityBuffer.remove(0);
1475 qreal QQuickPathViewPrivate::calcVelocity() const
1478 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1479 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1480 for (int i = 0; i < count; ++i) {
1481 qreal v = velocityBuffer.at(i);
1489 qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
1491 if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
1492 return event->timestamp();
1495 return QQuickItemPrivate::elapsed(timer);
1498 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1500 Q_D(QQuickPathView);
1501 if (d->interactive) {
1502 d->handleMousePressEvent(event);
1505 QQuickItem::mousePressEvent(event);
1509 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1511 if (!interactive || !items.count() || !model || !modelCount)
1513 velocityBuffer.clear();
1515 for (; idx < items.count(); ++idx) {
1516 QQuickItem *item = items.at(idx);
1517 if (item->contains(item->mapFromScene(event->windowPos())))
1520 if (idx == items.count() && dragMargin == 0.) // didn't click on an item
1523 startPoint = pointNear(event->localPos(), &startPc);
1524 if (idx == items.count()) {
1525 qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y());
1526 if (distance > dragMargin)
1531 if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1532 stealMouse = true; // If we've been flicked then steal the click.
1536 QQuickItemPrivate::start(timer);
1537 lastPosTime = computeCurrentTime(event);
1541 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1543 Q_D(QQuickPathView);
1544 if (d->interactive) {
1545 d->handleMouseMoveEvent(event);
1547 setKeepMouseGrab(true);
1550 QQuickItem::mouseMoveEvent(event);
1554 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1556 Q_Q(QQuickPathView);
1557 if (!interactive || !timer.isValid() || !model || !modelCount)
1560 qint64 currentTimestamp = computeCurrentTime(event);
1562 QPointF pathPoint = pointNear(event->localPos(), &newPc);
1564 QPointF delta = pathPoint - startPoint;
1565 if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1569 moveReason = QQuickPathViewPrivate::Mouse;
1570 qreal diff = (newPc - startPc)*modelCount*mappedRange;
1572 q->setOffset(offset + diff);
1574 if (diff > modelCount/2)
1576 else if (diff < -modelCount/2)
1579 qint64 elapsed = currentTimestamp - lastPosTime;
1581 addVelocitySample(diff / (qreal(elapsed) / 1000.));
1585 emit q->movingChanged();
1586 emit q->movementStarted();
1591 lastPosTime = currentTimestamp;
1594 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1596 Q_D(QQuickPathView);
1597 if (d->interactive) {
1598 d->handleMouseReleaseEvent(event);
1602 QQuickItem::mouseReleaseEvent(event);
1606 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1608 Q_Q(QQuickPathView);
1610 q->setKeepMouseGrab(false);
1612 if (!interactive || !timer.isValid() || !model || !modelCount) {
1615 q->movementEnding();
1619 qreal velocity = calcVelocity();
1620 qreal count = modelCount*mappedRange;
1621 qreal pixelVelocity = (path->path().length()/count) * velocity;
1622 if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
1623 if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
1625 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1626 velocity = maxVel / (path->path().length()/count);
1628 // Calculate the distance to be travelled
1629 qreal v2 = velocity*velocity;
1630 qreal accel = deceleration/10;
1632 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1633 || snapMode != QQuickPathView::NoSnap)) {
1634 if (snapMode == QQuickPathView::SnapOneItem) {
1635 // encourage snapping one item in direction of motion
1637 dist = qRound(0.5 + offset) - offset;
1639 dist = qRound(0.5 - offset) + offset;
1641 // + 0.25 to encourage moving at least one item in the flick direction
1642 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1644 // round to nearest item.
1646 dist = qRound(dist + offset) - offset;
1648 dist = qRound(dist - offset) + offset;
1650 // Calculate accel required to stop on item boundary
1655 accel = v2 / (2.0f * qAbs(dist));
1658 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1660 flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1662 moveOffset.setValue(offset);
1663 tl.accel(moveOffset, velocity, accel, dist);
1664 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1667 emit q->flickingChanged();
1668 emit q->flickStarted();
1676 q->movementEnding();
1679 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1681 Q_D(QQuickPathView);
1682 QPointF localPos = mapFromScene(event->windowPos());
1684 QQuickWindow *c = window();
1685 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
1686 bool stealThisEvent = d->stealMouse;
1687 if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
1688 QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
1689 event->button(), event->buttons(), event->modifiers());
1690 mouseEvent.setAccepted(false);
1692 switch (mouseEvent.type()) {
1693 case QEvent::MouseMove:
1694 d->handleMouseMoveEvent(&mouseEvent);
1696 case QEvent::MouseButtonPress:
1697 d->handleMousePressEvent(&mouseEvent);
1698 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1700 case QEvent::MouseButtonRelease:
1701 d->handleMouseReleaseEvent(&mouseEvent);
1706 grabber = c->mouseGrabberItem();
1707 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1710 return d->stealMouse;
1711 } else if (d->timer.isValid()) {
1712 d->timer.invalidate();
1715 if (event->type() == QEvent::MouseButtonRelease)
1716 d->stealMouse = false;
1720 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1722 Q_D(QQuickPathView);
1723 if (!isVisible() || !d->interactive)
1724 return QQuickItem::childMouseEventFilter(i, e);
1726 switch (e->type()) {
1727 case QEvent::MouseButtonPress:
1728 case QEvent::MouseMove:
1729 case QEvent::MouseButtonRelease:
1730 return sendMouseEvent(static_cast<QMouseEvent *>(e));
1735 return QQuickItem::childMouseEventFilter(i, e);
1738 void QQuickPathView::mouseUngrabEvent()
1740 Q_D(QQuickPathView);
1741 if (d->stealMouse) {
1742 // if our mouse grab has been removed (probably by a Flickable),
1744 d->stealMouse = false;
1745 setKeepMouseGrab(false);
1746 d->timer.invalidate();
1748 d->setDragging(false);
1749 if (!d->tl.isActive())
1754 void QQuickPathView::updatePolish()
1756 QQuickItem::updatePolish();
1760 void QQuickPathView::componentComplete()
1762 Q_D(QQuickPathView);
1763 if (d->model && d->ownModel)
1764 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1766 QQuickItem::componentComplete();
1769 d->modelCount = d->model->count();
1770 if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex
1771 d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
1774 d->createHighlight();
1776 d->updateHighlight();
1780 emit countChanged();
1783 void QQuickPathView::refill()
1785 Q_D(QQuickPathView);
1787 d->layoutScheduled = false;
1789 if (!d->isValid() || !isComponentComplete())
1792 bool currentVisible = false;
1794 // first move existing items and remove items off path
1795 int idx = d->firstIndex;
1796 QList<QQuickItem*>::iterator it = d->items.begin();
1797 while (it != d->items.end()) {
1798 qreal pos = d->positionOfIndex(idx);
1799 QQuickItem *item = *it;
1801 d->updateItem(item, pos);
1802 if (idx == d->currentIndex) {
1803 currentVisible = true;
1804 d->currentItemOffset = pos;
1808 // qDebug() << "release";
1809 d->updateItem(item, 1.0);
1810 d->releaseItem(item);
1811 if (it == d->items.begin()) {
1812 if (++d->firstIndex >= d->modelCount)
1815 it = d->items.erase(it);
1818 if (idx >= d->modelCount)
1821 if (!d->items.count())
1824 bool waiting = false;
1825 if (d->modelCount) {
1826 // add items to beginning and end
1827 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1828 if (d->items.count() < count) {
1829 int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1830 qreal startPos = 0.0;
1831 if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1832 || d->snapMode != QQuickPathView::NoSnap))
1833 startPos = d->highlightRangeStart;
1834 if (d->firstIndex >= 0) {
1835 startPos = d->positionOfIndex(d->firstIndex);
1836 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1838 qreal pos = d->positionOfIndex(idx);
1839 while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1840 // qDebug() << "append" << idx;
1841 QQuickItem *item = d->getItem(idx, idx+1);
1846 if (d->currentIndex == idx) {
1847 currentVisible = true;
1848 d->currentItemOffset = pos;
1850 if (d->items.count() == 0)
1851 d->firstIndex = idx;
1852 d->items.append(item);
1853 d->updateItem(item, pos);
1855 if (idx >= d->modelCount)
1857 pos = d->positionOfIndex(idx);
1860 idx = d->firstIndex - 1;
1862 idx = d->modelCount - 1;
1863 pos = d->positionOfIndex(idx);
1864 while (!waiting && (pos >= 0.0 && pos < startPos) && d->items.count() < count) {
1865 // qDebug() << "prepend" << idx;
1866 QQuickItem *item = d->getItem(idx, idx+1);
1871 if (d->currentIndex == idx) {
1872 currentVisible = true;
1873 d->currentItemOffset = pos;
1875 d->items.prepend(item);
1876 d->updateItem(item, pos);
1877 d->firstIndex = idx;
1878 idx = d->firstIndex - 1;
1880 idx = d->modelCount - 1;
1881 pos = d->positionOfIndex(idx);
1886 if (!currentVisible) {
1887 d->currentItemOffset = 1.0;
1888 if (d->currentItem) {
1889 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1890 att->setOnPath(false);
1891 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
1892 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
1893 d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
1894 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1895 att->setIsCurrentItem(true);
1898 } else if (!waiting && !d->currentItem) {
1899 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
1900 d->currentItem->setFocus(true);
1901 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1902 att->setIsCurrentItem(true);
1906 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1907 d->updateItem(d->highlightItem, d->highlightRangeStart);
1908 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1909 att->setOnPath(true);
1910 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
1911 d->updateItem(d->highlightItem, d->currentItemOffset);
1912 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1913 att->setOnPath(currentVisible);
1915 while (d->itemCache.count())
1916 d->releaseItem(d->itemCache.takeLast());
1919 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1921 Q_D(QQuickPathView);
1922 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1926 d->modelCount = d->model->count();
1928 emit countChanged();
1932 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1935 const int modelCount = d->modelCount;
1938 bool currentChanged = false;
1939 bool changedOffset = false;
1940 foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
1941 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
1942 d->currentIndex -= r.count;
1943 currentChanged = true;
1944 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
1945 // current item has been removed.
1948 moveOffset = d->currentIndex - r.index;
1949 } else if (d->currentItem) {
1950 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1951 att->setIsCurrentItem(true);
1952 d->releaseItem(d->currentItem);
1955 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
1956 currentChanged = true;
1959 if (r.index > d->currentIndex) {
1960 changedOffset = true;
1961 d->offset -= r.count;
1962 d->offsetAdj -= r.count;
1964 d->modelCount -= r.count;
1966 foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
1967 if (d->modelCount) {
1968 if (moveId == -1 && i.index <= d->currentIndex) {
1969 d->currentIndex += i.count;
1970 currentChanged = true;
1972 if (moveId != -1 && moveId == i.moveId) {
1973 d->currentIndex = i.index + moveOffset;
1974 currentChanged = true;
1976 if (i.index > d->currentIndex) {
1977 d->offset += i.count;
1978 d->offsetAdj += i.count;
1979 changedOffset = true;
1983 d->modelCount += i.count;
1986 d->offset = qmlMod(d->offset, d->modelCount);
1988 d->offset += d->modelCount;
1989 if (d->currentIndex == -1)
1990 d->currentIndex = d->calcCurrentIndex();
1992 d->itemCache += d->items;
1995 if (!d->modelCount) {
1996 while (d->itemCache.count())
1997 d->releaseItem(d->itemCache.takeLast());
1999 changedOffset = true;
2000 d->tl.reset(d->moveOffset);
2002 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2003 d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
2004 changedOffset = true;
2007 d->updateMappedRange();
2008 d->scheduleLayout();
2011 emit offsetChanged();
2013 emit currentIndexChanged();
2014 if (d->modelCount != modelCount)
2015 emit countChanged();
2018 void QQuickPathView::destroyingItem(QQuickItem *item)
2023 void QQuickPathView::ticked()
2025 Q_D(QQuickPathView);
2029 void QQuickPathView::movementEnding()
2031 Q_D(QQuickPathView);
2033 d->flicking = false;
2034 emit flickingChanged();
2037 if (d->moving && !d->stealMouse) {
2039 emit movingChanged();
2040 emit movementEnded();
2044 // find the item closest to the snap position
2045 int QQuickPathViewPrivate::calcCurrentIndex()
2048 if (modelCount && model && items.count()) {
2049 offset = qmlMod(offset, modelCount);
2051 offset += modelCount;
2052 current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
2053 current = current % modelCount;
2059 void QQuickPathViewPrivate::createCurrentItem()
2061 if (requestedIndex != -1)
2063 int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
2064 if (itemIndex < items.count()) {
2065 if ((currentItem = getItem(currentIndex, currentIndex, true))) {
2066 currentItem->setFocus(true);
2067 if (QQuickPathViewAttached *att = attached(currentItem))
2068 att->setIsCurrentItem(true);
2070 } else if (currentIndex >= 0 && currentIndex < modelCount) {
2071 if ((currentItem = getItem(currentIndex, currentIndex, false))) {
2072 updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
2073 if (QQuickPathViewAttached *att = attached(currentItem))
2074 att->setIsCurrentItem(true);
2079 void QQuickPathViewPrivate::updateCurrent()
2081 Q_Q(QQuickPathView);
2082 if (moveReason == SetIndex)
2084 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
2087 int idx = calcCurrentIndex();
2088 if (model && (idx != currentIndex || !currentItem)) {
2090 if (QQuickPathViewAttached *att = attached(currentItem))
2091 att->setIsCurrentItem(false);
2092 releaseItem(currentItem);
2094 int oldCurrentIndex = currentIndex;
2097 createCurrentItem();
2098 if (oldCurrentIndex != currentIndex)
2099 emit q->currentIndexChanged();
2100 emit q->currentItemChanged();
2104 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
2106 ((QQuickPathViewPrivate *)d)->fixOffset();
2109 void QQuickPathViewPrivate::fixOffset()
2111 Q_Q(QQuickPathView);
2112 if (model && items.count()) {
2113 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
2114 || snapMode != QQuickPathView::NoSnap)) {
2115 int curr = calcCurrentIndex();
2116 if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
2117 q->setCurrentIndex(curr);
2124 void QQuickPathViewPrivate::snapToIndex(int index)
2126 if (!model || modelCount <= 0)
2129 qreal targetOffset = qmlMod(modelCount - index, modelCount);
2131 if (offset == targetOffset)
2136 tl.reset(moveOffset);
2137 moveOffset.setValue(offset);
2139 const int duration = highlightMoveDuration;
2142 tl.set(moveOffset, targetOffset);
2143 } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2.0)) {
2144 qreal distance = modelCount - targetOffset + offset;
2145 if (targetOffset > moveOffset) {
2146 tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
2147 tl.set(moveOffset, modelCount);
2148 tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
2150 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2152 } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2.0) {
2153 qreal distance = modelCount - offset + targetOffset;
2154 if (targetOffset < moveOffset) {
2155 tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
2156 tl.set(moveOffset, 0.0);
2157 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
2159 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2162 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2164 moveDirection = Shortest;
2167 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
2169 return new QQuickPathViewAttached(obj);