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), mappedCache(0.0)
118 , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
119 , autoHighlight(true), highlightUp(false), layoutScheduled(false)
120 , moving(false), flicking(false), dragging(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), cacheSize(0), 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 async)
150 requestedIndex = modelIndex;
153 QQuickItem *item = model->item(modelIndex, async);
155 item->setParentItem(q);
157 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
158 itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
164 void QQuickPathView::createdItem(int index, QQuickItem *item)
167 if (d->requestedIndex != index) {
168 qPathViewAttachedType = d->attachedType();
169 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
170 qPathViewAttachedType = 0;
173 att->setOnPath(false);
175 item->setParentItem(this);
176 d->updateItem(item, 1.0);
178 d->requestedIndex = -1;
184 void QQuickPathView::initItem(int index, QQuickItem *item)
187 if (d->requestedIndex == index) {
188 QQuickItemPrivate::get(item)->setCulled(true);
189 item->setParentItem(this);
190 qPathViewAttachedType = d->attachedType();
191 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
192 qPathViewAttachedType = 0;
195 qreal percent = d->positionOfIndex(index);
197 foreach (const QString &attr, d->path->attributes())
198 att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
199 item->setZ(d->requestedZ);
201 att->setOnPath(percent < 1.0);
206 void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
210 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
211 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
212 if (model->release(item) == 0) {
213 // item was not destroyed, and we no longer reference it.
214 if (QQuickPathViewAttached *att = attached(item))
215 att->setOnPath(false);
219 QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
221 return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
224 QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
228 // pre-create one metatype to share with all attached objects
229 attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
230 foreach (const QString &attr, path->attributes())
231 attType->createProperty(attr.toUtf8());
237 void QQuickPathViewPrivate::clear()
240 releaseItem(currentItem);
243 for (int i=0; i<items.count(); i++){
244 QQuickItem *p = items[i];
247 if (requestedIndex >= 0) {
249 model->cancel(requestedIndex);
257 void QQuickPathViewPrivate::updateMappedRange()
259 if (model && pathItems != -1 && pathItems < modelCount) {
260 mappedRange = qreal(modelCount)/pathItems;
261 mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
268 qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
272 if (model && index >= 0 && index < modelCount) {
274 if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
275 || snapMode != QQuickPathView::NoSnap))
276 start = highlightRangeStart;
277 qreal globalPos = index + offset;
278 globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
279 if (pathItems != -1 && pathItems < modelCount) {
280 globalPos += start / mappedRange;
281 globalPos = qmlMod(globalPos, 1.0);
282 pos = globalPos * mappedRange;
284 pos = qmlMod(globalPos + start, 1.0);
291 // returns true if position is between lower and upper, taking into
292 // account the circular space.
293 bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower, qreal upper) const
296 if (position > upper && position > lower)
297 position -= mappedRange;
298 lower -= mappedRange;
300 return position >= lower && position < upper;
303 void QQuickPathViewPrivate::createHighlight()
306 if (!q->isComponentComplete())
309 bool changed = false;
311 highlightItem->setParentItem(0);
312 highlightItem->deleteLater();
317 QQuickItem *item = 0;
318 if (highlightComponent) {
319 QQmlContext *creationContext = highlightComponent->creationContext();
320 QQmlContext *highlightContext = new QQmlContext(
321 creationContext ? creationContext : qmlContext(q));
322 QObject *nobj = highlightComponent->create(highlightContext);
324 QQml_setParent_noEvent(highlightContext, nobj);
325 item = qobject_cast<QQuickItem *>(nobj);
329 delete highlightContext;
332 item = new QQuickItem;
335 QQml_setParent_noEvent(item, q);
336 item->setParentItem(q);
337 highlightItem = item;
341 emit q->highlightItemChanged();
344 void QQuickPathViewPrivate::updateHighlight()
347 if (!q->isComponentComplete() || !isValid())
350 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
351 updateItem(highlightItem, highlightRangeStart);
353 qreal target = currentIndex;
356 tl.reset(moveHighlight);
357 moveHighlight.setValue(highlightPosition);
359 const int duration = highlightMoveDuration;
361 if (target - highlightPosition > modelCount/2) {
363 qreal distance = modelCount - target + highlightPosition;
364 tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
365 tl.set(moveHighlight, modelCount-0.01);
366 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
367 } else if (target - highlightPosition <= -modelCount/2) {
369 qreal distance = modelCount - highlightPosition + target;
370 tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
371 tl.set(moveHighlight, 0.0);
372 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
374 highlightUp = highlightPosition - target < 0;
375 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
381 void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
383 if (pos != highlightPosition) {
386 if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
387 start = highlightRangeStart;
388 end = highlightRangeEnd;
391 qreal range = qreal(modelCount);
392 // calc normalized position of highlight relative to offset
393 qreal relativeHighlight = qmlMod(pos + offset, range) / range;
395 if (!highlightUp && relativeHighlight > end / mappedRange) {
396 qreal diff = 1.0 - relativeHighlight;
397 setOffset(offset + diff * range);
398 } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) {
399 qreal diff = relativeHighlight - (end - start) / mappedRange;
400 setOffset(offset - diff * range - 0.00001);
403 highlightPosition = pos;
404 qreal pathPos = positionOfIndex(pos);
405 updateItem(highlightItem, pathPos);
406 if (QQuickPathViewAttached *att = attached(highlightItem))
407 att->setOnPath(pathPos < 1.0);
411 void QQuickPathView::pathUpdated()
414 QList<QQuickItem*>::iterator it = d->items.begin();
415 while (it != d->items.end()) {
416 QQuickItem *item = *it;
417 if (QQuickPathViewAttached *att = d->attached(item))
424 void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
426 if (QQuickPathViewAttached *att = attached(item)) {
427 if (qFuzzyCompare(att->m_percent, percent))
429 att->m_percent = percent;
430 foreach (const QString &attr, path->attributes())
431 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
432 att->setOnPath(percent < 1.0);
434 QQuickItemPrivate::get(item)->setCulled(percent >= 1.0);
435 QPointF pf = path->pointAt(qMin(percent, qreal(1.0)));
436 item->setX(pf.x() - item->width()/2);
437 item->setY(pf.y() - item->height()/2);
440 void QQuickPathViewPrivate::regenerate()
443 if (!q->isComponentComplete())
456 void QQuickPathViewPrivate::setDragging(bool d)
464 emit q->dragStarted();
468 emit q->draggingChanged();
473 \instantiates QQuickPathView
474 \inqmlmodule QtQuick 2
475 \ingroup qtquick-paths
476 \ingroup qtquick-views
478 \brief Lays out model-provided items on a path
480 A PathView displays data from models created from built-in QML types like ListModel
481 and XmlListModel, or custom model classes defined in C++ that inherit from
484 The view has a \l model, which defines the data to be displayed, and
485 a \l delegate, which defines how the data should be displayed.
486 The \l delegate is instantiated for each item on the \l path.
487 The items may be flicked to move them along the path.
489 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
491 \snippet qml/pathview/ContactModel.qml 0
493 This data can be represented as a PathView, like this:
495 \snippet qml/pathview/pathview.qml 0
499 (Note the above example uses PathAttribute to scale and modify the
500 opacity of the items as they rotate. This additional code can be seen in the
501 PathAttribute documentation.)
503 PathView does not automatically handle keyboard navigation. This is because
504 the keys to use for navigation will depend upon the shape of the path. Navigation
505 can be added quite simply by setting \c focus to \c true and calling
506 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
507 using the left and right arrow keys:
513 Keys.onLeftPressed: decrementCurrentIndex()
514 Keys.onRightPressed: incrementCurrentIndex()
518 The path view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
520 Delegates are instantiated as needed and may be destroyed at any time.
521 State should \e never be stored in a delegate.
523 PathView attaches a number of properties to the root item of the delegate, for example
524 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
525 this attached property directly as \c PathView.isCurrentItem, while the child
526 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
528 \snippet qml/pathview/pathview.qml 1
530 \b Note that views do not enable \e clip automatically. If the view
531 is not clipped by another item or the screen, it will be necessary
532 to set \e {clip: true} in order to have the out of view items clipped
535 \sa Path, {quick/modelviews/pathview}{PathView example}
538 QQuickPathView::QQuickPathView(QQuickItem *parent)
539 : QQuickItem(*(new QQuickPathViewPrivate), parent)
545 QQuickPathView::~QQuickPathView()
550 d->attType->release();
556 \qmlattachedproperty PathView QtQuick2::PathView::view
557 This attached property holds the view that manages this delegate instance.
559 It is attached to each instance of the delegate.
563 \qmlattachedproperty bool QtQuick2::PathView::onPath
564 This attached property holds whether the item is currently on the path.
566 If a pathItemCount has been set, it is possible that some items may
567 be instantiated, but not considered to be currently on the path.
568 Usually, these items would be set invisible, for example:
573 visible: PathView.onPath
579 It is attached to each instance of the delegate.
583 \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem
584 This attached property is true if this delegate is the current item; otherwise false.
586 It is attached to each instance of the delegate.
588 This property may be used to adjust the appearance of the current item.
590 \snippet qml/pathview/pathview.qml 1
594 \qmlproperty model QtQuick2::PathView::model
595 This property holds the model providing data for the view.
597 The model provides a set of data that is used to create the items for the view.
598 For large or dynamic datasets the model is usually provided by a C++ model object.
599 Models can also be created directly in QML, using the ListModel type.
601 \note changing the model will reset the offset and currentIndex to 0.
603 \sa {qml-data-models}{Data Models}
605 QVariant QQuickPathView::model() const
607 Q_D(const QQuickPathView);
608 return d->modelVariant;
611 void QQuickPathView::setModel(const QVariant &model)
614 if (d->modelVariant == model)
618 qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
619 this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
620 qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
621 this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
622 qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
623 this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
627 d->modelVariant = model;
628 QObject *object = qvariant_cast<QObject*>(model);
629 QQuickVisualModel *vim = 0;
630 if (object && (vim = qobject_cast<QQuickVisualModel *>(object))) {
638 d->model = new QQuickVisualDataModel(qmlContext(this));
640 if (isComponentComplete())
641 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
643 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
644 dataModel->setModel(model);
646 int oldModelCount = d->modelCount;
649 qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
650 this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
651 qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
652 this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
653 qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
654 this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
655 d->modelCount = d->model->count();
657 if (isComponentComplete()) {
658 if (d->currentIndex != 0) {
660 emit currentIndexChanged();
662 if (d->offset != 0.0) {
664 emit offsetChanged();
668 if (d->modelCount != oldModelCount)
674 \qmlproperty int QtQuick2::PathView::count
675 This property holds the number of items in the model.
677 int QQuickPathView::count() const
679 Q_D(const QQuickPathView);
680 return d->model ? d->modelCount : 0;
684 \qmlproperty Path QtQuick2::PathView::path
685 This property holds the path used to lay out the items.
686 For more information see the \l Path documentation.
688 QQuickPath *QQuickPathView::path() const
690 Q_D(const QQuickPathView);
694 void QQuickPathView::setPath(QQuickPath *path)
700 qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
701 this, QQuickPathView, SLOT(pathUpdated()));
703 qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
704 this, QQuickPathView, SLOT(pathUpdated()));
705 if (d->isValid() && isComponentComplete()) {
708 d->attType->release();
717 \qmlproperty int QtQuick2::PathView::currentIndex
718 This property holds the index of the current item.
720 int QQuickPathView::currentIndex() const
722 Q_D(const QQuickPathView);
723 return d->currentIndex;
727 \qmlproperty int QtQuick2::PathView::currentItem
728 This property holds the current item in the view.
730 void QQuickPathView::setCurrentIndex(int idx)
733 if (!isComponentComplete()) {
734 if (idx != d->currentIndex) {
735 d->currentIndex = idx;
736 emit currentIndexChanged();
742 ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
744 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
745 if (d->currentItem) {
746 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
747 att->setIsCurrentItem(false);
748 d->releaseItem(d->currentItem);
750 int oldCurrentIdx = d->currentIndex;
751 QQuickItem *oldCurrentItem = d->currentItem;
753 d->moveReason = QQuickPathViewPrivate::SetIndex;
754 d->currentIndex = idx;
756 d->createCurrentItem();
757 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
758 d->snapToIndex(d->currentIndex);
759 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
760 d->updateHighlight();
762 if (oldCurrentIdx != d->currentIndex)
763 emit currentIndexChanged();
764 if (oldCurrentItem != d->currentItem)
765 emit currentItemChanged();
769 QQuickItem *QQuickPathView::currentItem() const
771 Q_D(const QQuickPathView);
772 return d->currentItem;
776 \qmlmethod QtQuick2::PathView::incrementCurrentIndex()
778 Increments the current index.
780 \b Note: methods should only be called after the Component has completed.
782 void QQuickPathView::incrementCurrentIndex()
785 d->moveDirection = QQuickPathViewPrivate::Positive;
786 setCurrentIndex(currentIndex()+1);
790 \qmlmethod QtQuick2::PathView::decrementCurrentIndex()
792 Decrements the current index.
794 \b Note: methods should only be called after the Component has completed.
796 void QQuickPathView::decrementCurrentIndex()
799 d->moveDirection = QQuickPathViewPrivate::Negative;
800 setCurrentIndex(currentIndex()-1);
804 \qmlproperty real QtQuick2::PathView::offset
806 The offset specifies how far along the path the items are from their initial positions.
807 This is a real number that ranges from 0.0 to the count of items in the model.
809 qreal QQuickPathView::offset() const
811 Q_D(const QQuickPathView);
815 void QQuickPathView::setOffset(qreal offset)
818 d->moveReason = QQuickPathViewPrivate::Other;
819 d->setOffset(offset);
823 void QQuickPathViewPrivate::setOffset(qreal o)
827 if (isValid() && q->isComponentComplete()) {
828 offset = qmlMod(o, qreal(modelCount));
830 offset += qreal(modelCount);
835 emit q->offsetChanged();
839 void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
841 setOffset(o+offsetAdj);
845 \qmlproperty Component QtQuick2::PathView::highlight
846 This property holds the component to use as the highlight.
848 An instance of the highlight component will be created for each view.
849 The geometry of the resultant component instance will be managed by the view
850 so as to stay with the current item.
852 The below example demonstrates how to make a simple highlight. Note the use
853 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
854 the highlight is hidden when flicked away from the path.
859 visible: PathView.onPath
865 \sa highlightItem, highlightRangeMode
868 QQmlComponent *QQuickPathView::highlight() const
870 Q_D(const QQuickPathView);
871 return d->highlightComponent;
874 void QQuickPathView::setHighlight(QQmlComponent *highlight)
877 if (highlight != d->highlightComponent) {
878 d->highlightComponent = highlight;
879 d->createHighlight();
880 d->updateHighlight();
881 emit highlightChanged();
886 \qmlproperty Item QtQuick2::PathView::highlightItem
888 \c highlightItem holds the highlight item, which was created
889 from the \l highlight component.
893 QQuickItem *QQuickPathView::highlightItem()
895 Q_D(const QQuickPathView);
896 return d->highlightItem;
899 \qmlproperty real QtQuick2::PathView::preferredHighlightBegin
900 \qmlproperty real QtQuick2::PathView::preferredHighlightEnd
901 \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode
903 These properties set the preferred range of the highlight (current item)
904 within the view. The preferred values must be in the range 0.0-1.0.
906 If highlightRangeMode is set to \e PathView.NoHighlightRange
908 If highlightRangeMode is set to \e PathView.ApplyRange the view will
909 attempt to maintain the highlight within the range, however
910 the highlight can move outside of the range at the ends of the path
911 or due to a mouse interaction.
913 If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
914 move outside of the range. This means that the current item will change
915 if a keyboard or mouse action would cause the highlight to move
916 outside of the range.
918 Note that this is the correct way to influence where the
919 current item ends up when the view moves. For example, if you want the
920 currently selected item to be in the middle of the path, then set the
921 highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
922 Then, when the path scrolls,
923 the currently selected item will be the item at that position. This also applies to
924 when the currently selected item changes - it will scroll to within the preferred
925 highlight range. Furthermore, the behaviour of the current item index will occur
926 whether or not a highlight exists.
928 The default value is \e PathView.StrictlyEnforceRange.
930 Note that a valid range requires preferredHighlightEnd to be greater
931 than or equal to preferredHighlightBegin.
933 qreal QQuickPathView::preferredHighlightBegin() const
935 Q_D(const QQuickPathView);
936 return d->highlightRangeStart;
939 void QQuickPathView::setPreferredHighlightBegin(qreal start)
942 if (d->highlightRangeStart == start || start < 0 || start > 1.0)
944 d->highlightRangeStart = start;
945 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
947 emit preferredHighlightBeginChanged();
950 qreal QQuickPathView::preferredHighlightEnd() const
952 Q_D(const QQuickPathView);
953 return d->highlightRangeEnd;
956 void QQuickPathView::setPreferredHighlightEnd(qreal end)
959 if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
961 d->highlightRangeEnd = end;
962 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
964 emit preferredHighlightEndChanged();
967 QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
969 Q_D(const QQuickPathView);
970 return d->highlightRangeMode;
973 void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
976 if (d->highlightRangeMode == mode)
978 d->highlightRangeMode = mode;
979 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
980 if (d->haveHighlightRange) {
982 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
984 d->snapToIndex(index);
986 emit highlightRangeModeChanged();
990 \qmlproperty int QtQuick2::PathView::highlightMoveDuration
991 This property holds the move animation duration of the highlight delegate.
993 If the highlightRangeMode is StrictlyEnforceRange then this property
994 determines the speed that the items move along the path.
996 The default value for the duration is 300ms.
998 int QQuickPathView::highlightMoveDuration() const
1000 Q_D(const QQuickPathView);
1001 return d->highlightMoveDuration;
1004 void QQuickPathView::setHighlightMoveDuration(int duration)
1006 Q_D(QQuickPathView);
1007 if (d->highlightMoveDuration == duration)
1009 d->highlightMoveDuration = duration;
1010 emit highlightMoveDurationChanged();
1014 \qmlproperty real QtQuick2::PathView::dragMargin
1015 This property holds the maximum distance from the path that initiate mouse dragging.
1017 By default the path can only be dragged by clicking on an item. If
1018 dragMargin is greater than zero, a drag can be initiated by clicking
1019 within dragMargin pixels of the path.
1021 qreal QQuickPathView::dragMargin() const
1023 Q_D(const QQuickPathView);
1024 return d->dragMargin;
1027 void QQuickPathView::setDragMargin(qreal dragMargin)
1029 Q_D(QQuickPathView);
1030 if (d->dragMargin == dragMargin)
1032 d->dragMargin = dragMargin;
1033 emit dragMarginChanged();
1037 \qmlproperty real QtQuick2::PathView::flickDeceleration
1038 This property holds the rate at which a flick will decelerate.
1042 qreal QQuickPathView::flickDeceleration() const
1044 Q_D(const QQuickPathView);
1045 return d->deceleration;
1048 void QQuickPathView::setFlickDeceleration(qreal dec)
1050 Q_D(QQuickPathView);
1051 if (d->deceleration == dec)
1053 d->deceleration = dec;
1054 emit flickDecelerationChanged();
1058 \qmlproperty real QtQuick2::PathView::maximumFlickVelocity
1059 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1061 The default value is platform dependent.
1063 qreal QQuickPathView::maximumFlickVelocity() const
1065 Q_D(const QQuickPathView);
1066 return d->maximumFlickVelocity;
1069 void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1071 Q_D(QQuickPathView);
1072 if (vel == d->maximumFlickVelocity)
1074 d->maximumFlickVelocity = vel;
1075 emit maximumFlickVelocityChanged();
1080 \qmlproperty bool QtQuick2::PathView::interactive
1082 A user cannot drag or flick a PathView that is not interactive.
1084 This property is useful for temporarily disabling flicking. This allows
1085 special interaction with PathView's children.
1087 bool QQuickPathView::isInteractive() const
1089 Q_D(const QQuickPathView);
1090 return d->interactive;
1093 void QQuickPathView::setInteractive(bool interactive)
1095 Q_D(QQuickPathView);
1096 if (interactive != d->interactive) {
1097 d->interactive = interactive;
1100 emit interactiveChanged();
1105 \qmlproperty bool QtQuick2::PathView::moving
1107 This property holds whether the view is currently moving
1108 due to the user either dragging or flicking the view.
1110 bool QQuickPathView::isMoving() const
1112 Q_D(const QQuickPathView);
1117 \qmlproperty bool QtQuick2::PathView::flicking
1119 This property holds whether the view is currently moving
1120 due to the user flicking the view.
1122 bool QQuickPathView::isFlicking() const
1124 Q_D(const QQuickPathView);
1129 \qmlproperty bool QtQuick2::PathView::dragging
1131 This property holds whether the view is currently moving
1132 due to the user dragging the view.
1134 bool QQuickPathView::isDragging() const
1136 Q_D(const QQuickPathView);
1141 \qmlsignal QtQuick2::PathView::onMovementStarted()
1143 This handler is called when the view begins moving due to user
1148 \qmlsignal QtQuick2::PathView::onMovementEnded()
1150 This handler is called when the view stops moving due to user
1151 interaction. If a flick was generated, this handler will
1152 be triggered once the flick stops. If a flick was not
1153 generated, the handler will be triggered when the
1154 user stops dragging - i.e. a mouse or touch release.
1158 \qmlsignal QtQuick2::PathView::onFlickStarted()
1160 This handler is called when the view is flicked. A flick
1161 starts from the point that the mouse or touch is released,
1162 while still in motion.
1166 \qmlsignal QtQuick2::PathView::onFlickEnded()
1168 This handler is called when the view stops moving due to a flick.
1172 \qmlsignal QtQuick2::PathView::onDragStarted()
1174 This handler is called when the view starts to be dragged due to user
1179 \qmlsignal QtQuick2::PathView::onDragEnded()
1181 This handler is called when the user stops dragging the view.
1183 If the velocity of the drag is suffient at the time the
1184 touch/mouse button is released then a flick will start.
1188 \qmlproperty Component QtQuick2::PathView::delegate
1190 The delegate provides a template defining each item instantiated by the view.
1191 The index is exposed as an accessible \c index property. Properties of the
1192 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
1194 The number of objects and bindings in the delegate has a direct effect on the
1195 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
1196 that is not needed for the normal display of the delegate in a \l Loader which
1197 can load additional components when needed.
1199 Note that the PathView will layout the items based on the size of the root
1200 item in the delegate.
1202 Here is an example delegate:
1203 \snippet qml/pathview/pathview.qml 1
1205 QQmlComponent *QQuickPathView::delegate() const
1207 Q_D(const QQuickPathView);
1209 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model))
1210 return dataModel->delegate();
1216 void QQuickPathView::setDelegate(QQmlComponent *delegate)
1218 Q_D(QQuickPathView);
1219 if (delegate == this->delegate())
1222 d->model = new QQuickVisualDataModel(qmlContext(this));
1225 if (QQuickVisualDataModel *dataModel = qobject_cast<QQuickVisualDataModel*>(d->model)) {
1226 int oldCount = dataModel->count();
1227 dataModel->setDelegate(delegate);
1228 d->modelCount = dataModel->count();
1230 if (oldCount != dataModel->count())
1231 emit countChanged();
1232 emit delegateChanged();
1237 \qmlproperty int QtQuick2::PathView::pathItemCount
1238 This property holds the number of items visible on the path at any one time.
1240 Setting pathItemCount to undefined will show all items on the path.
1242 int QQuickPathView::pathItemCount() const
1244 Q_D(const QQuickPathView);
1245 return d->pathItems;
1248 void QQuickPathView::setPathItemCount(int i)
1250 Q_D(QQuickPathView);
1251 if (i == d->pathItems)
1256 d->updateMappedRange();
1257 if (d->isValid() && isComponentComplete()) {
1260 emit pathItemCountChanged();
1263 void QQuickPathView::resetPathItemCount()
1265 Q_D(QQuickPathView);
1266 if (-1 == d->pathItems)
1269 d->updateMappedRange();
1270 if (d->isValid() && isComponentComplete())
1272 emit pathItemCountChanged();
1276 \qmlproperty int QtQuick2::PathView::cacheItemCount
1277 This property holds the maximum number of items to cache off the path.
1279 For example, a PathView with a model containing 20 items, a pathItemCount
1280 of 10, and an cacheItemCount of 4 will create up to 14 items, with 10 visible
1281 on the path and 4 invisible cached items.
1283 The cached delegates are created asynchronously,
1284 allowing creation to occur across multiple frames and reducing the
1285 likelihood of skipping frames.
1287 Setting this value can improve the smoothness of scrolling behavior at the expense
1288 of additional memory usage. It is not a substitute for creating efficient
1289 delegates; the fewer objects and bindings in a delegate, the faster a view can be
1294 int QQuickPathView::cacheItemCount() const
1296 Q_D(const QQuickPathView);
1297 return d->cacheSize;
1300 void QQuickPathView::setCacheItemCount(int i)
1302 Q_D(QQuickPathView);
1303 if (i == d->cacheSize || i < 0)
1307 d->updateMappedRange();
1309 emit cacheItemCountChanged();
1313 \qmlproperty enumeration QtQuick2::PathView::snapMode
1315 This property determines how the items will settle following a drag or flick.
1316 The possible values are:
1319 \li PathView.NoSnap (default) - the items stop anywhere along the path.
1320 \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
1321 \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
1322 \l preferredHighlightBegin at the time the press is released. This mode is particularly
1323 useful for moving one page at a time.
1326 \c snapMode does not affect the \l currentIndex. To update the
1327 \l currentIndex as the view is moved, set \l highlightRangeMode
1328 to \c PathView.StrictlyEnforceRange (default for PathView).
1330 \sa highlightRangeMode
1332 QQuickPathView::SnapMode QQuickPathView::snapMode() const
1334 Q_D(const QQuickPathView);
1338 void QQuickPathView::setSnapMode(SnapMode mode)
1340 Q_D(QQuickPathView);
1341 if (mode == d->snapMode)
1344 emit snapModeChanged();
1348 \qmlmethod QtQuick2::PathView::positionViewAtIndex(int index, PositionMode mode)
1350 Positions the view such that the \a index is at the position specified by
1354 \li PathView.Beginning - position item at the beginning of the path.
1355 \li PathView.Center - position item in the center of the path.
1356 \li PathView.End - position item at the end of the path.
1357 \li PathView.Contain - ensure the item is positioned on the path.
1358 \li PathView.SnapPosition - position the item at \l preferredHighlightBegin. This mode
1359 is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
1363 \b Note: methods should only be called after the Component has completed. To position
1364 the view at startup, this method should be called by Component.onCompleted. For
1365 example, to position the view at the end:
1368 Component.onCompleted: positionViewAtIndex(count - 1, PathView.End)
1371 void QQuickPathView::positionViewAtIndex(int index, int mode)
1373 Q_D(QQuickPathView);
1376 if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView
1379 if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems))
1382 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1383 int idx = (index+d->modelCount) % d->modelCount;
1384 bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1385 || d->snapMode != QQuickPathView::NoSnap);
1390 beginOffset = d->modelCount - idx - qFloor(count * d->highlightRangeStart);
1391 endOffset = beginOffset + count - 1;
1393 beginOffset = d->modelCount - idx;
1394 // Small offset since the last point coincides with the first and
1395 // this the only "end" position that gives the expected visual result.
1396 qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001;
1397 endOffset = qmlMod(beginOffset + count, d->modelCount) - adj;
1399 qreal offset = d->offset;
1402 offset = beginOffset;
1408 if (beginOffset < endOffset)
1409 offset = (beginOffset + endOffset)/2;
1411 offset = (beginOffset + (endOffset + d->modelCount))/2;
1413 offset = qRound(offset);
1416 if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset))
1417 || (d->offset < beginOffset && d->offset > endOffset)) {
1418 qreal diff1 = qmlMod(beginOffset - d->offset + d->modelCount, d->modelCount);
1419 qreal diff2 = qmlMod(d->offset - endOffset + d->modelCount, d->modelCount);
1421 offset = beginOffset;
1427 offset = d->modelCount - idx;
1436 \qmlmethod int QtQuick2::PathView::indexAt(int x, int y)
1438 Returns the index of the item containing the point \a x, \a y in content
1439 coordinates. If there is no item at the point specified, -1 is returned.
1441 \b Note: methods should only be called after the Component has completed.
1443 int QQuickPathView::indexAt(qreal x, qreal y) const
1445 Q_D(const QQuickPathView);
1449 for (int idx = 0; idx < d->items.count(); ++idx) {
1450 QQuickItem *item = d->items.at(idx);
1451 QPointF p = item->mapFromItem(this, QPointF(x, y));
1452 if (item->contains(p))
1453 return (d->firstIndex + idx) % d->modelCount;
1460 \qmlmethod Item QtQuick2::PathView::itemAt(int x, int y)
1462 Returns the item containing the point \a x, \a y in content
1463 coordinates. If there is no item at the point specified, null is returned.
1465 \b Note: methods should only be called after the Component has completed.
1467 QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const
1469 Q_D(const QQuickPathView);
1473 for (int idx = 0; idx < d->items.count(); ++idx) {
1474 QQuickItem *item = d->items.at(idx);
1475 QPointF p = item->mapFromItem(this, QPointF(x, y));
1476 if (item->contains(p))
1483 QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1485 qreal samples = qMin(path->path().length()/5, qreal(500.0));
1486 qreal res = path->path().length()/samples;
1488 qreal mindist = 1e10; // big number
1489 QPointF nearPoint = path->pointAt(0);
1493 for (qreal i=1; i < samples; i++) {
1494 QPointF pt = path->pointAt(i/samples);
1495 QPointF diff = pt - point;
1496 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1497 if (dist < mindist) {
1505 qreal approxPc = nearPc;
1506 for (qreal i = approxPc-1.0; i < approxPc+1.0; i += 1/(2*res)) {
1507 QPointF pt = path->pointAt(i/samples);
1508 QPointF diff = pt - point;
1509 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1510 if (dist < mindist) {
1518 *nearPercent = nearPc / samples;
1523 void QQuickPathViewPrivate::addVelocitySample(qreal v)
1525 velocityBuffer.append(v);
1526 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1527 velocityBuffer.remove(0);
1530 qreal QQuickPathViewPrivate::calcVelocity() const
1533 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1534 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1535 for (int i = 0; i < count; ++i) {
1536 qreal v = velocityBuffer.at(i);
1544 qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
1546 if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
1547 return event->timestamp();
1550 return QQuickItemPrivate::elapsed(timer);
1553 void QQuickPathView::mousePressEvent(QMouseEvent *event)
1555 Q_D(QQuickPathView);
1556 if (d->interactive) {
1557 d->handleMousePressEvent(event);
1560 QQuickItem::mousePressEvent(event);
1564 void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1566 if (!interactive || !items.count() || !model || !modelCount)
1568 velocityBuffer.clear();
1570 for (; idx < items.count(); ++idx) {
1571 QQuickItem *item = items.at(idx);
1572 if (item->contains(item->mapFromScene(event->windowPos())))
1575 if (idx == items.count() && dragMargin == 0.) // didn't click on an item
1578 startPoint = pointNear(event->localPos(), &startPc);
1579 if (idx == items.count()) {
1580 qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y());
1581 if (distance > dragMargin)
1586 if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
1587 stealMouse = true; // If we've been flicked then steal the click.
1591 QQuickItemPrivate::start(timer);
1592 lastPosTime = computeCurrentTime(event);
1596 void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1598 Q_D(QQuickPathView);
1599 if (d->interactive) {
1600 d->handleMouseMoveEvent(event);
1602 setKeepMouseGrab(true);
1605 QQuickItem::mouseMoveEvent(event);
1609 void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1611 Q_Q(QQuickPathView);
1612 if (!interactive || !timer.isValid() || !model || !modelCount)
1615 qint64 currentTimestamp = computeCurrentTime(event);
1617 QPointF pathPoint = pointNear(event->localPos(), &newPc);
1619 QPointF delta = pathPoint - startPoint;
1620 if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
1624 moveReason = QQuickPathViewPrivate::Mouse;
1625 int count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
1626 qreal diff = (newPc - startPc)*count;
1628 q->setOffset(offset + diff);
1630 if (diff > modelCount/2)
1632 else if (diff < -modelCount/2)
1635 qint64 elapsed = currentTimestamp - lastPosTime;
1637 addVelocitySample(diff / (qreal(elapsed) / 1000.));
1641 emit q->movingChanged();
1642 emit q->movementStarted();
1647 lastPosTime = currentTimestamp;
1650 void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1652 Q_D(QQuickPathView);
1653 if (d->interactive) {
1654 d->handleMouseReleaseEvent(event);
1658 QQuickItem::mouseReleaseEvent(event);
1662 void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
1664 Q_Q(QQuickPathView);
1666 q->setKeepMouseGrab(false);
1668 if (!interactive || !timer.isValid() || !model || !modelCount) {
1671 q->movementEnding();
1675 qreal velocity = calcVelocity();
1676 qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
1677 qreal pixelVelocity = (path->path().length()/count) * velocity;
1678 if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
1679 if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
1681 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1682 velocity = maxVel / (path->path().length()/count);
1684 // Calculate the distance to be travelled
1685 qreal v2 = velocity*velocity;
1686 qreal accel = deceleration/10;
1688 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1689 || snapMode != QQuickPathView::NoSnap)) {
1690 if (snapMode == QQuickPathView::SnapOneItem) {
1691 // encourage snapping one item in direction of motion
1693 dist = qRound(0.5 + offset) - offset;
1695 dist = qRound(0.5 - offset) + offset;
1697 // + 0.25 to encourage moving at least one item in the flick direction
1698 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1700 // round to nearest item.
1702 dist = qRound(dist + offset) - offset;
1704 dist = qRound(dist - offset) + offset;
1706 // Calculate accel required to stop on item boundary
1711 accel = v2 / (2.0f * qAbs(dist));
1714 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
1716 flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
1718 moveOffset.setValue(offset);
1719 tl.accel(moveOffset, velocity, accel, dist);
1720 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1723 emit q->flickingChanged();
1724 emit q->flickStarted();
1732 q->movementEnding();
1735 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1737 Q_D(QQuickPathView);
1738 QPointF localPos = mapFromScene(event->windowPos());
1740 QQuickWindow *c = window();
1741 QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
1742 bool stealThisEvent = d->stealMouse;
1743 if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
1744 QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
1745 event->button(), event->buttons(), event->modifiers());
1746 mouseEvent.setAccepted(false);
1748 switch (mouseEvent.type()) {
1749 case QEvent::MouseMove:
1750 d->handleMouseMoveEvent(&mouseEvent);
1752 case QEvent::MouseButtonPress:
1753 d->handleMousePressEvent(&mouseEvent);
1754 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1756 case QEvent::MouseButtonRelease:
1757 d->handleMouseReleaseEvent(&mouseEvent);
1762 grabber = c->mouseGrabberItem();
1763 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1766 return d->stealMouse;
1767 } else if (d->timer.isValid()) {
1768 d->timer.invalidate();
1771 if (event->type() == QEvent::MouseButtonRelease)
1772 d->stealMouse = false;
1776 bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1778 Q_D(QQuickPathView);
1779 if (!isVisible() || !d->interactive)
1780 return QQuickItem::childMouseEventFilter(i, e);
1782 switch (e->type()) {
1783 case QEvent::MouseButtonPress:
1784 case QEvent::MouseMove:
1785 case QEvent::MouseButtonRelease:
1786 return sendMouseEvent(static_cast<QMouseEvent *>(e));
1791 return QQuickItem::childMouseEventFilter(i, e);
1794 void QQuickPathView::mouseUngrabEvent()
1796 Q_D(QQuickPathView);
1797 if (d->stealMouse) {
1798 // if our mouse grab has been removed (probably by a Flickable),
1800 d->stealMouse = false;
1801 setKeepMouseGrab(false);
1802 d->timer.invalidate();
1804 d->setDragging(false);
1805 if (!d->tl.isActive())
1810 void QQuickPathView::updatePolish()
1812 QQuickItem::updatePolish();
1816 void QQuickPathView::componentComplete()
1818 Q_D(QQuickPathView);
1819 if (d->model && d->ownModel)
1820 static_cast<QQuickVisualDataModel *>(d->model.data())->componentComplete();
1822 QQuickItem::componentComplete();
1825 d->modelCount = d->model->count();
1826 if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex
1827 d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
1830 d->createHighlight();
1832 d->updateHighlight();
1836 emit countChanged();
1839 void QQuickPathView::refill()
1841 Q_D(QQuickPathView);
1843 d->layoutScheduled = false;
1845 if (!d->isValid() || !isComponentComplete())
1848 bool currentVisible = false;
1849 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1851 // first move existing items and remove items off path
1852 int idx = d->firstIndex;
1853 QList<QQuickItem*>::iterator it = d->items.begin();
1854 while (it != d->items.end()) {
1855 qreal pos = d->positionOfIndex(idx);
1856 QQuickItem *item = *it;
1858 d->updateItem(item, pos);
1859 if (idx == d->currentIndex) {
1860 currentVisible = true;
1861 d->currentItemOffset = pos;
1865 d->updateItem(item, pos);
1866 if (QQuickPathViewAttached *att = d->attached(item))
1867 att->setOnPath(pos < 1.0);
1868 if (!d->isInBound(pos, d->mappedRange - d->mappedCache, 1.0 + d->mappedCache)) {
1869 // qDebug() << "release";
1870 d->releaseItem(item);
1871 if (it == d->items.begin()) {
1872 if (++d->firstIndex >= d->modelCount) {
1876 it = d->items.erase(it);
1882 if (idx >= d->modelCount)
1886 if (!d->items.count())
1889 bool waiting = false;
1890 if (d->modelCount) {
1891 // add items to beginning and end
1892 if (d->items.count() < count+d->cacheSize) {
1893 int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1894 qreal startPos = 0.0;
1895 if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1896 || d->snapMode != QQuickPathView::NoSnap))
1897 startPos = d->highlightRangeStart;
1898 if (d->firstIndex >= 0) {
1899 startPos = d->positionOfIndex(d->firstIndex);
1900 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1902 qreal pos = d->positionOfIndex(idx);
1903 while ((d->isInBound(pos, startPos, 1.0 + d->mappedCache) || !d->items.count()) && d->items.count() < count+d->cacheSize) {
1904 // qDebug() << "append" << idx;
1905 QQuickItem *item = d->getItem(idx, idx+1, pos >= 1.0);
1910 if (d->currentIndex == idx) {
1911 currentVisible = true;
1912 d->currentItemOffset = pos;
1914 if (d->items.count() == 0)
1915 d->firstIndex = idx;
1916 d->items.append(item);
1917 d->updateItem(item, pos);
1919 if (idx >= d->modelCount)
1921 pos = d->positionOfIndex(idx);
1924 idx = d->firstIndex - 1;
1926 idx = d->modelCount - 1;
1927 pos = d->positionOfIndex(idx);
1928 while (!waiting && d->isInBound(pos, d->mappedRange - d->mappedCache, startPos) && d->items.count() < count+d->cacheSize) {
1929 // qDebug() << "prepend" << idx;
1930 QQuickItem *item = d->getItem(idx, idx+1, pos >= 1.0);
1935 if (d->currentIndex == idx) {
1936 currentVisible = true;
1937 d->currentItemOffset = pos;
1939 d->items.prepend(item);
1940 d->updateItem(item, pos);
1941 d->firstIndex = idx;
1942 idx = d->firstIndex - 1;
1944 idx = d->modelCount - 1;
1945 pos = d->positionOfIndex(idx);
1950 if (!currentVisible) {
1951 d->currentItemOffset = 1.0;
1952 if (d->currentItem) {
1953 d->updateItem(d->currentItem, 1.0);
1954 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
1955 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
1956 d->updateItem(d->currentItem, 1.0);
1957 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1958 att->setIsCurrentItem(true);
1961 } else if (!waiting && !d->currentItem) {
1962 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
1963 d->currentItem->setFocus(true);
1964 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
1965 att->setIsCurrentItem(true);
1969 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
1970 d->updateItem(d->highlightItem, d->highlightRangeStart);
1971 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1972 att->setOnPath(true);
1973 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
1974 d->updateItem(d->highlightItem, d->currentItemOffset);
1975 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
1976 att->setOnPath(currentVisible);
1978 while (d->itemCache.count())
1979 d->releaseItem(d->itemCache.takeLast());
1982 void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
1984 Q_D(QQuickPathView);
1985 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
1989 d->modelCount = d->model->count();
1991 emit countChanged();
1995 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
1998 const int modelCount = d->modelCount;
2001 bool currentChanged = false;
2002 bool changedOffset = false;
2003 foreach (const QQuickChangeSet::Remove &r, changeSet.removes()) {
2004 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
2005 d->currentIndex -= r.count;
2006 currentChanged = true;
2007 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
2008 // current item has been removed.
2011 moveOffset = d->currentIndex - r.index;
2012 } else if (d->currentItem) {
2013 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2014 att->setIsCurrentItem(true);
2015 d->releaseItem(d->currentItem);
2018 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
2019 currentChanged = true;
2022 if (r.index > d->currentIndex) {
2023 changedOffset = true;
2024 d->offset -= r.count;
2025 d->offsetAdj -= r.count;
2027 d->modelCount -= r.count;
2029 foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
2030 if (d->modelCount) {
2031 if (moveId == -1 && i.index <= d->currentIndex) {
2032 d->currentIndex += i.count;
2033 currentChanged = true;
2035 if (moveId != -1 && moveId == i.moveId) {
2036 d->currentIndex = i.index + moveOffset;
2037 currentChanged = true;
2039 if (i.index > d->currentIndex) {
2040 d->offset += i.count;
2041 d->offsetAdj += i.count;
2042 changedOffset = true;
2046 d->modelCount += i.count;
2049 d->offset = qmlMod(d->offset, d->modelCount);
2051 d->offset += d->modelCount;
2052 if (d->currentIndex == -1)
2053 d->currentIndex = d->calcCurrentIndex();
2055 d->itemCache += d->items;
2058 if (!d->modelCount) {
2059 while (d->itemCache.count())
2060 d->releaseItem(d->itemCache.takeLast());
2062 changedOffset = true;
2063 d->tl.reset(d->moveOffset);
2065 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2066 d->offset = qmlMod(d->modelCount - d->currentIndex, d->modelCount);
2067 changedOffset = true;
2070 d->updateMappedRange();
2071 d->scheduleLayout();
2074 emit offsetChanged();
2076 emit currentIndexChanged();
2077 if (d->modelCount != modelCount)
2078 emit countChanged();
2081 void QQuickPathView::destroyingItem(QQuickItem *item)
2086 void QQuickPathView::ticked()
2088 Q_D(QQuickPathView);
2092 void QQuickPathView::movementEnding()
2094 Q_D(QQuickPathView);
2096 d->flicking = false;
2097 emit flickingChanged();
2100 if (d->moving && !d->stealMouse) {
2102 emit movingChanged();
2103 emit movementEnded();
2107 // find the item closest to the snap position
2108 int QQuickPathViewPrivate::calcCurrentIndex()
2111 if (modelCount && model && items.count()) {
2112 offset = qmlMod(offset, modelCount);
2114 offset += modelCount;
2115 current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
2116 current = current % modelCount;
2122 void QQuickPathViewPrivate::createCurrentItem()
2124 if (requestedIndex != -1)
2126 int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
2127 if (itemIndex < items.count()) {
2128 if ((currentItem = getItem(currentIndex, currentIndex))) {
2129 currentItem->setFocus(true);
2130 if (QQuickPathViewAttached *att = attached(currentItem))
2131 att->setIsCurrentItem(true);
2133 } else if (currentIndex >= 0 && currentIndex < modelCount) {
2134 if ((currentItem = getItem(currentIndex, currentIndex))) {
2135 updateItem(currentItem, 1.0);
2136 if (QQuickPathViewAttached *att = attached(currentItem))
2137 att->setIsCurrentItem(true);
2142 void QQuickPathViewPrivate::updateCurrent()
2144 Q_Q(QQuickPathView);
2145 if (moveReason == SetIndex)
2147 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
2150 int idx = calcCurrentIndex();
2151 if (model && (idx != currentIndex || !currentItem)) {
2153 if (QQuickPathViewAttached *att = attached(currentItem))
2154 att->setIsCurrentItem(false);
2155 releaseItem(currentItem);
2157 int oldCurrentIndex = currentIndex;
2160 createCurrentItem();
2161 if (oldCurrentIndex != currentIndex)
2162 emit q->currentIndexChanged();
2163 emit q->currentItemChanged();
2167 void QQuickPathViewPrivate::fixOffsetCallback(void *d)
2169 ((QQuickPathViewPrivate *)d)->fixOffset();
2172 void QQuickPathViewPrivate::fixOffset()
2174 Q_Q(QQuickPathView);
2175 if (model && items.count()) {
2176 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
2177 || snapMode != QQuickPathView::NoSnap)) {
2178 int curr = calcCurrentIndex();
2179 if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
2180 q->setCurrentIndex(curr);
2187 void QQuickPathViewPrivate::snapToIndex(int index)
2189 if (!model || modelCount <= 0)
2192 qreal targetOffset = qmlMod(modelCount - index, modelCount);
2194 if (offset == targetOffset)
2199 tl.reset(moveOffset);
2200 moveOffset.setValue(offset);
2202 const int duration = highlightMoveDuration;
2205 tl.set(moveOffset, targetOffset);
2206 } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2.0)) {
2207 qreal distance = modelCount - targetOffset + offset;
2208 if (targetOffset > moveOffset) {
2209 tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
2210 tl.set(moveOffset, modelCount);
2211 tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
2213 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2215 } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2.0) {
2216 qreal distance = modelCount - offset + targetOffset;
2217 if (targetOffset < moveOffset) {
2218 tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
2219 tl.set(moveOffset, 0.0);
2220 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
2222 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2225 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2227 moveDirection = Shortest;
2230 QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
2232 return new QQuickPathViewAttached(obj);