1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickpathview_p.h"
41#include "qquickpathview_p_p.h"
42#include "qquickwindow.h"
43#include "qquickflickablebehavior_p.h" //Contains flicking behavior defines
44#include "qquicktext_p.h"
45
46#include <QtQuick/private/qquickstate_p.h>
47#include <private/qqmlglobal_p.h>
48#include <private/qqmlopenmetaobject_p.h>
49#include <private/qqmlchangeset_p.h>
50
51#include <QtQml/qqmlinfo.h>
52#include <QtGui/qevent.h>
53#include <QtGui/qevent.h>
54#include <QtGui/qguiapplication.h>
55#include <QtGui/qstylehints.h>
56#include <QtCore/qmath.h>
57
58#include <cmath>
59
60QT_BEGIN_NAMESPACE
61
62Q_DECLARE_LOGGING_CATEGORY(lcItemViewDelegateLifecycle)
63Q_LOGGING_CATEGORY(lcPathView, "qt.quick.pathview")
64
65static const qreal MinimumFlickVelocity = 75;
66
67static QQmlOpenMetaObjectType *qPathViewAttachedType = nullptr;
68
69QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
70: QObject(parent), m_percent(-1), m_view(nullptr), m_onPath(false), m_isCurrent(false)
71{
72 if (qPathViewAttachedType) {
73 m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
74 m_metaobject->setCached(true);
75 } else {
76 m_metaobject = new QQmlOpenMetaObject(this);
77 }
78}
79
80QQuickPathViewAttached::~QQuickPathViewAttached()
81{
82}
83
84QVariant QQuickPathViewAttached::value(const QByteArray &name) const
85{
86 return m_metaobject->value(name);
87}
88void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
89{
90 m_metaobject->setValue(name, val);
91}
92
93QQuickPathViewPrivate::QQuickPathViewPrivate()
94 : path(nullptr), currentIndex(0), currentItemOffset(0), startPc(0)
95 , offset(0), offsetAdj(0), mappedRange(1), mappedCache(0)
96 , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
97 , autoHighlight(true), highlightUp(false), layoutScheduled(false)
98 , moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false)
99 , inRefill(false)
100 , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY)
101 , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
102 , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedZ(0)
103 , moveReason(Other), movementDirection(QQuickPathView::Shortest), moveDirection(QQuickPathView::Shortest)
104 , attType(nullptr), highlightComponent(nullptr), highlightItem(nullptr)
105 , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
106 , highlightPosition(0)
107 , highlightRangeStart(0), highlightRangeEnd(0)
108 , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
109 , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
110{
111}
112
113void QQuickPathViewPrivate::init()
114{
115 Q_Q(QQuickPathView);
116 offset = 0;
117 q->setAcceptedMouseButtons(Qt::LeftButton);
118 q->setFlag(flag: QQuickItem::ItemIsFocusScope);
119 q->setFiltersChildMouseEvents(true);
120 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()),
121 q, QQuickPathView, SLOT(ticked()));
122 timer.invalidate();
123 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
124 q, QQuickPathView, SLOT(movementEnding()));
125}
126
127QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool async)
128{
129 Q_Q(QQuickPathView);
130 requestedIndex = modelIndex;
131 requestedZ = z;
132 inRequest = true;
133 QObject *object = model->object(index: modelIndex, incubationMode: async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
134 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
135 if (!item) {
136 if (object) {
137 model->release(object);
138 if (!delegateValidated) {
139 delegateValidated = true;
140 QObject* delegate = q->delegate();
141 qmlWarning(me: delegate ? delegate : q) << QQuickPathView::tr(s: "Delegate must be of Item type");
142 }
143 }
144 } else {
145 item->setParentItem(q);
146 requestedIndex = -1;
147 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
148 itemPrivate->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry);
149 }
150 inRequest = false;
151 return item;
152}
153
154void QQuickPathView::createdItem(int index, QObject *object)
155{
156 Q_D(QQuickPathView);
157 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
158 if (d->requestedIndex != index) {
159 qPathViewAttachedType = d->attachedType();
160 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(obj: item));
161 qPathViewAttachedType = nullptr;
162 if (att) {
163 att->m_view = this;
164 att->setOnPath(false);
165 }
166 item->setParentItem(this);
167 d->updateItem(item, 1);
168 } else {
169 d->requestedIndex = -1;
170 if (!d->inRequest)
171 refill();
172 }
173}
174
175void QQuickPathView::initItem(int index, QObject *object)
176{
177 Q_D(QQuickPathView);
178 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
179 if (item && d->requestedIndex == index) {
180 QQuickItemPrivate::get(item)->setCulled(true);
181 item->setParentItem(this);
182 qPathViewAttachedType = d->attachedType();
183 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(obj: item));
184 qPathViewAttachedType = nullptr;
185 if (att) {
186 att->m_view = this;
187 qreal percent = d->positionOfIndex(index);
188 if (percent < 1 && d->path) {
189 const auto attributes = d->path->attributes();
190 for (const QString &attr : attributes)
191 att->setValue(name: attr.toUtf8(), val: d->path->attributeAt(attr, percent));
192 item->setZ(d->requestedZ);
193 }
194 att->setOnPath(percent < 1);
195 }
196 }
197}
198
199void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
200{
201 if (!item || !model)
202 return;
203 qCDebug(lcItemViewDelegateLifecycle) << "release" << item;
204 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
205 itemPrivate->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
206 QQmlInstanceModel::ReleaseFlags flags = model->release(object: item);
207 if (!flags) {
208 // item was not destroyed, and we no longer reference it.
209 if (QQuickPathViewAttached *att = attached(item))
210 att->setOnPath(false);
211 } else if (flags & QQmlInstanceModel::Destroyed) {
212 // but we still reference it
213 item->setParentItem(nullptr);
214 }
215}
216
217QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
218{
219 return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(obj: item, create: false));
220}
221
222QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
223{
224 Q_Q(QQuickPathView);
225 if (!attType) {
226 // pre-create one metatype to share with all attached objects
227 attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
228 if (path) {
229 const auto attributes = path->attributes();
230 for (const QString &attr : attributes)
231 attType->createProperty(name: attr.toUtf8());
232 }
233 }
234
235 return attType;
236}
237
238void QQuickPathViewPrivate::clear()
239{
240 if (currentItem) {
241 releaseItem(item: currentItem);
242 currentItem = nullptr;
243 }
244
245 for (QQuickItem *p : qAsConst(t&: items))
246 releaseItem(item: p);
247
248 for (QQuickItem *p : qAsConst(t&: itemCache))
249 releaseItem(item: p);
250
251 if (requestedIndex >= 0) {
252 if (model)
253 model->cancel(requestedIndex);
254 requestedIndex = -1;
255 }
256
257 items.clear();
258 itemCache.clear();
259 tl.clear();
260}
261
262void QQuickPathViewPrivate::updateMappedRange()
263{
264 if (model && pathItems != -1 && pathItems < modelCount) {
265 mappedRange = qreal(modelCount)/pathItems;
266 mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
267 } else {
268 mappedRange = 1;
269 mappedCache = 0;
270 }
271}
272
273qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
274{
275 qreal pos = -1;
276
277 if (model && index >= 0 && index < modelCount) {
278 qreal start = 0;
279 if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
280 || snapMode != QQuickPathView::NoSnap))
281 start = highlightRangeStart;
282 qreal globalPos = index + offset;
283 globalPos = std::fmod(x: globalPos, y: qreal(modelCount)) / modelCount;
284 if (pathItems != -1 && pathItems < modelCount) {
285 globalPos += start / mappedRange;
286 globalPos = std::fmod(x: globalPos, y: qreal(1));
287 pos = globalPos * mappedRange;
288 } else {
289 pos = std::fmod(x: globalPos + start, y: qreal(1));
290 }
291 }
292
293 return pos;
294}
295
296// returns true if position is between lower and upper, taking into
297// account the circular space.
298bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower, qreal upper) const
299{
300 if (qFuzzyCompare(p1: lower, p2: upper))
301 return true;
302 if (lower > upper) {
303 if (position > upper && position > lower)
304 position -= mappedRange;
305 lower -= mappedRange;
306 }
307 return position >= lower && position < upper;
308}
309
310void QQuickPathViewPrivate::createHighlight()
311{
312 Q_Q(QQuickPathView);
313 if (!q->isComponentComplete())
314 return;
315
316 bool changed = false;
317 if (highlightItem) {
318 highlightItem->setParentItem(nullptr);
319 highlightItem->deleteLater();
320 highlightItem = nullptr;
321 changed = true;
322 }
323
324 QQuickItem *item = nullptr;
325 if (highlightComponent) {
326 QQmlContext *creationContext = highlightComponent->creationContext();
327 QQmlContext *highlightContext = new QQmlContext(
328 creationContext ? creationContext : qmlContext(q));
329 QObject *nobj = highlightComponent->create(context: highlightContext);
330 if (nobj) {
331 QQml_setParent_noEvent(object: highlightContext, parent: nobj);
332 item = qobject_cast<QQuickItem *>(object: nobj);
333 if (!item)
334 delete nobj;
335 } else {
336 delete highlightContext;
337 }
338 } else {
339 item = new QQuickItem;
340 }
341 if (item) {
342 QQml_setParent_noEvent(object: item, parent: q);
343 item->setParentItem(q);
344 highlightItem = item;
345 changed = true;
346 }
347 if (changed)
348 emit q->highlightItemChanged();
349}
350
351void QQuickPathViewPrivate::updateHighlight()
352{
353 Q_Q(QQuickPathView);
354 if (!q->isComponentComplete() || !isValid())
355 return;
356 if (highlightItem) {
357 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
358 updateItem(highlightItem, highlightRangeStart);
359 } else {
360 qreal target = currentIndex;
361
362 offsetAdj = 0;
363 tl.reset(moveHighlight);
364 moveHighlight.setValue(highlightPosition);
365
366 const int duration = highlightMoveDuration;
367
368 if (target - highlightPosition > modelCount/2) {
369 highlightUp = false;
370 qreal distance = modelCount - target + highlightPosition;
371 tl.move(moveHighlight, destination: 0, QEasingCurve(QEasingCurve::InQuad), time: int(duration * highlightPosition / distance));
372 tl.set(moveHighlight, modelCount-0.01);
373 tl.move(moveHighlight, destination: target, QEasingCurve(QEasingCurve::OutQuad), time: int(duration * (modelCount-target) / distance));
374 } else if (target - highlightPosition <= -modelCount/2) {
375 highlightUp = true;
376 qreal distance = modelCount - highlightPosition + target;
377 tl.move(moveHighlight, destination: modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), time: int(duration * (modelCount-highlightPosition) / distance));
378 tl.set(moveHighlight, 0);
379 tl.move(moveHighlight, destination: target, QEasingCurve(QEasingCurve::OutQuad), time: int(duration * target / distance));
380 } else {
381 highlightUp = highlightPosition - target < 0;
382 tl.move(moveHighlight, destination: target, QEasingCurve(QEasingCurve::InOutQuad), time: duration);
383 }
384 }
385 }
386}
387
388void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
389{
390 if (!(qFuzzyCompare(p1: pos, p2: highlightPosition))) {
391 qreal start = 0;
392 qreal end = 1;
393 if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
394 start = highlightRangeStart;
395 end = highlightRangeEnd;
396 }
397
398 qreal range = qreal(modelCount);
399 // calc normalized position of highlight relative to offset
400 qreal relativeHighlight = std::fmod(x: pos + offset, y: range) / range;
401
402 if (!highlightUp && relativeHighlight > end / mappedRange) {
403 qreal diff = 1 - relativeHighlight;
404 setOffset(offset + diff * range);
405 } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) {
406 qreal diff = relativeHighlight - (end - start) / mappedRange;
407 setOffset(offset - diff * range - 0.00001);
408 }
409
410 highlightPosition = pos;
411 qreal pathPos = positionOfIndex(index: pos);
412 updateItem(highlightItem, pathPos);
413 if (QQuickPathViewAttached *att = attached(item: highlightItem))
414 att->setOnPath(pathPos < 1);
415 }
416}
417
418void QQuickPathView::pathUpdated()
419{
420 Q_D(QQuickPathView);
421 for (QQuickItem *item : qAsConst(t&: d->items)) {
422 if (QQuickPathViewAttached *att = d->attached(item))
423 att->m_percent = -1;
424 }
425 refill();
426}
427
428void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
429{
430 if (!path)
431 return;
432 if (QQuickPathViewAttached *att = attached(item)) {
433 if (qFuzzyCompare(p1: att->m_percent, p2: percent))
434 return;
435 att->m_percent = percent;
436 const auto attributes = path->attributes();
437 for (const QString &attr : attributes)
438 att->setValue(name: attr.toUtf8(), val: path->attributeAt(attr, percent));
439 att->setOnPath(percent < 1);
440 }
441 QQuickItemPrivate::get(item)->setCulled(percent >= 1);
442 QPointF pf = path->pointAtPercent(t: qMin(a: percent, b: qreal(1)));
443 item->setPosition(pf - QPointF(item->width()/2, item->height()/2));
444}
445
446void QQuickPathViewPrivate::regenerate()
447{
448 Q_Q(QQuickPathView);
449 if (!q->isComponentComplete())
450 return;
451
452 clear();
453
454 if (!isValid())
455 return;
456
457 updateMappedRange();
458 q->refill();
459}
460
461void QQuickPathViewPrivate::setDragging(bool d)
462{
463 Q_Q(QQuickPathView);
464 if (dragging == d)
465 return;
466
467 dragging = d;
468 if (dragging)
469 emit q->dragStarted();
470 else
471 emit q->dragEnded();
472
473 emit q->draggingChanged();
474}
475
476/*!
477 \qmltype PathView
478 \instantiates QQuickPathView
479 \inqmlmodule QtQuick
480 \ingroup qtquick-paths
481 \ingroup qtquick-views
482 \inherits Item
483 \brief Lays out model-provided items on a path.
484
485 A PathView displays data from models created from built-in QML types like ListModel
486 and XmlListModel, or custom model classes defined in C++ that inherit from
487 QAbstractListModel.
488
489 The view has a \l model, which defines the data to be displayed, and
490 a \l delegate, which defines how the data should be displayed.
491 The \l delegate is instantiated for each item on the \l path.
492 The items may be flicked to move them along the path.
493
494 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
495
496 \snippet qml/pathview/ContactModel.qml 0
497
498 This data can be represented as a PathView, like this:
499
500 \snippet qml/pathview/pathview.qml 0
501
502 \image pathview.gif
503
504 (Note the above example uses PathAttribute to scale and modify the
505 opacity of the items as they rotate. This additional code can be seen in the
506 PathAttribute documentation.)
507
508 PathView does not automatically handle keyboard navigation. This is because
509 the keys to use for navigation will depend upon the shape of the path. Navigation
510 can be added quite simply by setting \c focus to \c true and calling
511 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
512 using the left and right arrow keys:
513
514 \qml
515 PathView {
516 // ...
517 focus: true
518 Keys.onLeftPressed: decrementCurrentIndex()
519 Keys.onRightPressed: incrementCurrentIndex()
520 }
521 \endqml
522
523 The path view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
524
525 Delegates are instantiated as needed and may be destroyed at any time.
526 State should \e never be stored in a delegate.
527
528 PathView attaches a number of properties to the root item of the delegate, for example
529 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
530 this attached property directly as \c PathView.isCurrentItem, while the child
531 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
532
533 \snippet qml/pathview/pathview.qml 1
534
535 \b Note that views do not enable \e clip automatically. If the view
536 is not clipped by another item or the screen, it will be necessary
537 to set \e {clip: true} in order to have the out of view items clipped
538 nicely.
539
540 \sa Path, {QML Data Models}, ListView, GridView, {Qt Quick Examples - Views}
541*/
542
543QQuickPathView::QQuickPathView(QQuickItem *parent)
544 : QQuickItem(*(new QQuickPathViewPrivate), parent)
545{
546 Q_D(QQuickPathView);
547 d->init();
548}
549
550QQuickPathView::~QQuickPathView()
551{
552 Q_D(QQuickPathView);
553 d->clear();
554 if (d->attType)
555 d->attType->release();
556 if (d->ownModel)
557 delete d->model;
558}
559
560/*!
561 \qmlattachedproperty PathView QtQuick::PathView::view
562 This attached property holds the view that manages this delegate instance.
563
564 It is attached to each instance of the delegate.
565*/
566
567/*!
568 \qmlattachedproperty bool QtQuick::PathView::onPath
569 This attached property holds whether the item is currently on the path.
570
571 If a pathItemCount has been set, it is possible that some items may
572 be instantiated, but not considered to be currently on the path.
573 Usually, these items would be set invisible, for example:
574
575 \qml
576 Component {
577 Rectangle {
578 visible: PathView.onPath
579 // ...
580 }
581 }
582 \endqml
583
584 It is attached to each instance of the delegate.
585*/
586
587/*!
588 \qmlattachedproperty bool QtQuick::PathView::isCurrentItem
589 This attached property is true if this delegate is the current item; otherwise false.
590
591 It is attached to each instance of the delegate.
592
593 This property may be used to adjust the appearance of the current item.
594
595 \snippet qml/pathview/pathview.qml 1
596*/
597
598/*!
599 \qmlproperty model QtQuick::PathView::model
600 This property holds the model providing data for the view.
601
602 The model provides a set of data that is used to create the items for the view.
603 For large or dynamic datasets the model is usually provided by a C++ model object.
604 Models can also be created directly in QML, using the ListModel type.
605
606 \note changing the model will reset the offset and currentIndex to 0.
607
608 \sa {qml-data-models}{Data Models}
609*/
610QVariant QQuickPathView::model() const
611{
612 Q_D(const QQuickPathView);
613 return d->modelVariant;
614}
615
616void QQuickPathView::setModel(const QVariant &m)
617{
618 Q_D(QQuickPathView);
619 QVariant model = m;
620 if (model.userType() == qMetaTypeId<QJSValue>())
621 model = model.value<QJSValue>().toVariant();
622
623 if (d->modelVariant == model)
624 return;
625
626 if (d->model) {
627 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
628 this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
629 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
630 this, QQuickPathView, SLOT(createdItem(int,QObject*)));
631 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
632 this, QQuickPathView, SLOT(initItem(int,QObject*)));
633 d->clear();
634 }
635
636 d->modelVariant = model;
637 QObject *object = qvariant_cast<QObject*>(v: model);
638 QQmlInstanceModel *vim = nullptr;
639 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
640 if (d->ownModel) {
641 delete d->model;
642 d->ownModel = false;
643 }
644 d->model = vim;
645 } else {
646 if (!d->ownModel) {
647 d->model = new QQmlDelegateModel(qmlContext(this));
648 d->ownModel = true;
649 if (isComponentComplete())
650 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
651 }
652 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
653 dataModel->setModel(model);
654 }
655 int oldModelCount = d->modelCount;
656 d->modelCount = 0;
657 if (d->model) {
658 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
659 this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
660 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
661 this, QQuickPathView, SLOT(createdItem(int,QObject*)));
662 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
663 this, QQuickPathView, SLOT(initItem(int,QObject*)));
664 d->modelCount = d->model->count();
665 }
666 if (isComponentComplete()) {
667 if (d->currentIndex != 0) {
668 d->currentIndex = 0;
669 emit currentIndexChanged();
670 }
671 if (!(qFuzzyIsNull(d: d->offset))) {
672 d->offset = 0;
673 emit offsetChanged();
674 }
675 }
676 d->regenerate();
677 if (d->modelCount != oldModelCount)
678 emit countChanged();
679 emit modelChanged();
680}
681
682/*!
683 \qmlproperty int QtQuick::PathView::count
684 This property holds the number of items in the model.
685*/
686int QQuickPathView::count() const
687{
688 Q_D(const QQuickPathView);
689 return d->model ? d->modelCount : 0;
690}
691
692/*!
693 \qmlproperty Path QtQuick::PathView::path
694 This property holds the path used to lay out the items.
695 For more information see the \l Path documentation.
696*/
697QQuickPath *QQuickPathView::path() const
698{
699 Q_D(const QQuickPathView);
700 return d->path;
701}
702
703void QQuickPathView::setPath(QQuickPath *path)
704{
705 Q_D(QQuickPathView);
706 if (d->path == path)
707 return;
708 if (d->path)
709 qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
710 this, QQuickPathView, SLOT(pathUpdated()));
711 d->path = path;
712
713 if (path) {
714 qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
715 this, QQuickPathView, SLOT(pathUpdated()));
716 }
717
718 if (isComponentComplete()) {
719 d->clear();
720 if (d->isValid()) {
721 if (d->attType) {
722 d->attType->release();
723 d->attType = nullptr;
724 }
725 d->regenerate();
726 }
727 }
728
729 emit pathChanged();
730}
731
732/*!
733 \qmlproperty int QtQuick::PathView::currentIndex
734 This property holds the index of the current item.
735*/
736int QQuickPathView::currentIndex() const
737{
738 Q_D(const QQuickPathView);
739 return d->currentIndex;
740}
741
742void QQuickPathView::setCurrentIndex(int idx)
743{
744 Q_D(QQuickPathView);
745 if (!isComponentComplete()) {
746 if (idx != d->currentIndex) {
747 d->currentIndex = idx;
748 emit currentIndexChanged();
749 }
750 return;
751 }
752
753 idx = d->modelCount
754 ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
755 : 0;
756 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
757 if (d->currentItem) {
758 if (QQuickPathViewAttached *att = d->attached(item: d->currentItem))
759 att->setIsCurrentItem(false);
760 d->releaseItem(item: d->currentItem);
761 }
762 int oldCurrentIdx = d->currentIndex;
763 QQuickItem *oldCurrentItem = d->currentItem;
764 d->currentItem = nullptr;
765 d->moveReason = QQuickPathViewPrivate::SetIndex;
766 d->currentIndex = idx;
767 if (d->modelCount) {
768 d->createCurrentItem();
769 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
770 d->snapToIndex(index: d->currentIndex, reason: QQuickPathViewPrivate::SetIndex);
771 d->currentItemOffset = d->positionOfIndex(index: d->currentIndex);
772 d->updateHighlight();
773 }
774 if (oldCurrentIdx != d->currentIndex)
775 emit currentIndexChanged();
776 if (oldCurrentItem != d->currentItem)
777 emit currentItemChanged();
778 }
779}
780
781/*!
782 \qmlproperty Item QtQuick::PathView::currentItem
783 This property holds the current item in the view.
784*/
785QQuickItem *QQuickPathView::currentItem() const
786{
787 Q_D(const QQuickPathView);
788 return d->currentItem;
789}
790
791/*!
792 \qmlmethod QtQuick::PathView::incrementCurrentIndex()
793
794 Increments the current index.
795
796 \b Note: methods should only be called after the Component has completed.
797*/
798void QQuickPathView::incrementCurrentIndex()
799{
800 Q_D(QQuickPathView);
801 d->moveDirection = QQuickPathView::Positive;
802 setCurrentIndex(currentIndex()+1);
803}
804
805/*!
806 \qmlmethod QtQuick::PathView::decrementCurrentIndex()
807
808 Decrements the current index.
809
810 \b Note: methods should only be called after the Component has completed.
811*/
812void QQuickPathView::decrementCurrentIndex()
813{
814 Q_D(QQuickPathView);
815 d->moveDirection = QQuickPathView::Negative;
816 setCurrentIndex(currentIndex()-1);
817}
818
819/*!
820 \qmlproperty real QtQuick::PathView::offset
821
822 The offset specifies how far along the path the items are from their initial positions.
823 This is a real number that ranges from \c 0 to the count of items in the model.
824*/
825qreal QQuickPathView::offset() const
826{
827 Q_D(const QQuickPathView);
828 return d->offset;
829}
830
831void QQuickPathView::setOffset(qreal offset)
832{
833 Q_D(QQuickPathView);
834 d->moveReason = QQuickPathViewPrivate::Other;
835 d->setOffset(offset);
836 d->updateCurrent();
837}
838
839void QQuickPathViewPrivate::setOffset(qreal o)
840{
841 Q_Q(QQuickPathView);
842 if (!qFuzzyCompare(p1: offset, p2: o)) {
843 if (isValid() && q->isComponentComplete()) {
844 qreal oldOffset = offset;
845 offset = std::fmod(x: o, y: qreal(modelCount));
846 if (offset < 0)
847 offset += qreal(modelCount);
848 qCDebug(lcItemViewDelegateLifecycle) << o << "was" << oldOffset << "now" << offset;
849 q->refill();
850 } else {
851 offset = o;
852 }
853 emit q->offsetChanged();
854 }
855}
856
857void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
858{
859 setOffset(o+offsetAdj);
860}
861
862/*!
863 \qmlproperty Component QtQuick::PathView::highlight
864 This property holds the component to use as the highlight.
865
866 An instance of the highlight component will be created for each view.
867 The geometry of the resultant component instance will be managed by the view
868 so as to stay with the current item.
869
870 The below example demonstrates how to make a simple highlight. Note the use
871 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
872 the highlight is hidden when flicked away from the path.
873
874 \qml
875 Component {
876 Rectangle {
877 visible: PathView.onPath
878 // ...
879 }
880 }
881 \endqml
882
883 \sa highlightItem, highlightRangeMode
884*/
885QQmlComponent *QQuickPathView::highlight() const
886{
887 Q_D(const QQuickPathView);
888 return d->highlightComponent;
889}
890
891void QQuickPathView::setHighlight(QQmlComponent *highlight)
892{
893 Q_D(QQuickPathView);
894 if (highlight != d->highlightComponent) {
895 d->highlightComponent = highlight;
896 d->createHighlight();
897 d->updateHighlight();
898 emit highlightChanged();
899 }
900}
901
902/*!
903 \qmlproperty Item QtQuick::PathView::highlightItem
904
905 \c highlightItem holds the highlight item, which was created
906 from the \l highlight component.
907
908 \sa highlight
909*/
910QQuickItem *QQuickPathView::highlightItem() const
911{
912 Q_D(const QQuickPathView);
913 return d->highlightItem;
914}
915
916/*!
917 \qmlproperty real QtQuick::PathView::preferredHighlightBegin
918 \qmlproperty real QtQuick::PathView::preferredHighlightEnd
919 \qmlproperty enumeration QtQuick::PathView::highlightRangeMode
920
921 These properties set the preferred range of the highlight (current item)
922 within the view. The preferred values must be in the range from \c 0 to \c 1.
923
924 Valid values for \c highlightRangeMode are:
925
926 \list
927 \li \e PathView.NoHighlightRange - no range is applied and the
928 highlight will move freely within the view.
929 \li \e PathView.ApplyRange - the view will attempt to maintain
930 the highlight within the range, however the highlight can
931 move outside of the range at the ends of the path or due to
932 a mouse interaction.
933 \li \e PathView.StrictlyEnforceRange - the highlight will never
934 move outside of the range. This means that the current item
935 will change if a keyboard or mouse action would cause the
936 highlight to move outside of the range.
937 \endlist
938
939 The default value is \e PathView.StrictlyEnforceRange.
940
941 Defining a highlight range is the correct way to influence where the
942 current item ends up when the view moves. For example, if you want the
943 currently selected item to be in the middle of the path, then set the
944 highlight range to be 0.5,0.5 and highlightRangeMode to \e PathView.StrictlyEnforceRange.
945 Then, when the path scrolls,
946 the currently selected item will be the item at that position. This also applies to
947 when the currently selected item changes - it will scroll to within the preferred
948 highlight range. Furthermore, the behaviour of the current item index will occur
949 whether or not a highlight exists.
950
951 \note A valid range requires \c preferredHighlightEnd to be greater
952 than or equal to \c preferredHighlightBegin.
953*/
954qreal QQuickPathView::preferredHighlightBegin() const
955{
956 Q_D(const QQuickPathView);
957 return d->highlightRangeStart;
958}
959
960void QQuickPathView::setPreferredHighlightBegin(qreal start)
961{
962 Q_D(QQuickPathView);
963 if (qFuzzyCompare(p1: d->highlightRangeStart, p2: start) || start < 0 || start > 1)
964 return;
965 d->highlightRangeStart = start;
966 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
967 refill();
968 emit preferredHighlightBeginChanged();
969}
970
971qreal QQuickPathView::preferredHighlightEnd() const
972{
973 Q_D(const QQuickPathView);
974 return d->highlightRangeEnd;
975}
976
977void QQuickPathView::setPreferredHighlightEnd(qreal end)
978{
979 Q_D(QQuickPathView);
980 if (qFuzzyCompare(p1: d->highlightRangeEnd, p2: end) || end < 0 || end > 1)
981 return;
982 d->highlightRangeEnd = end;
983 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
984 refill();
985 emit preferredHighlightEndChanged();
986}
987
988QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
989{
990 Q_D(const QQuickPathView);
991 return d->highlightRangeMode;
992}
993
994void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
995{
996 Q_D(QQuickPathView);
997 if (d->highlightRangeMode == mode)
998 return;
999 d->highlightRangeMode = mode;
1000 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
1001 if (d->haveHighlightRange) {
1002 d->regenerate();
1003 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
1004 if (index >= 0)
1005 d->snapToIndex(index, reason: QQuickPathViewPrivate::Other);
1006 }
1007 emit highlightRangeModeChanged();
1008}
1009
1010/*!
1011 \qmlproperty int QtQuick::PathView::highlightMoveDuration
1012 This property holds the move animation duration of the highlight delegate.
1013
1014 If the highlightRangeMode is StrictlyEnforceRange then this property
1015 determines the speed that the items move along the path.
1016
1017 The default value for the duration is 300ms.
1018*/
1019int QQuickPathView::highlightMoveDuration() const
1020{
1021 Q_D(const QQuickPathView);
1022 return d->highlightMoveDuration;
1023}
1024
1025void QQuickPathView::setHighlightMoveDuration(int duration)
1026{
1027 Q_D(QQuickPathView);
1028 if (d->highlightMoveDuration == duration)
1029 return;
1030 d->highlightMoveDuration = duration;
1031 emit highlightMoveDurationChanged();
1032}
1033
1034/*!
1035 \qmlproperty real QtQuick::PathView::dragMargin
1036 This property holds the maximum distance from the path that initiates mouse dragging.
1037
1038 By default the path can only be dragged by clicking on an item. If
1039 dragMargin is greater than zero, a drag can be initiated by clicking
1040 within dragMargin pixels of the path.
1041*/
1042qreal QQuickPathView::dragMargin() const
1043{
1044 Q_D(const QQuickPathView);
1045 return d->dragMargin;
1046}
1047
1048void QQuickPathView::setDragMargin(qreal dragMargin)
1049{
1050 Q_D(QQuickPathView);
1051 if (qFuzzyCompare(p1: d->dragMargin, p2: dragMargin))
1052 return;
1053 d->dragMargin = dragMargin;
1054 emit dragMarginChanged();
1055}
1056
1057/*!
1058 \qmlproperty real QtQuick::PathView::flickDeceleration
1059 This property holds the rate at which a flick will decelerate.
1060
1061 The default is 100.
1062*/
1063qreal QQuickPathView::flickDeceleration() const
1064{
1065 Q_D(const QQuickPathView);
1066 return d->deceleration;
1067}
1068
1069void QQuickPathView::setFlickDeceleration(qreal dec)
1070{
1071 Q_D(QQuickPathView);
1072 if (qFuzzyCompare(p1: d->deceleration, p2: dec))
1073 return;
1074 d->deceleration = dec;
1075 emit flickDecelerationChanged();
1076}
1077
1078/*!
1079 \qmlproperty real QtQuick::PathView::maximumFlickVelocity
1080 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1081
1082 The default value is platform dependent.
1083*/
1084qreal QQuickPathView::maximumFlickVelocity() const
1085{
1086 Q_D(const QQuickPathView);
1087 return d->maximumFlickVelocity;
1088}
1089
1090void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1091{
1092 Q_D(QQuickPathView);
1093 if (qFuzzyCompare(p1: vel, p2: d->maximumFlickVelocity))
1094 return;
1095 d->maximumFlickVelocity = vel;
1096 emit maximumFlickVelocityChanged();
1097}
1098
1099
1100/*!
1101 \qmlproperty bool QtQuick::PathView::interactive
1102
1103 A user cannot drag or flick a PathView that is not interactive.
1104
1105 This property is useful for temporarily disabling flicking. This allows
1106 special interaction with PathView's children.
1107*/
1108bool QQuickPathView::isInteractive() const
1109{
1110 Q_D(const QQuickPathView);
1111 return d->interactive;
1112}
1113
1114void QQuickPathView::setInteractive(bool interactive)
1115{
1116 Q_D(QQuickPathView);
1117 if (interactive != d->interactive) {
1118 d->interactive = interactive;
1119 if (!interactive)
1120 d->tl.clear();
1121 emit interactiveChanged();
1122 }
1123}
1124
1125/*!
1126 \qmlproperty bool QtQuick::PathView::moving
1127
1128 This property holds whether the view is currently moving
1129 due to the user either dragging or flicking the view.
1130*/
1131bool QQuickPathView::isMoving() const
1132{
1133 Q_D(const QQuickPathView);
1134 return d->moving;
1135}
1136
1137/*!
1138 \qmlproperty bool QtQuick::PathView::flicking
1139
1140 This property holds whether the view is currently moving
1141 due to the user flicking the view.
1142*/
1143bool QQuickPathView::isFlicking() const
1144{
1145 Q_D(const QQuickPathView);
1146 return d->flicking;
1147}
1148
1149/*!
1150 \qmlproperty bool QtQuick::PathView::dragging
1151
1152 This property holds whether the view is currently moving
1153 due to the user dragging the view.
1154*/
1155bool QQuickPathView::isDragging() const
1156{
1157 Q_D(const QQuickPathView);
1158 return d->dragging;
1159}
1160
1161/*!
1162 \qmlsignal QtQuick::PathView::movementStarted()
1163
1164 This signal is emitted when the view begins moving due to user
1165 interaction.
1166*/
1167
1168/*!
1169 \qmlsignal QtQuick::PathView::movementEnded()
1170
1171 This signal is emitted when the view stops moving due to user
1172 interaction. If a flick was generated, this signal will
1173 be emitted once the flick stops. If a flick was not
1174 generated, this signal will be emitted when the
1175 user stops dragging - i.e. a mouse or touch release.
1176*/
1177
1178/*!
1179 \qmlsignal QtQuick::PathView::flickStarted()
1180
1181 This signal is emitted when the view is flicked. A flick
1182 starts from the point that the mouse or touch is released,
1183 while still in motion.
1184*/
1185
1186/*!
1187 \qmlsignal QtQuick::PathView::flickEnded()
1188
1189 This signal is emitted when the view stops moving due to a flick.
1190*/
1191
1192/*!
1193 \qmlsignal QtQuick::PathView::dragStarted()
1194
1195 This signal is emitted when the view starts to be dragged due to user
1196 interaction.
1197*/
1198
1199/*!
1200 \qmlsignal QtQuick::PathView::dragEnded()
1201
1202 This signal is emitted when the user stops dragging the view.
1203
1204 If the velocity of the drag is suffient at the time the
1205 touch/mouse button is released then a flick will start.
1206*/
1207
1208/*!
1209 \qmlproperty Component QtQuick::PathView::delegate
1210
1211 The delegate provides a template defining each item instantiated by the view.
1212 The index is exposed as an accessible \c index property. Properties of the
1213 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
1214
1215 The number of objects and bindings in the delegate has a direct effect on the
1216 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
1217 that is not needed for the normal display of the delegate in a \l Loader which
1218 can load additional components when needed.
1219
1220 Note that the PathView will layout the items based on the size of the root
1221 item in the delegate.
1222
1223 Here is an example delegate:
1224 \snippet qml/pathview/pathview.qml 1
1225*/
1226QQmlComponent *QQuickPathView::delegate() const
1227{
1228 Q_D(const QQuickPathView);
1229 if (d->model) {
1230 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
1231 return dataModel->delegate();
1232 }
1233
1234 return nullptr;
1235}
1236
1237void QQuickPathView::setDelegate(QQmlComponent *delegate)
1238{
1239 Q_D(QQuickPathView);
1240 if (delegate == this->delegate())
1241 return;
1242 if (!d->ownModel) {
1243 d->model = new QQmlDelegateModel(qmlContext(this));
1244 d->ownModel = true;
1245 if (isComponentComplete())
1246 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1247 }
1248 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model)) {
1249 int oldCount = dataModel->count();
1250 dataModel->setDelegate(delegate);
1251 d->modelCount = dataModel->count();
1252 d->regenerate();
1253 if (oldCount != dataModel->count())
1254 emit countChanged();
1255 emit delegateChanged();
1256 d->delegateValidated = false;
1257 }
1258}
1259
1260/*!
1261 \qmlproperty int QtQuick::PathView::pathItemCount
1262 This property holds the number of items visible on the path at any one time.
1263
1264 Setting pathItemCount to undefined will show all items on the path.
1265*/
1266int QQuickPathView::pathItemCount() const
1267{
1268 Q_D(const QQuickPathView);
1269 return d->pathItems;
1270}
1271
1272void QQuickPathView::setPathItemCount(int i)
1273{
1274 Q_D(QQuickPathView);
1275 if (i == d->pathItems)
1276 return;
1277 if (i < 1)
1278 i = 1;
1279 d->pathItems = i;
1280 d->updateMappedRange();
1281 if (d->isValid() && isComponentComplete()) {
1282 d->regenerate();
1283 }
1284 emit pathItemCountChanged();
1285}
1286
1287void QQuickPathView::resetPathItemCount()
1288{
1289 Q_D(QQuickPathView);
1290 if (-1 == d->pathItems)
1291 return;
1292 d->pathItems = -1;
1293 d->updateMappedRange();
1294 if (d->isValid() && isComponentComplete())
1295 d->regenerate();
1296 emit pathItemCountChanged();
1297}
1298
1299/*!
1300 \qmlproperty int QtQuick::PathView::cacheItemCount
1301 This property holds the maximum number of items to cache off the path.
1302
1303 For example, a PathView with a model containing 20 items, a pathItemCount
1304 of 10, and an cacheItemCount of 4 will create up to 14 items, with 10 visible
1305 on the path and 4 invisible cached items.
1306
1307 The cached delegates are created asynchronously,
1308 allowing creation to occur across multiple frames and reducing the
1309 likelihood of skipping frames.
1310
1311 \note Setting this property is not a replacement for creating efficient delegates.
1312 It can improve the smoothness of scrolling behavior at the expense of additional
1313 memory usage. The fewer objects and bindings in a delegate, the faster a
1314 view can be scrolled. It is important to realize that setting cacheItemCount
1315 will only postpone issues caused by slow-loading delegates, it is not a
1316 solution for this scenario.
1317
1318 \sa pathItemCount
1319*/
1320int QQuickPathView::cacheItemCount() const
1321{
1322 Q_D(const QQuickPathView);
1323 return d->cacheSize;
1324}
1325
1326void QQuickPathView::setCacheItemCount(int i)
1327{
1328 Q_D(QQuickPathView);
1329 if (i == d->cacheSize || i < 0)
1330 return;
1331
1332 d->cacheSize = i;
1333 d->updateMappedRange();
1334 refill();
1335 emit cacheItemCountChanged();
1336}
1337
1338/*!
1339 \qmlproperty enumeration QtQuick::PathView::snapMode
1340
1341 This property determines how the items will settle following a drag or flick.
1342 The possible values are:
1343
1344 \list
1345 \li PathView.NoSnap (default) - the items stop anywhere along the path.
1346 \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin.
1347 \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest
1348 \l preferredHighlightBegin at the time the press is released. This mode is particularly
1349 useful for moving one page at a time.
1350 \endlist
1351
1352 \c snapMode does not affect the \l currentIndex. To update the
1353 \l currentIndex as the view is moved, set \l highlightRangeMode
1354 to \c PathView.StrictlyEnforceRange (default for PathView).
1355
1356 \sa highlightRangeMode
1357*/
1358QQuickPathView::SnapMode QQuickPathView::snapMode() const
1359{
1360 Q_D(const QQuickPathView);
1361 return d->snapMode;
1362}
1363
1364void QQuickPathView::setSnapMode(SnapMode mode)
1365{
1366 Q_D(QQuickPathView);
1367 if (mode == d->snapMode)
1368 return;
1369 d->snapMode = mode;
1370 emit snapModeChanged();
1371}
1372
1373/*!
1374 \qmlproperty enumeration QtQuick::PathView::movementDirection
1375 \since 5.7
1376
1377 This property determines the direction in which items move when setting the current index.
1378 The possible values are:
1379
1380 \list
1381 \li PathView.Shortest (default) - the items move in the direction that requires the least
1382 movement, which could be either \c Negative or \c Positive.
1383 \li PathView.Negative - the items move backwards towards their destination.
1384 \li PathView.Positive - the items move forwards towards their destination.
1385 \endlist
1386
1387 For example, suppose that there are 5 items in the model, and \l currentIndex is \c 0.
1388 If currentIndex is set to \c 2,
1389
1390 \list
1391 \li a \c Positive movement direction will result in the following order: 0, 1, 2
1392 \li a \c Negative movement direction will result in the following order: 0, 5, 4, 3, 2
1393 \li a \c Shortest movement direction will result in same order with \c Positive .
1394 \endlist
1395
1396 \note this property doesn't affect the movement of \l incrementCurrentIndex() and \l decrementCurrentIndex().
1397*/
1398QQuickPathView::MovementDirection QQuickPathView::movementDirection() const
1399{
1400 Q_D(const QQuickPathView);
1401 return d->movementDirection;
1402}
1403
1404void QQuickPathView::setMovementDirection(QQuickPathView::MovementDirection dir)
1405{
1406 Q_D(QQuickPathView);
1407 if (dir == d->movementDirection)
1408 return;
1409 d->movementDirection = dir;
1410 if (!d->tl.isActive())
1411 d->moveDirection = d->movementDirection;
1412 emit movementDirectionChanged();
1413}
1414
1415/*!
1416 \qmlmethod QtQuick::PathView::positionViewAtIndex(int index, PositionMode mode)
1417
1418 Positions the view such that the \a index is at the position specified by
1419 \a mode:
1420
1421 \list
1422 \li PathView.Beginning - position item at the beginning of the path.
1423 \li PathView.Center - position item in the center of the path.
1424 \li PathView.End - position item at the end of the path.
1425 \li PathView.Contain - ensure the item is positioned on the path.
1426 \li PathView.SnapPosition - position the item at \l preferredHighlightBegin. This mode
1427 is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
1428 via \l snapMode.
1429 \endlist
1430
1431 \b Note: methods should only be called after the Component has completed. To position
1432 the view at startup, this method should be called by Component.onCompleted. For
1433 example, to position the view at the end:
1434
1435 \code
1436 Component.onCompleted: positionViewAtIndex(count - 1, PathView.End)
1437 \endcode
1438*/
1439void QQuickPathView::positionViewAtIndex(int index, int mode)
1440{
1441 Q_D(QQuickPathView);
1442 if (!d->isValid())
1443 return;
1444 if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView
1445 return;
1446
1447 if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems))
1448 return;
1449
1450 int count = d->pathItems == -1 ? d->modelCount : qMin(a: d->pathItems, b: d->modelCount);
1451 int idx = (index+d->modelCount) % d->modelCount;
1452 bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1453 || d->snapMode != QQuickPathView::NoSnap);
1454
1455 qreal beginOffset;
1456 qreal endOffset;
1457 if (snap) {
1458 beginOffset = d->modelCount - idx - qFloor(v: count * d->highlightRangeStart);
1459 endOffset = beginOffset + count - 1;
1460 } else {
1461 beginOffset = d->modelCount - idx;
1462 // Small offset since the last point coincides with the first and
1463 // this the only "end" position that gives the expected visual result.
1464 qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001;
1465 endOffset = std::fmod(x: beginOffset + count, y: qreal(d->modelCount)) - adj;
1466 }
1467 qreal offset = d->offset;
1468 switch (mode) {
1469 case Beginning:
1470 offset = beginOffset;
1471 break;
1472 case End:
1473 offset = endOffset;
1474 break;
1475 case Center:
1476 if (beginOffset < endOffset)
1477 offset = (beginOffset + endOffset)/2;
1478 else
1479 offset = (beginOffset + (endOffset + d->modelCount))/2;
1480 if (snap)
1481 offset = qRound(d: offset);
1482 break;
1483 case Contain:
1484 if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset))
1485 || (d->offset < beginOffset && d->offset > endOffset)) {
1486 qreal diff1 = std::fmod(x: beginOffset - d->offset + d->modelCount, y: qreal(d->modelCount));
1487 qreal diff2 = std::fmod(x: d->offset - endOffset + d->modelCount, y: qreal(d->modelCount));
1488 if (diff1 < diff2)
1489 offset = beginOffset;
1490 else
1491 offset = endOffset;
1492 }
1493 break;
1494 case SnapPosition:
1495 offset = d->modelCount - idx;
1496 break;
1497 }
1498
1499 d->tl.clear();
1500 setOffset(offset);
1501}
1502
1503/*!
1504 \qmlmethod int QtQuick::PathView::indexAt(real x, real y)
1505
1506 Returns the index of the item containing the point \a x, \a y in content
1507 coordinates. If there is no item at the point specified, -1 is returned.
1508
1509 \b Note: methods should only be called after the Component has completed.
1510*/
1511int QQuickPathView::indexAt(qreal x, qreal y) const
1512{
1513 Q_D(const QQuickPathView);
1514 QQuickItem *item = itemAt(x, y);
1515 return item ? d->model->indexOf(object: item, objectContext: nullptr) : -1;
1516}
1517
1518/*!
1519 \qmlmethod Item QtQuick::PathView::itemAt(real x, real y)
1520
1521 Returns the item containing the point \a x, \a y in content
1522 coordinates. If there is no item at the point specified, null is returned.
1523
1524 \b Note: methods should only be called after the Component has completed.
1525*/
1526QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const
1527{
1528 Q_D(const QQuickPathView);
1529 if (!d->isValid())
1530 return nullptr;
1531
1532 for (QQuickItem *item : d->items) {
1533 QPointF p = item->mapFromItem(item: this, point: QPointF(x, y));
1534 if (item->contains(point: p))
1535 return item;
1536 }
1537
1538 return nullptr;
1539}
1540
1541/*!
1542 \qmlmethod Item QtQuick::QQuickPathView::itemAtIndex(int index)
1543
1544 Returns the item for \a index. If there is no item for that index, for example
1545 because it has not been created yet, or because it has been panned out of
1546 the visible area and removed from the cache, null is returned.
1547
1548 \b Note: this method should only be called after the Component has completed.
1549 The returned value should also not be stored since it can turn to null
1550 as soon as control goes out of the calling scope, if the view releases that item.
1551
1552 \since 5.13
1553*/
1554QQuickItem *QQuickPathView::itemAtIndex(int index) const
1555{
1556 Q_D(const QQuickPathView);
1557 if (!d->isValid())
1558 return nullptr;
1559
1560 for (QQuickItem *item : d->items) {
1561 if (index == d->model->indexOf(object: item, objectContext: nullptr))
1562 return item;
1563 }
1564
1565 return nullptr;
1566}
1567
1568QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1569{
1570 const auto pathLength = path->path().length();
1571 qreal samples = qMin(a: pathLength / 5, b: qreal(500));
1572 qreal res = pathLength / samples;
1573
1574 qreal mindist = 1e10; // big number
1575 QPointF nearPoint = path->pointAtPercent(t: 0);
1576 qreal nearPc = 0;
1577
1578 // get rough pos
1579 for (qreal i=1; i < samples; i++) {
1580 QPointF pt = path->pointAtPercent(t: i/samples);
1581 QPointF diff = pt - point;
1582 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1583 if (dist < mindist) {
1584 nearPoint = pt;
1585 nearPc = i;
1586 mindist = dist;
1587 }
1588 }
1589
1590 // now refine
1591 qreal approxPc = nearPc;
1592 for (qreal i = approxPc-1; i < approxPc+1; i += 1/(2*res)) {
1593 QPointF pt = path->pointAtPercent(t: i/samples);
1594 QPointF diff = pt - point;
1595 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1596 if (dist < mindist) {
1597 nearPoint = pt;
1598 nearPc = i;
1599 mindist = dist;
1600 }
1601 }
1602
1603 if (nearPercent)
1604 *nearPercent = nearPc / samples;
1605
1606 return nearPoint;
1607}
1608
1609void QQuickPathViewPrivate::addVelocitySample(qreal v)
1610{
1611 velocityBuffer.append(v);
1612 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1613 velocityBuffer.remove(idx: 0);
1614 qCDebug(lcPathView) << "instantaneous velocity" << v;
1615}
1616
1617qreal QQuickPathViewPrivate::calcVelocity() const
1618{
1619 qreal velocity = 0;
1620 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1621 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1622 for (int i = 0; i < count; ++i) {
1623 qreal v = velocityBuffer.at(idx: i);
1624 velocity += v;
1625 }
1626 velocity /= count;
1627 qCDebug(lcPathView) << "average velocity" << velocity << "based on" << count << "samples";
1628 }
1629 return velocity;
1630}
1631
1632qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event) const
1633{
1634 if (0 != event->timestamp())
1635 return qint64(event->timestamp());
1636 return timer.elapsed();
1637}
1638
1639void QQuickPathView::mousePressEvent(QMouseEvent *event)
1640{
1641 Q_D(QQuickPathView);
1642 if (d->interactive) {
1643 d->handleMousePressEvent(event);
1644 event->accept();
1645 } else {
1646 QQuickItem::mousePressEvent(event);
1647 }
1648}
1649
1650void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
1651{
1652 Q_Q(QQuickPathView);
1653 if (!interactive || !items.count() || !model || !modelCount)
1654 return;
1655 velocityBuffer.clear();
1656 int idx = 0;
1657 for (; idx < items.count(); ++idx) {
1658 QQuickItem *item = items.at(i: idx);
1659 if (item->contains(point: item->mapFromScene(point: event->windowPos())))
1660 break;
1661 }
1662 if (idx == items.count() && qFuzzyIsNull(d: dragMargin)) // didn't click on an item
1663 return;
1664
1665 startPoint = pointNear(point: event->localPos(), nearPercent: &startPc);
1666 startPos = event->localPos();
1667 if (idx == items.count()) {
1668 qreal distance = qAbs(t: event->localPos().x() - startPoint.x()) + qAbs(t: event->localPos().y() - startPoint.y());
1669 if (distance > dragMargin)
1670 return;
1671 }
1672
1673 if (tl.isActive() && flicking && flickDuration && qreal(tl.time()) / flickDuration < 0.8) {
1674 stealMouse = true; // If we've been flicked then steal the click.
1675 q->grabMouse(); // grab it right now too, just to be sure (QTBUG-77173)
1676 } else {
1677 stealMouse = false;
1678 }
1679 q->setKeepMouseGrab(stealMouse);
1680
1681 timer.start();
1682 lastPosTime = computeCurrentTime(event);
1683 tl.clear();
1684}
1685
1686void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1687{
1688 Q_D(QQuickPathView);
1689 if (d->interactive) {
1690 d->handleMouseMoveEvent(event);
1691 event->accept();
1692 } else {
1693 QQuickItem::mouseMoveEvent(event);
1694 }
1695}
1696
1697void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
1698{
1699 Q_Q(QQuickPathView);
1700 if (!interactive || !timer.isValid() || !model || !modelCount)
1701 return;
1702
1703 qint64 currentTimestamp = computeCurrentTime(event);
1704 qreal newPc;
1705 QPointF pathPoint = pointNear(point: event->localPos(), nearPercent: &newPc);
1706 if (!stealMouse) {
1707 QPointF posDelta = event->localPos() - startPos;
1708 if (QQuickWindowPrivate::dragOverThreshold(d: posDelta.y(), axis: Qt::YAxis, event) || QQuickWindowPrivate::dragOverThreshold(d: posDelta.x(), axis: Qt::XAxis, event)) {
1709 // The touch has exceeded the threshold. If the movement along the path is close to the drag threshold
1710 // then we'll assume that this gesture targets the PathView. This ensures PathView gesture grabbing
1711 // is in sync with other items.
1712 QPointF pathDelta = pathPoint - startPoint;
1713 const int startDragDistance = QGuiApplication::styleHints()->startDragDistance();
1714 if (qAbs(t: pathDelta.x()) > startDragDistance * 0.8
1715 || qAbs(t: pathDelta.y()) > startDragDistance * 0.8) {
1716 stealMouse = true;
1717 q->setKeepMouseGrab(true);
1718 }
1719 }
1720 } else {
1721 moveReason = QQuickPathViewPrivate::Mouse;
1722 int count = pathItems == -1 ? modelCount : qMin(a: pathItems, b: modelCount);
1723 qreal diff = (newPc - startPc)*count;
1724 if (!qFuzzyIsNull(d: diff)) {
1725 q->setOffset(offset + diff);
1726
1727 if (diff > modelCount/2)
1728 diff -= modelCount;
1729 else if (diff < -modelCount/2)
1730 diff += modelCount;
1731
1732 qint64 elapsed = currentTimestamp - lastPosTime;
1733 if (elapsed > 0)
1734 addVelocitySample(v: diff / (qreal(elapsed) / 1000));
1735 }
1736 if (!moving) {
1737 moving = true;
1738 emit q->movingChanged();
1739 emit q->movementStarted();
1740 }
1741 setDragging(true);
1742 }
1743 startPc = newPc;
1744 lastPosTime = currentTimestamp;
1745}
1746
1747void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1748{
1749 Q_D(QQuickPathView);
1750 if (d->interactive) {
1751 d->handleMouseReleaseEvent(event);
1752 event->accept();
1753 ungrabMouse();
1754 } else {
1755 QQuickItem::mouseReleaseEvent(event);
1756 }
1757}
1758
1759void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *event)
1760{
1761 Q_Q(QQuickPathView);
1762 stealMouse = false;
1763 q->setKeepMouseGrab(false);
1764 setDragging(false);
1765 if (!interactive || !timer.isValid() || !model || !modelCount) {
1766 timer.invalidate();
1767 if (!tl.isActive())
1768 q->movementEnding();
1769 return;
1770 }
1771
1772 qreal velocity = calcVelocity();
1773 qint64 elapsed = computeCurrentTime(event) - lastPosTime;
1774 // Let the velocity linearly decay such that it becomes 0 if elapsed time > QML_FLICK_VELOCITY_DECAY_TIME
1775 // The intention is that if you are flicking at some speed, then stop in one place for some time before releasing,
1776 // the previous velocity is lost. (QTBUG-77173, QTBUG-59052)
1777 velocity *= qreal(qMax(a: 0LL, QML_FLICK_VELOCITY_DECAY_TIME - elapsed)) / QML_FLICK_VELOCITY_DECAY_TIME;
1778 qCDebug(lcPathView) << "after elapsed time" << elapsed << "velocity decayed to" << velocity;
1779 qreal count = pathItems == -1 ? modelCount : qMin(a: pathItems, b: modelCount);
1780 const auto averageItemLength = path->path().length() / count;
1781 qreal pixelVelocity = averageItemLength * velocity;
1782 if (qAbs(t: pixelVelocity) > MinimumFlickVelocity) {
1783 if (qAbs(t: pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
1784 // limit velocity
1785 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1786 velocity = maxVel / averageItemLength;
1787 }
1788 // Calculate the distance to be travelled
1789 qreal v2 = velocity*velocity;
1790 qreal accel = deceleration/10;
1791 qreal dist = 0;
1792 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1793 || snapMode != QQuickPathView::NoSnap)) {
1794 if (snapMode == QQuickPathView::SnapOneItem) {
1795 // encourage snapping one item in direction of motion
1796 if (velocity > 0)
1797 dist = qRound(d: 0.5 + offset) - offset;
1798 else
1799 dist = qRound(d: 0.5 - offset) + offset;
1800 } else {
1801 // + 0.25 to encourage moving at least one item in the flick direction
1802 dist = qMin(a: qreal(modelCount-1), b: qreal(v2 / (accel * 2) + 0.25));
1803
1804 // round to nearest item.
1805 if (velocity > 0)
1806 dist = qRound(d: dist + offset) - offset;
1807 else
1808 dist = qRound(d: dist - offset) + offset;
1809 }
1810 // Calculate accel required to stop on item boundary
1811 if (dist <= 0) {
1812 dist = 0;
1813 accel = 0;
1814 } else {
1815 accel = v2 / (2 * qAbs(t: dist));
1816 }
1817 } else {
1818 dist = qMin(a: qreal(modelCount-1), b: qreal(v2 / (accel * 2)));
1819 }
1820 flickDuration = int(1000 * qAbs(t: velocity) / accel);
1821 offsetAdj = 0;
1822 moveOffset.setValue(offset);
1823 tl.accel(moveOffset, velocity, accel, maxDistance: dist);
1824 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1825 if (!flicking) {
1826 flicking = true;
1827 emit q->flickingChanged();
1828 emit q->flickStarted();
1829 }
1830 } else {
1831 fixOffset();
1832 }
1833
1834 timer.invalidate();
1835 if (!tl.isActive())
1836 q->movementEnding();
1837}
1838
1839bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
1840{
1841 Q_D(QQuickPathView);
1842 QPointF localPos = mapFromScene(point: event->windowPos());
1843
1844 QQuickWindow *c = window();
1845 QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr;
1846 if (grabber == this && d->stealMouse) {
1847 // we are already the grabber and we do want the mouse event to ourselves.
1848 return true;
1849 }
1850
1851 bool grabberDisabled = grabber && !grabber->isEnabled();
1852 bool stealThisEvent = d->stealMouse;
1853 if ((stealThisEvent || contains(point: localPos)) && (!grabber || !grabber->keepMouseGrab() || grabberDisabled)) {
1854 QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, transformedLocalPos: &localPos));
1855 mouseEvent->setAccepted(false);
1856
1857 switch (mouseEvent->type()) {
1858 case QEvent::MouseMove:
1859 d->handleMouseMoveEvent(event: mouseEvent.data());
1860 break;
1861 case QEvent::MouseButtonPress:
1862 d->handleMousePressEvent(event: mouseEvent.data());
1863 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1864 break;
1865 case QEvent::MouseButtonRelease:
1866 d->handleMouseReleaseEvent(event: mouseEvent.data());
1867 break;
1868 default:
1869 break;
1870 }
1871 grabber = c ? c->mouseGrabberItem() : nullptr;
1872 if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || grabberDisabled) {
1873 grabMouse();
1874 }
1875
1876 const bool filtered = stealThisEvent || grabberDisabled;
1877 if (filtered) {
1878 event->setAccepted(false);
1879 }
1880 return filtered;
1881 } else if (d->timer.isValid()) {
1882 d->timer.invalidate();
1883 d->fixOffset();
1884 }
1885 if (event->type() == QEvent::MouseButtonRelease || (grabber && grabber->keepMouseGrab() && !grabberDisabled)) {
1886 d->stealMouse = false;
1887 }
1888 return false;
1889}
1890
1891bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1892{
1893 Q_D(QQuickPathView);
1894 if (!isVisible() || !d->interactive)
1895 return QQuickItem::childMouseEventFilter(i, e);
1896
1897 switch (e->type()) {
1898 case QEvent::MouseButtonPress:
1899 case QEvent::MouseMove:
1900 case QEvent::MouseButtonRelease:
1901 return sendMouseEvent(event: static_cast<QMouseEvent *>(e));
1902 default:
1903 break;
1904 }
1905
1906 return QQuickItem::childMouseEventFilter(i, e);
1907}
1908
1909void QQuickPathView::mouseUngrabEvent()
1910{
1911 Q_D(QQuickPathView);
1912 if (d->stealMouse ||
1913 (!d->flicking && d->snapMode != NoSnap && !qFuzzyCompare(p1: qRound(d: d->offset), p2: d->offset))) {
1914 // if our mouse grab has been removed (probably by a Flickable),
1915 // or if we should snap but haven't done it, fix our state
1916 d->stealMouse = false;
1917 setKeepMouseGrab(false);
1918 d->timer.invalidate();
1919 d->fixOffset();
1920 d->setDragging(false);
1921 if (!d->tl.isActive())
1922 movementEnding();
1923 }
1924}
1925
1926void QQuickPathView::updatePolish()
1927{
1928 QQuickItem::updatePolish();
1929 refill();
1930}
1931
1932static inline int currentIndexRemainder(int currentIndex, int modelCount) Q_DECL_NOTHROW
1933{
1934 if (currentIndex < 0)
1935 return modelCount + currentIndex % modelCount;
1936 else
1937 return currentIndex % modelCount;
1938}
1939
1940void QQuickPathView::componentComplete()
1941{
1942 Q_D(QQuickPathView);
1943 if (d->model && d->ownModel)
1944 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1945
1946 QQuickItem::componentComplete();
1947
1948 if (d->model) {
1949 d->modelCount = d->model->count();
1950 if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex
1951 d->offset = std::fmod(x: qreal(d->modelCount - currentIndexRemainder(currentIndex: d->currentIndex, modelCount: d->modelCount)), y: qreal(d->modelCount));
1952 }
1953
1954 d->createHighlight();
1955 d->regenerate();
1956 d->updateHighlight();
1957 d->updateCurrent();
1958
1959 if (d->modelCount)
1960 emit countChanged();
1961}
1962
1963void QQuickPathView::refill()
1964{
1965 Q_D(QQuickPathView);
1966
1967 if (d->inRefill) {
1968 d->scheduleLayout();
1969 return;
1970 }
1971
1972 d->layoutScheduled = false;
1973
1974 if (!d->isValid() || !isComponentComplete())
1975 return;
1976
1977 d->inRefill = true;
1978
1979 bool currentVisible = false;
1980 int count = d->pathItems == -1 ? d->modelCount : qMin(a: d->pathItems, b: d->modelCount);
1981
1982 // first move existing items and remove items off path
1983 qCDebug(lcItemViewDelegateLifecycle) << "currentIndex" << d->currentIndex << "offset" << d->offset;
1984 QList<QQuickItem*>::iterator it = d->items.begin();
1985 while (it != d->items.end()) {
1986 QQuickItem *item = *it;
1987 int idx = d->model->indexOf(object: item, objectContext: nullptr);
1988 qreal pos = d->positionOfIndex(index: idx);
1989 if (lcItemViewDelegateLifecycle().isDebugEnabled()) {
1990 QQuickText *text = qmlobject_cast<QQuickText*>(object: item);
1991 if (text)
1992 qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ": QQuickText" << text->objectName() << text->text().leftRef(n: 40);
1993 else
1994 qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ":" << item;
1995 }
1996 if (pos < 1) {
1997 d->updateItem(item, percent: pos);
1998 if (idx == d->currentIndex) {
1999 currentVisible = true;
2000 d->currentItemOffset = pos;
2001 }
2002 ++it;
2003 } else {
2004 d->updateItem(item, percent: pos);
2005 if (QQuickPathViewAttached *att = d->attached(item))
2006 att->setOnPath(pos < 1);
2007 if (!d->isInBound(position: pos, lower: d->mappedRange - d->mappedCache, upper: 1 + d->mappedCache)) {
2008 qCDebug(lcItemViewDelegateLifecycle) << "release" << idx << "@" << pos << ", !isInBound: lower" << (d->mappedRange - d->mappedCache) << "upper" << (1 + d->mappedCache);
2009 d->releaseItem(item);
2010 it = d->items.erase(pos: it);
2011 } else {
2012 ++it;
2013 }
2014 }
2015 }
2016
2017 bool waiting = false;
2018 if (d->modelCount) {
2019 // add items as needed
2020 if (d->items.count() < count+d->cacheSize) {
2021 int endIdx = 0;
2022 qreal endPos;
2023 int startIdx = 0;
2024 qreal startPos = 0;
2025 const bool wasEmpty = d->items.isEmpty();
2026 if (!wasEmpty) {
2027 //Find the beginning and end, items may not be in sorted order
2028 endPos = -1;
2029 startPos = 2;
2030
2031 for (QQuickItem * item : qAsConst(t&: d->items)) {
2032 int idx = d->model->indexOf(object: item, objectContext: nullptr);
2033 qreal curPos = d->positionOfIndex(index: idx);
2034 if (curPos > endPos) {
2035 endPos = curPos;
2036 endIdx = idx;
2037 }
2038
2039 if (curPos < startPos) {
2040 startPos = curPos;
2041 startIdx = idx;
2042 }
2043 }
2044 } else {
2045 if (d->haveHighlightRange
2046 && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
2047 || d->snapMode != QQuickPathView::NoSnap))
2048 startPos = d->highlightRangeStart;
2049 // With no items, then "end" is just off the top so we populate via append
2050 endIdx = (qRound(d: d->modelCount - d->offset) - 1) % d->modelCount;
2051 endPos = d->positionOfIndex(index: endIdx);
2052 }
2053 //Append
2054 int idx = endIdx + 1;
2055 if (idx >= d->modelCount)
2056 idx = 0;
2057 qreal nextPos = d->positionOfIndex(index: idx);
2058 while ((d->isInBound(position: nextPos, lower: endPos, upper: 1 + d->mappedCache) || !d->items.count())
2059 && d->items.count() < count+d->cacheSize) {
2060 qCDebug(lcItemViewDelegateLifecycle) << "append" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.count();
2061 QQuickItem *item = d->getItem(modelIndex: idx, z: idx+1, async: nextPos >= 1);
2062 if (!item) {
2063 waiting = true;
2064 break;
2065 }
2066 if (d->items.contains(t: item)) {
2067 d->releaseItem(item);
2068 break; //Otherwise we'd "re-add" it, and get confused
2069 }
2070 if (d->currentIndex == idx) {
2071 currentVisible = true;
2072 d->currentItemOffset = nextPos;
2073 }
2074 d->items.append(t: item);
2075 d->updateItem(item, percent: nextPos);
2076 endIdx = idx;
2077 endPos = nextPos;
2078 ++idx;
2079 if (idx >= d->modelCount)
2080 idx = 0;
2081 nextPos = d->positionOfIndex(index: idx);
2082 }
2083
2084 //Prepend
2085 idx = (wasEmpty ? d->calcCurrentIndex() : startIdx) - 1;
2086
2087 if (idx < 0)
2088 idx = d->modelCount - 1;
2089 nextPos = d->positionOfIndex(index: idx);
2090 while (!waiting && d->isInBound(position: nextPos, lower: d->mappedRange - d->mappedCache, upper: startPos)
2091 && d->items.count() < count+d->cacheSize) {
2092 qCDebug(lcItemViewDelegateLifecycle) << "prepend" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.count();
2093 QQuickItem *item = d->getItem(modelIndex: idx, z: idx+1, async: nextPos >= 1);
2094 if (!item) {
2095 waiting = true;
2096 break;
2097 }
2098 if (d->items.contains(t: item)) {
2099 d->releaseItem(item);
2100 break; //Otherwise we'd "re-add" it, and get confused
2101 }
2102 if (d->currentIndex == idx) {
2103 currentVisible = true;
2104 d->currentItemOffset = nextPos;
2105 }
2106 d->items.prepend(t: item);
2107 d->updateItem(item, percent: nextPos);
2108 startIdx = idx;
2109 startPos = nextPos;
2110 --idx;
2111 if (idx < 0)
2112 idx = d->modelCount - 1;
2113 nextPos = d->positionOfIndex(index: idx);
2114 }
2115
2116 // In rare cases, when jumping around with pathCount close to modelCount,
2117 // new items appear in the middle. This more generic addition iteration handles this
2118 // Since this is the rare case, we try append/prepend first and only do this if
2119 // there are gaps still left to fill.
2120 if (!waiting && d->items.count() < count+d->cacheSize) {
2121 qCDebug(lcItemViewDelegateLifecycle) << "Checking for pathview middle inserts, items count was" << d->items.count();
2122 idx = startIdx;
2123 QQuickItem *lastItem = d->items.at(i: 0);
2124 while (idx != endIdx) {
2125 nextPos = d->positionOfIndex(index: idx);
2126 if (d->isInBound(position: nextPos, lower: d->mappedRange - d->mappedCache, upper: 1 + d->mappedCache)) {
2127 //This gets the reference from the delegate model, and will not re-create
2128 QQuickItem *item = d->getItem(modelIndex: idx, z: idx+1, async: nextPos >= 1);
2129 if (!item) {
2130 waiting = true;
2131 break;
2132 }
2133
2134 if (!d->items.contains(t: item)) { //We found a hole
2135 qCDebug(lcItemViewDelegateLifecycle) << "middle insert" << idx << "@" << nextPos
2136 << (d->currentIndex == idx ? "current" : "")
2137 << "items count was" << d->items.count();
2138 if (d->currentIndex == idx) {
2139 currentVisible = true;
2140 d->currentItemOffset = nextPos;
2141 }
2142 int lastListIdx = d->items.indexOf(t: lastItem);
2143 d->items.insert(i: lastListIdx + 1, t: item);
2144 d->updateItem(item, percent: nextPos);
2145 } else {
2146 d->releaseItem(item);
2147 }
2148
2149 lastItem = item;
2150 }
2151
2152 ++idx;
2153 if (idx >= d->modelCount)
2154 idx = 0;
2155 }
2156 }
2157 }
2158 }
2159
2160 bool currentChanged = false;
2161 if (!currentVisible) {
2162 d->currentItemOffset = 1;
2163 if (d->currentItem) {
2164 d->updateItem(item: d->currentItem, percent: 1);
2165 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
2166 if ((d->currentItem = d->getItem(modelIndex: d->currentIndex, z: d->currentIndex))) {
2167 currentChanged = true;
2168 d->updateItem(item: d->currentItem, percent: 1);
2169 if (QQuickPathViewAttached *att = d->attached(item: d->currentItem))
2170 att->setIsCurrentItem(true);
2171 }
2172 }
2173 } else if (!waiting && !d->currentItem) {
2174 if ((d->currentItem = d->getItem(modelIndex: d->currentIndex, z: d->currentIndex))) {
2175 currentChanged = true;
2176 d->currentItem->setFocus(true);
2177 if (QQuickPathViewAttached *att = d->attached(item: d->currentItem))
2178 att->setIsCurrentItem(true);
2179 }
2180 }
2181
2182 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2183 d->updateItem(item: d->highlightItem, percent: d->highlightRangeStart);
2184 if (QQuickPathViewAttached *att = d->attached(item: d->highlightItem))
2185 att->setOnPath(true);
2186 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
2187 d->updateItem(item: d->highlightItem, percent: d->currentItemOffset);
2188 if (QQuickPathViewAttached *att = d->attached(item: d->highlightItem))
2189 att->setOnPath(currentVisible);
2190 }
2191 for (QQuickItem *item : qAsConst(t&: d->itemCache))
2192 d->releaseItem(item);
2193 d->itemCache.clear();
2194
2195 d->inRefill = false;
2196 if (currentChanged)
2197 emit currentItemChanged();
2198}
2199
2200void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
2201{
2202 Q_D(QQuickPathView);
2203 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
2204 return;
2205
2206 if (reset) {
2207 d->modelCount = d->model->count();
2208 d->regenerate();
2209 emit countChanged();
2210 return;
2211 }
2212
2213 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
2214 return;
2215
2216 const int modelCount = d->modelCount;
2217 int moveId = -1;
2218 int moveOffset = 0;
2219 bool currentChanged = false;
2220 bool changedOffset = false;
2221 for (const QQmlChangeSet::Change &r : changeSet.removes()) {
2222 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
2223 d->currentIndex -= r.count;
2224 currentChanged = true;
2225 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
2226 // current item has been removed.
2227 if (r.isMove()) {
2228 moveId = r.moveId;
2229 moveOffset = d->currentIndex - r.index;
2230 } else if (d->currentItem) {
2231 if (QQuickPathViewAttached *att = d->attached(item: d->currentItem))
2232 att->setIsCurrentItem(true);
2233 d->releaseItem(item: d->currentItem);
2234 d->currentItem = nullptr;
2235 }
2236 d->currentIndex = qMin(a: r.index, b: d->modelCount - r.count - 1);
2237 currentChanged = true;
2238 }
2239
2240 if (r.index > d->currentIndex) {
2241 changedOffset = true;
2242 d->offset -= r.count;
2243 d->offsetAdj -= r.count;
2244 }
2245 d->modelCount -= r.count;
2246 }
2247 for (const QQmlChangeSet::Change &i : changeSet.inserts()) {
2248 if (d->modelCount) {
2249 if (moveId == -1 && i.index <= d->currentIndex) {
2250 d->currentIndex += i.count;
2251 currentChanged = true;
2252 } else {
2253 if (moveId != -1 && moveId == i.moveId) {
2254 d->currentIndex = i.index + moveOffset;
2255 currentChanged = true;
2256 }
2257 if (i.index > d->currentIndex) {
2258 d->offset += i.count;
2259 d->offsetAdj += i.count;
2260 changedOffset = true;
2261 }
2262 }
2263 }
2264 d->modelCount += i.count;
2265 }
2266
2267 d->offset = std::fmod(x: d->offset, y: qreal(d->modelCount));
2268 if (d->offset < 0)
2269 d->offset += d->modelCount;
2270 if (d->currentIndex == -1)
2271 d->currentIndex = d->calcCurrentIndex();
2272
2273 d->itemCache += d->items;
2274 d->items.clear();
2275
2276 if (!d->modelCount) {
2277 for (QQuickItem * item : qAsConst(t&: d->itemCache))
2278 d->releaseItem(item);
2279 d->itemCache.clear();
2280 d->offset = 0;
2281 changedOffset = true;
2282 d->tl.reset(d->moveOffset);
2283 } else {
2284 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2285 d->offset = std::fmod(x: qreal(d->modelCount - d->currentIndex), y: qreal(d->modelCount));
2286 changedOffset = true;
2287 }
2288 d->updateMappedRange();
2289 d->scheduleLayout();
2290 }
2291 if (changedOffset)
2292 emit offsetChanged();
2293 if (currentChanged)
2294 emit currentIndexChanged();
2295 if (d->modelCount != modelCount)
2296 emit countChanged();
2297}
2298
2299void QQuickPathView::destroyingItem(QObject *item)
2300{
2301 Q_UNUSED(item);
2302}
2303
2304void QQuickPathView::ticked()
2305{
2306 Q_D(QQuickPathView);
2307 d->updateCurrent();
2308}
2309
2310void QQuickPathView::movementEnding()
2311{
2312 Q_D(QQuickPathView);
2313 if (d->flicking) {
2314 d->flicking = false;
2315 emit flickingChanged();
2316 emit flickEnded();
2317 }
2318 if (d->moving && !d->stealMouse) {
2319 d->moving = false;
2320 emit movingChanged();
2321 emit movementEnded();
2322 }
2323 d->moveDirection = d->movementDirection;
2324}
2325
2326// find the item closest to the snap position
2327int QQuickPathViewPrivate::calcCurrentIndex()
2328{
2329 int current = 0;
2330 if (modelCount && model && items.count()) {
2331 offset = std::fmod(x: offset, y: qreal(modelCount));
2332 if (offset < 0)
2333 offset += modelCount;
2334 current = qRound(d: qAbs(t: std::fmod(x: modelCount - offset, y: qreal(modelCount))));
2335 current = current % modelCount;
2336 }
2337
2338 return current;
2339}
2340
2341void QQuickPathViewPrivate::createCurrentItem()
2342{
2343 if (requestedIndex != -1)
2344 return;
2345
2346 bool inItems = false;
2347 for (QQuickItem *item : qAsConst(t&: items)) {
2348 if (model->indexOf(object: item, objectContext: nullptr) == currentIndex) {
2349 inItems = true;
2350 break;
2351 }
2352 }
2353
2354 if (inItems) {
2355 if ((currentItem = getItem(modelIndex: currentIndex, z: currentIndex))) {
2356 currentItem->setFocus(true);
2357 if (QQuickPathViewAttached *att = attached(item: currentItem))
2358 att->setIsCurrentItem(true);
2359 }
2360 } else if (currentIndex >= 0 && currentIndex < modelCount) {
2361 if ((currentItem = getItem(modelIndex: currentIndex, z: currentIndex))) {
2362 updateItem(item: currentItem, percent: 1);
2363 if (QQuickPathViewAttached *att = attached(item: currentItem))
2364 att->setIsCurrentItem(true);
2365 }
2366 }
2367}
2368
2369void QQuickPathViewPrivate::updateCurrent()
2370{
2371 Q_Q(QQuickPathView);
2372 if (moveReason == SetIndex)
2373 return;
2374 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
2375 return;
2376
2377 int idx = calcCurrentIndex();
2378 if (model && (idx != currentIndex || !currentItem)) {
2379 if (currentItem) {
2380 if (QQuickPathViewAttached *att = attached(item: currentItem))
2381 att->setIsCurrentItem(false);
2382 releaseItem(item: currentItem);
2383 }
2384 int oldCurrentIndex = currentIndex;
2385 currentIndex = idx;
2386 currentItem = nullptr;
2387 createCurrentItem();
2388 if (oldCurrentIndex != currentIndex)
2389 emit q->currentIndexChanged();
2390 emit q->currentItemChanged();
2391 }
2392}
2393
2394void QQuickPathViewPrivate::fixOffsetCallback(void *d)
2395{
2396 static_cast<QQuickPathViewPrivate *>(d)->fixOffset();
2397}
2398
2399void QQuickPathViewPrivate::fixOffset()
2400{
2401 Q_Q(QQuickPathView);
2402 if (model && items.count()) {
2403 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
2404 || snapMode != QQuickPathView::NoSnap)) {
2405 int curr = calcCurrentIndex();
2406 if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
2407 q->setCurrentIndex(curr);
2408 else
2409 snapToIndex(index: curr, reason: Other);
2410 }
2411 }
2412}
2413
2414void QQuickPathViewPrivate::snapToIndex(int index, MovementReason reason)
2415{
2416 if (!model || modelCount <= 0)
2417 return;
2418
2419 qreal targetOffset = std::fmod(x: qreal(modelCount - index), y: qreal(modelCount));
2420 moveReason = reason;
2421 offsetAdj = 0;
2422 tl.reset(moveOffset);
2423 moveOffset.setValue(offset);
2424
2425 const int duration = highlightMoveDuration;
2426
2427 const qreal count = pathItems == -1 ? modelCount : qMin(a: pathItems, b: modelCount);
2428 const qreal averageItemLength = path->path().length() / count;
2429 const qreal threshold = 0.5 / averageItemLength; // if we are within .5 px, we want to immediately assign rather than animate
2430
2431 if (!duration || qAbs(t: offset - targetOffset) < threshold || (qFuzzyIsNull(d: targetOffset) && qAbs(t: modelCount - offset) < threshold)) {
2432 tl.set(moveOffset, targetOffset);
2433 } else if (moveDirection == QQuickPathView::Positive || (moveDirection == QQuickPathView::Shortest && targetOffset - offset > modelCount/2)) {
2434 qreal distance = modelCount - targetOffset + offset;
2435 if (targetOffset > moveOffset) {
2436 tl.move(moveOffset, destination: 0, QEasingCurve(QEasingCurve::InQuad), time: int(duration * offset / distance));
2437 tl.set(moveOffset, modelCount);
2438 tl.move(moveOffset, destination: targetOffset, QEasingCurve(qFuzzyIsNull(d: offset) ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), time: int(duration * (modelCount-targetOffset) / distance));
2439 } else {
2440 tl.move(moveOffset, destination: targetOffset, QEasingCurve(QEasingCurve::InOutQuad), time: duration);
2441 }
2442 } else if (moveDirection == QQuickPathView::Negative || targetOffset - offset <= -modelCount/2) {
2443 qreal distance = modelCount - offset + targetOffset;
2444 if (targetOffset < moveOffset) {
2445 tl.move(moveOffset, destination: modelCount, QEasingCurve(qFuzzyIsNull(d: targetOffset) ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), time: int(duration * (modelCount-offset) / distance));
2446 tl.set(moveOffset, 0);
2447 tl.move(moveOffset, destination: targetOffset, QEasingCurve(QEasingCurve::OutQuad), time: int(duration * targetOffset / distance));
2448 } else {
2449 tl.move(moveOffset, destination: targetOffset, QEasingCurve(QEasingCurve::InOutQuad), time: duration);
2450 }
2451 } else {
2452 tl.move(moveOffset, destination: targetOffset, QEasingCurve(QEasingCurve::InOutQuad), time: duration);
2453 }
2454}
2455
2456QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
2457{
2458 return new QQuickPathViewAttached(obj);
2459}
2460
2461QT_END_NAMESPACE
2462
2463#include "moc_qquickpathview_p.cpp"
2464

source code of qtdeclarative/src/quick/items/qquickpathview.cpp