Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickpathview_p.h"
++ #include "qquickpathview_p_p.h"
++ #include "qquickwindow.h"
++ #include "qquicktext_p.h"
++
++ #include <QtQml/qqmlcomponent.h>
++ #include <QtQuick/private/qquickpointerhandler_p.h>
++ #include <QtQuick/private/qquickstate_p.h>
++ #include <private/qqmlglobal_p.h>
++ #include <private/qqmlopenmetaobject_p.h>
++ #include <private/qqmlchangeset_p.h>
++ #include <qpa/qplatformtheme.h>
++
++ #include <QtQml/qqmlinfo.h>
++
++ #include <QtGui/private/qeventpoint_p.h>
++ #include <QtGui/qevent.h>
++ #include <QtGui/qguiapplication.h>
++ #include <QtGui/private/qguiapplication_p.h>
++ #include <QtGui/qstylehints.h>
++ #include <QtCore/qmath.h>
++
++ #include <cmath>
++
++ #if QT_CONFIG(quick_itemview)
++ #include <private/qquickitemview_p.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ #if !QT_CONFIG(quick_itemview)
++ Q_STATIC_LOGGING_CATEGORY(lcItemViewDelegateLifecycle, "qt.quick.itemview.lifecycle")
++ #endif
++ Q_STATIC_LOGGING_CATEGORY(lcPathView, "qt.quick.pathview")
++
++ static QQmlOpenMetaObjectType *qPathViewAttachedType = nullptr;
++
++ QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
++ : QObject(parent), m_percent(-1), m_view(nullptr), m_onPath(false), m_isCurrent(false)
++ {
++ if (qPathViewAttachedType) {
++ m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
++ m_metaobject->setCached(true);
++ } else {
++ m_metaobject = new QQmlOpenMetaObject(this);
++ }
++ }
++
++ QQuickPathViewAttached::~QQuickPathViewAttached()
++ {
++ }
++
++ QVariant QQuickPathViewAttached::value(const QByteArray &name) const
++ {
++ return m_metaobject->value(name);
++ }
++ void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
++ {
++ m_metaobject->setValue(name, val);
++ }
++
++ QQuickPathViewPrivate::QQuickPathViewPrivate()
++ : path(nullptr), currentIndex(0), currentItemOffset(0), startPc(0)
++ , offset(0), offsetAdj(0), mappedRange(1), mappedCache(0)
++ , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
++ , autoHighlight(true), highlightUp(false), layoutScheduled(false)
++ , moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false)
++ , inRefill(false)
++ , dragMargin(0), deceleration(100)
++ , maximumFlickVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal())
++ , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
++ , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedCacheSize(0), requestedZ(0)
++ , moveReason(Other), movementDirection(QQuickPathView::Shortest), moveDirection(QQuickPathView::Shortest)
++ , attType(nullptr), highlightComponent(nullptr), highlightItem(nullptr)
++ , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
++ , highlightPosition(0)
++ , highlightRangeStart(0), highlightRangeEnd(0)
++ , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
++ , highlightMoveDuration(300), modelCount(0), velocitySamples(0)
++ , velocityWritePos(0), snapMode(QQuickPathView::NoSnap)
++ {
++ setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
++ }
++
++ void QQuickPathViewPrivate::init()
++ {
++ Q_Q(QQuickPathView);
++ offset = 0;
++ q->setAcceptedMouseButtons(Qt::LeftButton);
++ q->setFlag(QQuickItem::ItemIsFocusScope);
++ q->setFiltersChildMouseEvents(true);
++ qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()),
++ q, QQuickPathView, SLOT(ticked()));
++ timer.invalidate();
++ qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
++ q, QQuickPathView, SLOT(movementEnding()));
++ }
++
++ QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool async)
++ {
++ Q_Q(QQuickPathView);
++ requestedIndex = modelIndex;
++ requestedZ = z;
++ inRequest = true;
++ QObject *object = model->object(modelIndex, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
++ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
++ if (!item) {
++ if (object) {
++ model->release(object);
++ if (!delegateValidated) {
++ delegateValidated = true;
++ QObject* delegate = q->delegate();
++ qmlWarning(delegate ? delegate : q) << QQuickPathView::tr("Delegate must be of Item type");
++ }
++ }
++ } else {
++ item->setParentItem(q);
++ requestedIndex = -1;
++ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
++ itemPrivate->addItemChangeListener(
++ this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed);
++ }
++ inRequest = false;
++ return item;
++ }
++
++ void QQuickPathView::createdItem(int index, QObject *object)
++ {
++ Q_D(QQuickPathView);
++ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
++ if (d->requestedIndex != index) {
++ qPathViewAttachedType = d->attachedType();
++ QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
++ qPathViewAttachedType = nullptr;
++ if (att) {
++ att->m_view = this;
++ att->setOnPath(false);
++ }
++ item->setParentItem(this);
++ d->updateItem(item, 1);
++ } else {
++ d->requestedIndex = -1;
++ if (!d->inRequest)
++ refill();
++ }
++ }
++
++ void QQuickPathView::initItem(int index, QObject *object)
++ {
++ Q_D(QQuickPathView);
++ QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
++ if (item && d->requestedIndex == index) {
++ QQuickItemPrivate::get(item)->setCulled(true);
++ item->setParentItem(this);
++ qPathViewAttachedType = d->attachedType();
++ QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
++ qPathViewAttachedType = nullptr;
++ if (att) {
++ att->m_view = this;
++ qreal percent = d->positionOfIndex(index);
++ if (percent < 1 && d->path) {
++ const auto attributes = d->path->attributes();
++ for (const QString &attr : attributes)
++ att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
++ item->setZ(d->requestedZ);
++ }
++ att->setOnPath(percent < 1);
++ }
++ }
++ }
++
++ void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
++ {
++ if (!item)
++ return;
++ qCDebug(lcItemViewDelegateLifecycle) << "release" << item;
++ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
++ itemPrivate->removeItemChangeListener(
++ this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed);
++ if (!model)
++ return;
++ QQmlInstanceModel::ReleaseFlags flags = model->release(item);
++ if (!flags) {
++ // item was not destroyed, and we no longer reference it.
++ if (QQuickPathViewAttached *att = attached(item))
++ att->setOnPath(false);
++ } else if (flags & QQmlInstanceModel::Destroyed) {
++ // but we still reference it
++ item->setParentItem(nullptr);
++ }
++ }
++
++ QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item)
++ {
++ return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
++ }
++
++ QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
++ {
++ if (!attType) {
++ // pre-create one metatype to share with all attached objects
++ attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject);
++ if (path) {
++ const auto attributes = path->attributes();
++ for (const QString &attr : attributes)
++ attType->createProperty(attr.toUtf8());
++ }
++ }
++
++ return attType;
++ }
++
++ void QQuickPathViewPrivate::clear()
++ {
++ releaseCurrentItem();
++
++ for (QQuickItem *p : std::as_const(items))
++ releaseItem(p);
++
++ for (QQuickItem *p : std::as_const(itemCache))
++ releaseItem(p);
++
++ if (requestedIndex >= 0) {
++ if (model)
++ model->cancel(requestedIndex);
++ requestedIndex = -1;
++ }
++
++ items.clear();
++ itemCache.clear();
++ tl.clear();
++ }
++
++ void QQuickPathViewPrivate::updateMappedRange()
++ {
++ // Update the actual cache size to be at max
++ // the available non-visible items.
++ cacheSize = qMax(0, qMin(requestedCacheSize, modelCount - pathItems));
++
++ if (model && pathItems != -1 && pathItems < modelCount) {
++ mappedRange = qreal(modelCount)/pathItems;
++ mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
++ } else {
++ mappedRange = 1;
++ mappedCache = 0;
++ }
++ }
++
++ qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const
++ {
++ qreal pos = -1;
++
++ if (model && index >= 0 && index < modelCount) {
++ qreal start = 0;
++ if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
++ || snapMode != QQuickPathView::NoSnap))
++ start = highlightRangeStart;
++ qreal globalPos = index + offset;
++ globalPos = std::fmod(globalPos, qreal(modelCount)) / modelCount;
++ if (pathItems != -1 && pathItems < modelCount) {
++ globalPos += start / mappedRange;
++ globalPos = std::fmod(globalPos, qreal(1));
++ pos = globalPos * mappedRange;
++ } else {
++ pos = std::fmod(globalPos + start, qreal(1));
++ }
++ }
++
++ return pos;
++ }
++
++ // returns true if position is between lower and upper, taking into
++ // account the circular space.
++ bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower,
++ qreal upper, bool emptyRangeCheck) const
++ {
++ if (emptyRangeCheck && qFuzzyCompare(lower, upper))
++ return true;
++ if (lower > upper) {
++ if (position > upper && position > lower)
++ position -= mappedRange;
++ lower -= mappedRange;
++ }
++ return position >= lower && position < upper;
++ }
++
++ void QQuickPathViewPrivate::createHighlight()
++ {
++ Q_Q(QQuickPathView);
++ if (!q->isComponentComplete())
++ return;
++
++ bool changed = false;
++ if (highlightItem) {
++ highlightItem->setParentItem(nullptr);
++ highlightItem->deleteLater();
++ highlightItem = nullptr;
++ changed = true;
++ }
++
++ QQuickItem *item = nullptr;
++ if (highlightComponent) {
++ QQmlContext *creationContext = highlightComponent->creationContext();
++ QQmlContext *highlightContext = new QQmlContext(
++ creationContext ? creationContext : qmlContext(q));
++ QObject *nobj = highlightComponent->create(highlightContext);
++ if (nobj) {
++ QQml_setParent_noEvent(highlightContext, nobj);
++ item = qobject_cast<QQuickItem *>(nobj);
++ if (!item)
++ delete nobj;
++ } else {
++ delete highlightContext;
++ }
++ } else {
++ item = new QQuickItem;
++ }
++ if (item) {
++ QQml_setParent_noEvent(item, q);
++ item->setParentItem(q);
++ highlightItem = item;
++ changed = true;
++ }
++ if (changed)
++ emit q->highlightItemChanged();
++ }
++
++ void QQuickPathViewPrivate::updateHighlight()
++ {
++ Q_Q(QQuickPathView);
++ if (!q->isComponentComplete() || !isValid())
++ return;
++ if (highlightItem) {
++ if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
++ updateItem(highlightItem, highlightRangeStart);
++ } else {
++ qreal target = currentIndex;
++
++ offsetAdj = 0;
++ tl.reset(moveHighlight);
++ moveHighlight.setValue(highlightPosition);
++
++ const int duration = highlightMoveDuration;
++
++ if (target - highlightPosition > modelCount/2) {
++ highlightUp = false;
++ qreal distance = modelCount - target + highlightPosition;
++ tl.move(moveHighlight, 0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
++ tl.set(moveHighlight, modelCount-0.01);
++ tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
++ } else if (target - highlightPosition <= -modelCount/2) {
++ highlightUp = true;
++ qreal distance = modelCount - highlightPosition + target;
++ tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
++ tl.set(moveHighlight, 0);
++ tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
++ } else {
++ highlightUp = highlightPosition - target < 0;
++ tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
++ }
++ }
++ }
++ }
++
++ void QQuickPathViewPrivate::setHighlightPosition(qreal pos)
++ {
++ if (!(qFuzzyCompare(pos, highlightPosition))) {
++ qreal start = 0;
++ qreal end = 1;
++ if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
++ start = highlightRangeStart;
++ end = highlightRangeEnd;
++ }
++
++ qreal range = qreal(modelCount);
++ // calc normalized position of highlight relative to offset
++ qreal relativeHighlight = std::fmod(pos + offset, range) / range;
++
++ if (!highlightUp && relativeHighlight > end / mappedRange) {
++ qreal diff = 1 - relativeHighlight;
++ setOffset(offset + diff * range);
++ } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) {
++ qreal diff = relativeHighlight - (end - start) / mappedRange;
++ setOffset(offset - diff * range - 0.00001);
++ }
++
++ highlightPosition = pos;
++ qreal pathPos = positionOfIndex(pos);
++ updateItem(highlightItem, pathPos);
++ if (QQuickPathViewAttached *att = attached(highlightItem))
++ att->setOnPath(pathPos < 1);
++ }
++ }
++
++ void QQuickPathView::pathUpdated()
++ {
++ Q_D(QQuickPathView);
++ for (QQuickItem *item : std::as_const(d->items)) {
++ if (QQuickPathViewAttached *att = d->attached(item))
++ att->m_percent = -1;
++ }
++ refill();
++ }
++
++ void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
++ {
++ if (!path)
++ return;
++ if (QQuickPathViewAttached *att = attached(item)) {
++ if (qFuzzyCompare(att->m_percent, percent))
++ return;
++ att->m_percent = percent;
++ const auto attributes = path->attributes();
++ for (const QString &attr : attributes)
++ att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
++ att->setOnPath(percent < 1);
++ }
++ QQuickItemPrivate::get(item)->setCulled(percent >= 1);
++ QPointF pf = path->pointAtPercent(qMin(percent, qreal(1)));
++ item->setX(pf.x() - item->width()/2);
++ item->setY(pf.y() - item->height()/2);
++ }
++
++ void QQuickPathViewPrivate::regenerate()
++ {
++ Q_Q(QQuickPathView);
++ if (!q->isComponentComplete())
++ return;
++
++ clear();
++
++ if (!isValid())
++ return;
++
++ updateMappedRange();
++ q->refill();
++ }
++
++ void QQuickPathViewPrivate::setDragging(bool d)
++ {
++ Q_Q(QQuickPathView);
++ if (dragging == d)
++ return;
++
++ dragging = d;
++ if (dragging)
++ emit q->dragStarted();
++ else
++ emit q->dragEnded();
++
++ emit q->draggingChanged();
++ }
++
++ /*!
++ \qmltype PathView
++ \nativetype QQuickPathView
++ \inqmlmodule QtQuick
++ \ingroup qtquick-paths
++ \ingroup qtquick-views
++ \inherits Item
++ \brief Lays out model-provided items on a path.
++
++ A PathView displays data from models created from built-in QML types like ListModel
++ and XmlListModel, or custom model classes defined in C++ that inherit from
++ QAbstractListModel.
++
++ The view has a \l model, which defines the data to be displayed, and
++ a \l delegate, which defines how the data should be displayed.
++ The \l delegate is instantiated for each item on the \l path.
++ The items may be flicked to move them along the path.
++
++ For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
++
++ \snippet qml/pathview/ContactModel.qml 0
++
++ This data can be represented as a PathView, like this:
++
++ \snippet qml/pathview/pathview.qml 0
++
++ \image pathview.gif {Qt logos rotating along curved path with
++ names Jane Doe, Bill Jones, John Smith}
++
++ (Note the above example uses PathAttribute to scale and modify the
++ opacity of the items as they rotate. This additional code can be seen in the
++ PathAttribute documentation.)
++
++ PathView does not automatically handle keyboard navigation. This is because
++ the keys to use for navigation will depend upon the shape of the path. Navigation
++ can be added quite simply by setting \c focus to \c true and calling
++ \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
++ using the left and right arrow keys:
++
++ \qml
++ PathView {
++ // ...
++ focus: true
++ Keys.onLeftPressed: decrementCurrentIndex()
++ Keys.onRightPressed: incrementCurrentIndex()
++ }
++ \endqml
++
++ The path view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
++
++ Delegates are instantiated as needed and may be destroyed at any time.
++ State should \e never be stored in a delegate.
++
++ PathView attaches a number of properties to the root item of the delegate, for example
++ \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
++ this attached property directly as \c PathView.isCurrentItem, while the child
++ \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
++
++ \snippet qml/pathview/pathview.qml 1
++
++ \b Note that views do not enable \e clip automatically. If the view
++ is not clipped by another item or the screen, it will be necessary
++ to set \e {clip: true} in order to have the out of view items clipped
++ nicely.
++
++ \sa Path, {QML Data Models}, ListView, GridView, {Qt Quick Examples - Views}
++ */
++
++ QQuickPathView::QQuickPathView(QQuickItem *parent)
++ : QQuickItem(*(new QQuickPathViewPrivate), parent)
++ {
++ Q_D(QQuickPathView);
++ d->init();
++ }
++
++ QQuickPathView::~QQuickPathView()
++ {
++ Q_D(QQuickPathView);
++ d->clear();
++ if (d->attType)
++ d->attType->release();
++ if (d->ownModel)
++ delete d->model;
++ }
++
++ /*!
++ \qmlattachedproperty PathView QtQuick::PathView::view
++ \readonly
++
++ This attached property holds the view that manages this delegate instance.
++
++ It is attached to each instance of the delegate.
++ */
++
++ /*!
++ \qmlattachedproperty bool QtQuick::PathView::onPath
++ \readonly
++
++ This attached property holds whether the item is currently on the path.
++
++ If a pathItemCount has been set, it is possible that some items may
++ be instantiated, but not considered to be currently on the path.
++ Usually, these items would be set invisible, for example:
++
++ \qml
++ Component {
++ Rectangle {
++ visible: PathView.onPath
++ // ...
++ }
++ }
++ \endqml
++
++ It is attached to each instance of the delegate.
++ */
++
++ /*!
++ \qmlattachedproperty bool QtQuick::PathView::isCurrentItem
++ \readonly
++
++ This attached property is true if this delegate is the current item; otherwise false.
++
++ It is attached to each instance of the delegate.
++
++ This property may be used to adjust the appearance of the current item.
++
++ \snippet qml/pathview/pathview.qml 1
++ */
++
++ /*!
++ \qmlproperty model QtQuick::PathView::model
++ This property holds the model providing data for the view.
++
++ The model provides a set of data that is used to create the items for the view.
++ For large or dynamic datasets the model is usually provided by a C++ model object.
++ Models can also be created directly in QML, using the ListModel type.
++
++ \note changing the model will reset the offset and currentIndex to 0.
++
++ \sa {qml-data-models}{Data Models}
++ */
++ QVariant QQuickPathView::model() const
++ {
++ Q_D(const QQuickPathView);
++ return d->modelVariant;
++ }
++
++ void QQuickPathView::setModel(const QVariant &m)
++ {
++ Q_D(QQuickPathView);
++ QVariant model = m;
++ if (model.userType() == qMetaTypeId<QJSValue>())
++ model = model.value<QJSValue>().toVariant();
++
++ if (d->modelVariant == model)
++ return;
++
++ if (d->model) {
++ qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
++ this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
++ qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
++ this, QQuickPathView, SLOT(createdItem(int,QObject*)));
++ qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
++ this, QQuickPathView, SLOT(initItem(int,QObject*)));
++ d->clear();
++ }
++
++ d->modelVariant = model;
++ QObject *object = qvariant_cast<QObject*>(model);
++ QQmlInstanceModel *vim = nullptr;
++ if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
++ if (d->ownModel) {
++ delete d->model;
++ d->ownModel = false;
++ }
++ d->model = vim;
++ } else {
++ if (!d->ownModel) {
++ d->model = new QQmlDelegateModel(qmlContext(this));
++ d->ownModel = true;
++ if (isComponentComplete())
++ static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
++ }
++ if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
++ dataModel->setModel(model);
++ }
++ int oldModelCount = d->modelCount;
++ d->modelCount = 0;
++ if (d->model) {
++ qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
++ this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
++ qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
++ this, QQuickPathView, SLOT(createdItem(int,QObject*)));
++ qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
++ this, QQuickPathView, SLOT(initItem(int,QObject*)));
++ d->modelCount = d->model->count();
++ }
++ if (isComponentComplete()) {
++ if (d->currentIndex != 0) {
++ d->currentIndex = 0;
++ emit currentIndexChanged();
++ }
++ if (!(qFuzzyIsNull(d->offset))) {
++ d->offset = 0;
++ emit offsetChanged();
++ }
++ }
++ d->regenerate();
++ if (d->modelCount != oldModelCount)
++ emit countChanged();
++ emit modelChanged();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::PathView::count
++ This property holds the number of items in the model.
++ */
++ int QQuickPathView::count() const
++ {
++ Q_D(const QQuickPathView);
++ return d->model ? d->modelCount : 0;
++ }
++
++ /*!
++ \qmlproperty Path QtQuick::PathView::path
++ This property holds the path used to lay out the items.
++ For more information see the \l Path documentation.
++ */
++ QQuickPath *QQuickPathView::path() const
++ {
++ Q_D(const QQuickPathView);
++ return d->path;
++ }
++
++ void QQuickPathView::setPath(QQuickPath *path)
++ {
++ Q_D(QQuickPathView);
++ if (d->path == path)
++ return;
++ if (d->path)
++ qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
++ this, QQuickPathView, SLOT(pathUpdated()));
++ d->path = path;
++
++ if (path) {
++ qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
++ this, QQuickPathView, SLOT(pathUpdated()));
++ }
++
++ if (isComponentComplete()) {
++ d->clear();
++ if (d->isValid()) {
++ if (d->attType) {
++ d->attType->release();
++ d->attType = nullptr;
++ }
++ d->regenerate();
++ }
++ }
++
++ emit pathChanged();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::PathView::currentIndex
++ This property holds the index of the current item.
++ */
++ int QQuickPathView::currentIndex() const
++ {
++ Q_D(const QQuickPathView);
++ return d->currentIndex;
++ }
++
++ void QQuickPathView::setCurrentIndex(int idx)
++ {
++ Q_D(QQuickPathView);
++ if (!isComponentComplete()) {
++ if (idx != d->currentIndex) {
++ d->currentIndex = idx;
++ emit currentIndexChanged();
++ }
++ return;
++ }
++
++ idx = d->modelCount
++ ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
++ : 0;
++ if (d->model && (idx != d->currentIndex || !d->currentItem)) {
++ const bool hadCurrentItem = d->currentItem != nullptr;
++ const int oldCurrentIdx = d->currentIndex;
++ if (hadCurrentItem) {
++ if (QQuickPathViewAttached *att = d->attached(d->currentItem))
++ att->setIsCurrentItem(false);
++ d->releaseCurrentItem();
++ }
++ d->moveReason = QQuickPathViewPrivate::SetIndex;
++ d->currentIndex = idx;
++ if (d->modelCount) {
++ d->createCurrentItem();
++ if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
++ d->snapToIndex(d->currentIndex, QQuickPathViewPrivate::SetIndex);
++ d->currentItemOffset = d->positionOfIndex(d->currentIndex);
++ d->updateHighlight();
++ }
++ if (oldCurrentIdx != d->currentIndex)
++ emit currentIndexChanged();
++ if (hadCurrentItem)
++ emit currentItemChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty Item QtQuick::PathView::currentItem
++ This property holds the current item in the view.
++ */
++ QQuickItem *QQuickPathView::currentItem() const
++ {
++ Q_D(const QQuickPathView);
++ return d->currentItem;
++ }
++
++ /*!
++ \qmlmethod void QtQuick::PathView::incrementCurrentIndex()
++
++ Increments the current index.
++
++ \b Note: methods should only be called after the Component has completed.
++ */
++ void QQuickPathView::incrementCurrentIndex()
++ {
++ Q_D(QQuickPathView);
++ d->moveDirection = QQuickPathView::Positive;
++ setCurrentIndex(currentIndex()+1);
++ }
++
++ /*!
++ \qmlmethod void QtQuick::PathView::decrementCurrentIndex()
++
++ Decrements the current index.
++
++ \b Note: methods should only be called after the Component has completed.
++ */
++ void QQuickPathView::decrementCurrentIndex()
++ {
++ Q_D(QQuickPathView);
++ d->moveDirection = QQuickPathView::Negative;
++ setCurrentIndex(currentIndex()-1);
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathView::offset
++
++ The offset specifies how far along the path the items are from their initial positions.
++ This is a real number that ranges from \c 0 to the count of items in the model.
++ */
++ qreal QQuickPathView::offset() const
++ {
++ Q_D(const QQuickPathView);
++ return d->offset;
++ }
++
++ void QQuickPathView::setOffset(qreal offset)
++ {
++ Q_D(QQuickPathView);
++ d->moveReason = QQuickPathViewPrivate::Other;
++ d->setOffset(offset);
++ d->updateCurrent();
++ }
++
++ void QQuickPathViewPrivate::setOffset(qreal o)
++ {
++ Q_Q(QQuickPathView);
++ if (!qFuzzyCompare(offset, o)) {
++ if (isValid() && q->isComponentComplete()) {
++ qreal oldOffset = offset;
++ offset = std::fmod(o, qreal(modelCount));
++ if (offset < 0)
++ offset += qreal(modelCount);
++ qCDebug(lcItemViewDelegateLifecycle) << o << "was" << oldOffset << "now" << offset;
++ q->refill();
++ } else {
++ offset = o;
++ }
++ emit q->offsetChanged();
++ }
++ }
++
++ void QQuickPathViewPrivate::setAdjustedOffset(qreal o)
++ {
++ setOffset(o+offsetAdj);
++ }
++
++ /*!
++ \qmlproperty Component QtQuick::PathView::highlight
++ This property holds the component to use as the highlight.
++
++ An instance of the highlight component will be created for each view.
++ The geometry of the resultant component instance will be managed by the view
++ so as to stay with the current item.
++
++ The below example demonstrates how to make a simple highlight. Note the use
++ of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
++ the highlight is hidden when flicked away from the path.
++
++ \qml
++ Component {
++ Rectangle {
++ visible: PathView.onPath
++ // ...
++ }
++ }
++ \endqml
++
++ \sa highlightItem, highlightRangeMode
++ */
++ QQmlComponent *QQuickPathView::highlight() const
++ {
++ Q_D(const QQuickPathView);
++ return d->highlightComponent;
++ }
++
++ void QQuickPathView::setHighlight(QQmlComponent *highlight)
++ {
++ Q_D(QQuickPathView);
++ if (highlight != d->highlightComponent) {
++ d->highlightComponent = highlight;
++ d->createHighlight();
++ d->updateHighlight();
++ emit highlightChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty Item QtQuick::PathView::highlightItem
++
++ \c highlightItem holds the highlight item, which was created
++ from the \l highlight component.
++
++ \sa highlight
++ */
++ QQuickItem *QQuickPathView::highlightItem() const
++ {
++ Q_D(const QQuickPathView);
++ return d->highlightItem;
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathView::preferredHighlightBegin
++ \qmlproperty real QtQuick::PathView::preferredHighlightEnd
++ \qmlproperty enumeration QtQuick::PathView::highlightRangeMode
++
++ These properties set the preferred range of the highlight (current item)
++ within the view. The preferred values must be in the range from \c 0 to \c 1.
++
++ Valid values for \c highlightRangeMode are:
++
++ \value PathView.NoHighlightRange no range is applied: the highlight
++ will move freely within the view.
++ \value PathView.ApplyRange the view will attempt to maintain the highlight
++ within the range, however the highlight can move
++ outside of the range at the ends of the path or
++ due to a mouse interaction.
++ \value PathView.StrictlyEnforceRange the highlight will never move outside of the range.
++ This means that the current item will change if a
++ keyboard or mouse action would cause the highlight
++ to move outside of the range.
++
++ The default value is \e PathView.StrictlyEnforceRange.
++
++ Defining a highlight range is the correct way to influence where the
++ current item ends up when the view moves. For example, if you want the
++ currently selected item to be in the middle of the path, then set the
++ highlight range to be 0.5,0.5 and highlightRangeMode to \e PathView.StrictlyEnforceRange.
++ Then, when the path scrolls,
++ the currently selected item will be the item at that position. This also applies to
++ when the currently selected item changes - it will scroll to within the preferred
++ highlight range. Furthermore, the behaviour of the current item index will occur
++ whether or not a highlight exists.
++
++ \note A valid range requires \c preferredHighlightEnd to be greater
++ than or equal to \c preferredHighlightBegin.
++ */
++ qreal QQuickPathView::preferredHighlightBegin() const
++ {
++ Q_D(const QQuickPathView);
++ return d->highlightRangeStart;
++ }
++
++ void QQuickPathView::setPreferredHighlightBegin(qreal start)
++ {
++ Q_D(QQuickPathView);
++ if (qFuzzyCompare(d->highlightRangeStart, start) || start < 0 || start > 1)
++ return;
++ d->highlightRangeStart = start;
++ d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
++ refill();
++ emit preferredHighlightBeginChanged();
++ }
++
++ qreal QQuickPathView::preferredHighlightEnd() const
++ {
++ Q_D(const QQuickPathView);
++ return d->highlightRangeEnd;
++ }
++
++ void QQuickPathView::setPreferredHighlightEnd(qreal end)
++ {
++ Q_D(QQuickPathView);
++ if (qFuzzyCompare(d->highlightRangeEnd, end) || end < 0 || end > 1)
++ return;
++ d->highlightRangeEnd = end;
++ d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
++ refill();
++ emit preferredHighlightEndChanged();
++ }
++
++ QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
++ {
++ Q_D(const QQuickPathView);
++ return d->highlightRangeMode;
++ }
++
++ void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
++ {
++ Q_D(QQuickPathView);
++ if (d->highlightRangeMode == mode)
++ return;
++ d->highlightRangeMode = mode;
++ d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
++ if (d->haveHighlightRange) {
++ d->regenerate();
++ int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
++ if (index >= 0)
++ d->snapToIndex(index, QQuickPathViewPrivate::Other);
++ }
++ emit highlightRangeModeChanged();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::PathView::highlightMoveDuration
++ This property holds the move animation duration of the highlight delegate.
++
++ If the highlightRangeMode is StrictlyEnforceRange then this property
++ determines the speed that the items move along the path.
++
++ The default value for the duration is 300ms.
++ */
++ int QQuickPathView::highlightMoveDuration() const
++ {
++ Q_D(const QQuickPathView);
++ return d->highlightMoveDuration;
++ }
++
++ void QQuickPathView::setHighlightMoveDuration(int duration)
++ {
++ Q_D(QQuickPathView);
++ if (d->highlightMoveDuration == duration)
++ return;
++ d->highlightMoveDuration = duration;
++ emit highlightMoveDurationChanged();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathView::dragMargin
++ This property holds the maximum distance from the path that initiates mouse dragging.
++
++ By default the path can only be dragged by clicking on an item. If
++ dragMargin is greater than zero, a drag can be initiated by clicking
++ within dragMargin pixels of the path.
++ */
++ qreal QQuickPathView::dragMargin() const
++ {
++ Q_D(const QQuickPathView);
++ return d->dragMargin;
++ }
++
++ void QQuickPathView::setDragMargin(qreal dragMargin)
++ {
++ Q_D(QQuickPathView);
++ if (qFuzzyCompare(d->dragMargin, dragMargin))
++ return;
++ d->dragMargin = dragMargin;
++ emit dragMarginChanged();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathView::flickDeceleration
++ This property holds the rate at which a flick will decelerate.
++
++ The default is 100.
++ */
++ qreal QQuickPathView::flickDeceleration() const
++ {
++ Q_D(const QQuickPathView);
++ return d->deceleration;
++ }
++
++ void QQuickPathView::setFlickDeceleration(qreal dec)
++ {
++ Q_D(QQuickPathView);
++ if (qFuzzyCompare(d->deceleration, dec))
++ return;
++ d->deceleration = dec;
++ emit flickDecelerationChanged();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathView::maximumFlickVelocity
++ This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
++
++ The default value is platform dependent.
++ */
++ qreal QQuickPathView::maximumFlickVelocity() const
++ {
++ Q_D(const QQuickPathView);
++ return d->maximumFlickVelocity;
++ }
++
++ void QQuickPathView::setMaximumFlickVelocity(qreal vel)
++ {
++ Q_D(QQuickPathView);
++ if (qFuzzyCompare(vel, d->maximumFlickVelocity))
++ return;
++ d->maximumFlickVelocity = vel;
++ emit maximumFlickVelocityChanged();
++ }
++
++
++ /*!
++ \qmlproperty bool QtQuick::PathView::interactive
++
++ A user cannot drag or flick a PathView that is not interactive.
++
++ This property is useful for temporarily disabling flicking. This allows
++ special interaction with PathView's children.
++ */
++ bool QQuickPathView::isInteractive() const
++ {
++ Q_D(const QQuickPathView);
++ return d->interactive;
++ }
++
++ void QQuickPathView::setInteractive(bool interactive)
++ {
++ Q_D(QQuickPathView);
++ if (interactive != d->interactive) {
++ d->interactive = interactive;
++ if (!interactive)
++ d->tl.clear();
++ emit interactiveChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::PathView::moving
++
++ This property holds whether the view is currently moving
++ due to the user either dragging or flicking the view.
++ */
++ bool QQuickPathView::isMoving() const
++ {
++ Q_D(const QQuickPathView);
++ return d->moving;
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::PathView::flicking
++
++ This property holds whether the view is currently moving
++ due to the user flicking the view.
++ */
++ bool QQuickPathView::isFlicking() const
++ {
++ Q_D(const QQuickPathView);
++ return d->flicking;
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::PathView::dragging
++
++ This property holds whether the view is currently moving
++ due to the user dragging the view.
++ */
++ bool QQuickPathView::isDragging() const
++ {
++ Q_D(const QQuickPathView);
++ return d->dragging;
++ }
++
++ /*!
++ \qmlsignal QtQuick::PathView::movementStarted()
++
++ This signal is emitted when the view begins moving due to user
++ interaction.
++ */
++
++ /*!
++ \qmlsignal QtQuick::PathView::movementEnded()
++
++ This signal is emitted when the view stops moving due to user
++ interaction. If a flick was generated, this signal will
++ be emitted once the flick stops. If a flick was not
++ generated, this signal will be emitted when the
++ user stops dragging - i.e. a mouse or touch release.
++ */
++
++ /*!
++ \qmlsignal QtQuick::PathView::flickStarted()
++
++ This signal is emitted when the view is flicked. A flick
++ starts from the point that the mouse or touch is released,
++ while still in motion.
++ */
++
++ /*!
++ \qmlsignal QtQuick::PathView::flickEnded()
++
++ This signal is emitted when the view stops moving due to a flick.
++ */
++
++ /*!
++ \qmlsignal QtQuick::PathView::dragStarted()
++
++ This signal is emitted when the view starts to be dragged due to user
++ interaction.
++ */
++
++ /*!
++ \qmlsignal QtQuick::PathView::dragEnded()
++
++ This signal is emitted when the user stops dragging the view.
++
++ If the velocity of the drag is suffient at the time the
++ touch/mouse button is released then a flick will start.
++ */
++
++ /*!
++ \qmlproperty Component QtQuick::PathView::delegate
++
++ The delegate provides a template defining each item instantiated by the view.
++ The index is exposed as an accessible \c index property. Properties of the
++ model are also available depending upon the type of \l {qml-data-models}{Data Model}.
++
++ The number of objects and bindings in the delegate has a direct effect on the
++ flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
++ that is not needed for the normal display of the delegate in a \l Loader which
++ can load additional components when needed.
++
++ Note that the PathView will layout the items based on the size of the root
++ item in the delegate.
++
++ Here is an example delegate:
++ \snippet qml/pathview/pathview.qml 1
++ */
++ QQmlComponent *QQuickPathView::delegate() const
++ {
++ Q_D(const QQuickPathView);
++ if (d->model) {
++ if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
++ return dataModel->delegate();
++ }
++
++ return nullptr;
++ }
++
++ void QQuickPathView::setDelegate(QQmlComponent *delegate)
++ {
++ Q_D(QQuickPathView);
++ if (delegate == this->delegate())
++ return;
++ if (!d->ownModel) {
++ d->model = new QQmlDelegateModel(qmlContext(this));
++ d->ownModel = true;
++ if (isComponentComplete())
++ static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
++ }
++ if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) {
++ int oldCount = dataModel->count();
++ dataModel->setDelegate(delegate);
++ d->modelCount = dataModel->count();
++ d->regenerate();
++ if (oldCount != dataModel->count())
++ emit countChanged();
++ emit delegateChanged();
++ d->delegateValidated = false;
++ }
++ }
++
++ /*!
++ \qmlproperty int QtQuick::PathView::pathItemCount
++ This property holds the number of items visible on the path at any one time.
++
++ Setting pathItemCount to undefined will show all items on the path.
++ */
++ int QQuickPathView::pathItemCount() const
++ {
++ Q_D(const QQuickPathView);
++ return d->pathItems;
++ }
++
++ void QQuickPathView::setPathItemCount(int i)
++ {
++ Q_D(QQuickPathView);
++ if (i == d->pathItems)
++ return;
++ if (i < 1)
++ i = 1;
++ d->pathItems = i;
++ d->updateMappedRange();
++ if (d->isValid() && isComponentComplete()) {
++ d->regenerate();
++ }
++ emit pathItemCountChanged();
++ }
++
++ void QQuickPathView::resetPathItemCount()
++ {
++ Q_D(QQuickPathView);
++ if (-1 == d->pathItems)
++ return;
++ d->pathItems = -1;
++ d->updateMappedRange();
++ if (d->isValid() && isComponentComplete())
++ d->regenerate();
++ emit pathItemCountChanged();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::PathView::cacheItemCount
++ This property holds the maximum number of items to cache off the path.
++
++ For example, a PathView with a model containing 20 items, a pathItemCount
++ of 10, and an cacheItemCount of 4 will create up to 14 items, with 10 visible
++ on the path and 4 invisible cached items.
++
++ The cached delegates are created asynchronously,
++ allowing creation to occur across multiple frames and reducing the
++ likelihood of skipping frames.
++
++ \note Setting this property is not a replacement for creating efficient delegates.
++ It can improve the smoothness of scrolling behavior at the expense of additional
++ memory usage. The fewer objects and bindings in a delegate, the faster a
++ view can be scrolled. It is important to realize that setting cacheItemCount
++ will only postpone issues caused by slow-loading delegates, it is not a
++ solution for this scenario.
++
++ \sa pathItemCount
++ */
++ int QQuickPathView::cacheItemCount() const
++ {
++ Q_D(const QQuickPathView);
++ return d->requestedCacheSize;
++ }
++
++ void QQuickPathView::setCacheItemCount(int i)
++ {
++ Q_D(QQuickPathView);
++ if (i == d->requestedCacheSize || i < 0)
++ return;
++
++ d->requestedCacheSize = i;
++ d->updateMappedRange();
++ refill();
++ emit cacheItemCountChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::PathView::snapMode
++
++ This property determines how the items will settle following a drag or flick.
++ The possible values are:
++
++ \value PathView.NoSnap (default) the items stop anywhere along the path.
++ \value PathView.SnapToItem the items settle with an item aligned with the \l preferredHighlightBegin.
++ \value PathView.SnapOneItem the items settle no more than one item away from the item nearest
++ \l preferredHighlightBegin at the time the press is released. This mode is particularly
++ useful for moving one page at a time.
++
++ \c snapMode does not affect the \l currentIndex. To update the
++ \l currentIndex as the view is moved, set \l highlightRangeMode
++ to \c PathView.StrictlyEnforceRange (default for PathView).
++
++ \sa highlightRangeMode
++ */
++ QQuickPathView::SnapMode QQuickPathView::snapMode() const
++ {
++ Q_D(const QQuickPathView);
++ return d->snapMode;
++ }
++
++ void QQuickPathView::setSnapMode(SnapMode mode)
++ {
++ Q_D(QQuickPathView);
++ if (mode == d->snapMode)
++ return;
++ d->snapMode = mode;
++ emit snapModeChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::PathView::movementDirection
++ \since 5.7
++
++ This property determines the direction in which items move when setting the current index.
++ The possible values are:
++
++ \value PathView.Shortest (default) the items move in the direction that requires the least
++ movement, which could be either \c Negative or \c Positive.
++ \value PathView.Negative the items move backwards towards their destination.
++ \value PathView.Positive the items move forwards towards their destination.
++
++ For example, suppose that there are 5 items in the model, and \l currentIndex is \c 0.
++ If currentIndex is set to \c 2,
++
++ \list
++ \li a \c Positive movement direction will result in the following order: 0, 1, 2
++ \li a \c Negative movement direction will result in the following order: 0, 5, 4, 3, 2
++ \li a \c Shortest movement direction will result in same order with \c Positive .
++ \endlist
++
++ \note this property doesn't affect the movement of \l incrementCurrentIndex() and \l decrementCurrentIndex().
++ */
++ QQuickPathView::MovementDirection QQuickPathView::movementDirection() const
++ {
++ Q_D(const QQuickPathView);
++ return d->movementDirection;
++ }
++
++ void QQuickPathView::setMovementDirection(QQuickPathView::MovementDirection dir)
++ {
++ Q_D(QQuickPathView);
++ if (dir == d->movementDirection)
++ return;
++ d->movementDirection = dir;
++ if (!d->tl.isActive())
++ d->moveDirection = d->movementDirection;
++ emit movementDirectionChanged();
++ }
++
++ /*!
++ \qmlmethod void QtQuick::PathView::positionViewAtIndex(int index, PositionMode mode)
++
++ Positions the view such that the \a index is at the position specified by
++ \a mode:
++
++ \value PathView.Beginning position item at the beginning of the path.
++ \value PathView.Center position item in the center of the path.
++ \value PathView.End position item at the end of the path.
++ \value PathView.Contain ensure the item is positioned on the path.
++ \value PathView.SnapPosition position the item at \l preferredHighlightBegin. This mode
++ is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
++ via \l snapMode.
++
++ \b Note: methods should only be called after the Component has completed. To position
++ the view at startup, this method should be called by Component.onCompleted. For
++ example, to position the view at the end:
++
++ \code
++ Component.onCompleted: positionViewAtIndex(count - 1, PathView.End)
++ \endcode
++ */
++ void QQuickPathView::positionViewAtIndex(int index, int mode)
++ {
++ Q_D(QQuickPathView);
++ if (!d->isValid())
++ return;
++ if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView
++ return;
++
++ if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems))
++ return;
++
++ int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
++ int idx = (index+d->modelCount) % d->modelCount;
++ bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
++ || d->snapMode != QQuickPathView::NoSnap);
++
++ qreal beginOffset;
++ qreal endOffset;
++ if (snap) {
++ beginOffset = d->modelCount - idx - qFloor(count * d->highlightRangeStart);
++ endOffset = beginOffset + count - 1;
++ } else {
++ beginOffset = d->modelCount - idx;
++ // Small offset since the last point coincides with the first and
++ // this the only "end" position that gives the expected visual result.
++ qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001;
++ endOffset = std::fmod(beginOffset + count, qreal(d->modelCount)) - adj;
++ }
++ qreal offset = d->offset;
++ switch (mode) {
++ case Beginning:
++ offset = beginOffset;
++ break;
++ case End:
++ offset = endOffset;
++ break;
++ case Center:
++ if (beginOffset < endOffset)
++ offset = (beginOffset + endOffset)/2;
++ else
++ offset = (beginOffset + (endOffset + d->modelCount))/2;
++ if (snap)
++ offset = qRound(offset);
++ break;
++ case Contain:
++ if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset))
++ || (d->offset < beginOffset && d->offset > endOffset)) {
++ qreal diff1 = std::fmod(beginOffset - d->offset + d->modelCount, qreal(d->modelCount));
++ qreal diff2 = std::fmod(d->offset - endOffset + d->modelCount, qreal(d->modelCount));
++ if (diff1 < diff2)
++ offset = beginOffset;
++ else
++ offset = endOffset;
++ }
++ break;
++ case SnapPosition:
++ offset = d->modelCount - idx;
++ break;
++ }
++
++ d->tl.clear();
++ setOffset(offset);
++ }
++
++ /*!
++ \qmlmethod int QtQuick::PathView::indexAt(real x, real y)
++
++ Returns the index of the item containing the point \a x, \a y in content
++ coordinates. If there is no item at the point specified, -1 is returned.
++
++ \b Note: methods should only be called after the Component has completed.
++ */
++ int QQuickPathView::indexAt(qreal x, qreal y) const
++ {
++ Q_D(const QQuickPathView);
++ QQuickItem *item = itemAt(x, y);
++ return item ? d->model->indexOf(item, nullptr) : -1;
++ }
++
++ /*!
++ \qmlmethod Item QtQuick::PathView::itemAt(real x, real y)
++
++ Returns the item containing the point \a x, \a y in content
++ coordinates. If there is no item at the point specified, null is returned.
++
++ \b Note: methods should only be called after the Component has completed.
++ */
++ QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const
++ {
++ Q_D(const QQuickPathView);
++ if (!d->isValid())
++ return nullptr;
++
++ for (QQuickItem *item : d->items) {
++ QPointF p = item->mapFromItem(this, QPointF(x, y));
++ if (item->contains(p))
++ return item;
++ }
++
++ return nullptr;
++ }
++
++ /*!
++ \qmlmethod Item QtQuick::PathView::itemAtIndex(int index)
++
++ Returns the item for \a index. If there is no item for that index, for example
++ because it has not been created yet, or because it has been panned out of
++ the visible area and removed from the cache, null is returned.
++
++ \b Note: this method should only be called after the Component has completed.
++ The returned value should also not be stored since it can turn to null
++ as soon as control goes out of the calling scope, if the view releases that item.
++
++ \since 5.13
++ */
++ QQuickItem *QQuickPathView::itemAtIndex(int index) const
++ {
++ Q_D(const QQuickPathView);
++ if (!d->isValid())
++ return nullptr;
++
++ for (QQuickItem *item : d->items) {
++ if (index == d->model->indexOf(item, nullptr))
++ return item;
++ }
++
++ return nullptr;
++ }
++
++ /*!
++ \internal
++
++ Returns a point in the path, that has the closest distance from \a point.
++ A value in the range 0-1 will be written to \a nearPercent if given, which
++ represents where on the path the \a point is closest to. \c 0 means the very
++ beginning of the path, and \c 1 means the very end.
++ */
++ QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
++ {
++ const auto pathLength = path->path().length();
++ qreal samples = qMin(pathLength / 5, qreal(500));
++ qreal res = pathLength / samples;
++
++ qreal mindist = 1e10; // big number
++ QPointF nearPoint = path->pointAtPercent(0);
++ qreal nearPc = 0;
++
++ // get rough pos
++ for (qreal i=1; i < samples; i++) {
++ QPointF pt = path->pointAtPercent(i/samples);
++ QPointF diff = pt - point;
++ qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
++ if (dist < mindist) {
++ nearPoint = pt;
++ nearPc = i;
++ mindist = dist;
++ }
++ }
++
++ // now refine
++ qreal approxPc = nearPc;
++ for (qreal i = approxPc-1; i < approxPc+1; i += 1/(2*res)) {
++ QPointF pt = path->pointAtPercent(i/samples);
++ QPointF diff = pt - point;
++ qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
++ if (dist < mindist) {
++ nearPoint = pt;
++ nearPc = i;
++ mindist = dist;
++ }
++ }
++
++ if (nearPercent)
++ *nearPercent = nearPc / samples;
++
++ return nearPoint;
++ }
++
++ void QQuickPathViewPrivate::addVelocitySample(qreal v)
++ {
++ velocityBuffer[velocityWritePos] = v;
++ velocityWritePos = (velocityWritePos + 1) % QML_FLICK_SAMPLEBUFFER;
++ if (velocitySamples < QML_FLICK_SAMPLEBUFFER)
++ ++velocitySamples;
++ qCDebug(lcPathView) << "instantaneous velocity" << v;
++ }
++
++ qreal QQuickPathViewPrivate::calcVelocity() const
++ {
++ qreal velocity = 0;
++ if (velocitySamples > QML_FLICK_DISCARDSAMPLES) {
++ const int count = velocitySamples - QML_FLICK_DISCARDSAMPLES;
++ // velocityBuffer is a ring; read oldest-first so DISCARDSAMPLES drops the
++ // newest (least reliable, taken as the finger lifts) samples.
++ const int oldest = (velocityWritePos - velocitySamples + QML_FLICK_SAMPLEBUFFER)
++ % QML_FLICK_SAMPLEBUFFER;
++ for (int i = 0; i < count; ++i) {
++ qreal v = velocityBuffer[(oldest + i) % QML_FLICK_SAMPLEBUFFER];
++ velocity += v;
++ }
++ velocity /= count;
++ qCDebug(lcPathView) << "average velocity" << velocity << "based on" << count << "samples";
++ }
++ return velocity;
++ }
++
++ qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event) const
++ {
++ if (0 != event->timestamp())
++ return qint64(event->timestamp());
++ return timer.elapsed();
++ }
++
++ void QQuickPathView::mousePressEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickPathView);
++ if (d->interactive) {
++ d->handleMousePressEvent(event);
++ event->accept();
++ } else {
++ QQuickItem::mousePressEvent(event);
++ }
++ }
++
++ void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
++ {
++ Q_Q(QQuickPathView);
++ if (!interactive || !items.size() || !model || !modelCount)
++ return;
++ velocitySamples = 0;
++ velocityWritePos = 0;
++ int idx = 0;
++ for (; idx < items.size(); ++idx) {
++ QQuickItem *item = items.at(idx);
++ if (item->contains(item->mapFromScene(event->scenePosition())))
++ break;
++ }
++ if (idx == items.size() && qFuzzyIsNull(dragMargin)) // didn't click on an item
++ return;
++
++ startPoint = pointNear(event->position(), &startPc);
++ startPos = event->position();
++ if (idx == items.size()) {
++ qreal distance = qAbs(event->position().x() - startPoint.x()) + qAbs(event->position().y() - startPoint.y());
++ if (distance > dragMargin)
++ return;
++ }
++
++ if (tl.isActive() && flicking && flickDuration && qreal(tl.time()) / flickDuration < 0.8) {
++ stealMouse = true; // If we've been flicked then steal the click.
++ q->grabMouse(); // grab it right now too, just to be sure (QTBUG-77173)
++ } else {
++ stealMouse = false;
++ }
++ q->setKeepMouseGrab(stealMouse);
++
++ timer.start();
++ lastPosTime = computeCurrentTime(event);
++ tl.clear();
++ }
++
++ void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickPathView);
++ if (d->interactive) {
++ d->handleMouseMoveEvent(event);
++ event->accept();
++ } else {
++ QQuickItem::mouseMoveEvent(event);
++ }
++ }
++
++ void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
++ {
++ Q_Q(QQuickPathView);
++ if (!interactive || !timer.isValid() || !model || !modelCount)
++ return;
++
++ qint64 currentTimestamp = computeCurrentTime(event);
++ qreal newPc;
++ QPointF pathPoint = pointNear(event->position(), &newPc);
++ if (!stealMouse) {
++ QPointF posDelta = event->position() - startPos;
++ if (QQuickDeliveryAgentPrivate::dragOverThreshold(posDelta.y(), Qt::YAxis, event) ||
++ QQuickDeliveryAgentPrivate::dragOverThreshold(posDelta.x(), Qt::XAxis, event)) {
++ // The touch has exceeded the threshold. If the movement along the path is close to the drag threshold
++ // then we'll assume that this gesture targets the PathView. This ensures PathView gesture grabbing
++ // is in sync with other items.
++ QPointF pathDelta = pathPoint - startPoint;
++ const int startDragDistance = QGuiApplication::styleHints()->startDragDistance();
++ if (qAbs(pathDelta.x()) > startDragDistance * 0.8
++ || qAbs(pathDelta.y()) > startDragDistance * 0.8) {
++ stealMouse = true;
++ q->setKeepMouseGrab(true);
++ }
++ }
++ } else {
++ moveReason = QQuickPathViewPrivate::Mouse;
++ int count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
++ qreal diff = (newPc - startPc)*count;
++ if (!qFuzzyIsNull(diff)) {
++ q->setOffset(offset + diff);
++
++ if (diff > modelCount/2)
++ diff -= modelCount;
++ else if (diff < -modelCount/2)
++ diff += modelCount;
++
++ qint64 elapsed = currentTimestamp - lastPosTime;
++ if (elapsed > 0)
++ addVelocitySample(diff / (qreal(elapsed) / 1000));
++ }
++ if (!moving) {
++ moving = true;
++ emit q->movingChanged();
++ emit q->movementStarted();
++ }
++ setDragging(true);
++ }
++ startPc = newPc;
++ lastPosTime = currentTimestamp;
++ }
++
++ void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
++ {
++ Q_D(QQuickPathView);
++ if (d->interactive) {
++ d->handleMouseReleaseEvent(event);
++ event->accept();
++ ungrabMouse();
++ } else {
++ QQuickItem::mouseReleaseEvent(event);
++ }
++ }
++
++ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *event)
++ {
++ Q_Q(QQuickPathView);
++ stealMouse = false;
++ q->setKeepMouseGrab(false);
++ setDragging(false);
++ if (!interactive || !timer.isValid() || !model || !modelCount) {
++ timer.invalidate();
++ if (!tl.isActive())
++ q->movementEnding();
++ return;
++ }
++
++ qreal velocity = calcVelocity();
++ qint64 elapsed = computeCurrentTime(event) - lastPosTime;
++ // Let the velocity linearly decay such that it becomes 0 if elapsed time > QML_FLICK_VELOCITY_DECAY_TIME
++ // The intention is that if you are flicking at some speed, then stop in one place for some time before releasing,
++ // the previous velocity is lost. (QTBUG-77173, QTBUG-59052)
++ velocity *= qreal(qMax(0LL, QML_FLICK_VELOCITY_DECAY_TIME - elapsed)) / QML_FLICK_VELOCITY_DECAY_TIME;
++ qCDebug(lcPathView) << "after elapsed time" << elapsed << "velocity decayed to" << velocity;
++ qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
++ const auto averageItemLength = path->path().length() / count;
++ qreal pixelVelocity = averageItemLength * velocity;
++ if (qAbs(pixelVelocity) > _q_MinimumFlickVelocity) {
++ if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
++ // limit velocity
++ qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
++ velocity = maxVel / averageItemLength;
++ }
++ // Calculate the distance to be travelled
++ qreal v2 = velocity*velocity;
++ qreal accel = deceleration/10;
++ qreal dist = 0;
++ if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
++ || snapMode != QQuickPathView::NoSnap)) {
++ if (snapMode == QQuickPathView::SnapOneItem) {
++ // encourage snapping one item in direction of motion
++ if (velocity > 0)
++ dist = qRound(0.5 + offset) - offset;
++ else
++ dist = qRound(0.5 - offset) + offset;
++ } else {
++ // + 0.25 to encourage moving at least one item in the flick direction
++ dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2) + 0.25));
++
++ // round to nearest item.
++ if (velocity > 0)
++ dist = qRound(dist + offset) - offset;
++ else
++ dist = qRound(dist - offset) + offset;
++ }
++ // Calculate accel required to stop on item boundary
++ if (dist <= 0) {
++ dist = 0;
++ accel = 0;
++ } else {
++ accel = v2 / (2 * qAbs(dist));
++ }
++ } else {
++ dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2)));
++ }
++ flickDuration = int(1000 * qAbs(velocity) / accel);
++ offsetAdj = 0;
++ moveOffset.setValue(offset);
++ tl.accel(moveOffset, velocity, accel, dist);
++ tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
++ if (!flicking) {
++ flicking = true;
++ emit q->flickingChanged();
++ emit q->flickStarted();
++ }
++ } else {
++ fixOffset();
++ }
++
++ timer.invalidate();
++ if (!tl.isActive())
++ q->movementEnding();
++ }
++
++ bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
++ {
++ Q_D(QQuickPathView);
++ if (!isVisible() || !d->interactive || !e->isPointerEvent())
++ return QQuickItem::childMouseEventFilter(i, e);
++
++ QPointerEvent *pe = static_cast<QPointerEvent *>(e);
++ if (QQuickDeliveryAgentPrivate::isMouseEvent(pe)) {
++ // The event is localized for the intended receiver (in the delegate, probably),
++ // but we need to look at position relative to the PathView itself.
++ const auto &point = pe->points().first();
++ QPointF localPos = mapFromScene(point.scenePosition());
++ QObject *grabber = pe->exclusiveGrabber(point);
++ QQuickPointerHandler *grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber);
++ QQuickItem *grabberItem = grabberHandler ? grabberHandler->parentItem() : qmlobject_cast<QQuickItem *>(grabber);
++ if (grabberItem == this && d->stealMouse) {
++ // we are already the grabber and we do want the mouse event to ourselves.
++ return true;
++ }
++
++ bool grabberDisabled = (grabberItem && !grabberItem->isEnabled()) || (grabberHandler && !grabberHandler->enabled());
++ bool stealThisEvent = d->stealMouse;
++ const bool mouseFromTouch = pe->pointingDevice()->type() == QPointingDevice::DeviceType::TouchScreen;
++ if ((stealThisEvent || contains(localPos)) && (!grabberItem ||
++ (!grabberItem->keepMouseGrab() && (!mouseFromTouch || !grabberItem->keepTouchGrab())) || grabberDisabled)) {
++ // Make a localized copy of the QMouseEvent.
++ QMutableSinglePointEvent localizedEvent(*static_cast<QMouseEvent *>(pe));
++ QMutableEventPoint::setPosition(localizedEvent.point(0), localPos);
++ localizedEvent.setAccepted(false);
++
++ switch (localizedEvent.type()) {
++ case QEvent::MouseMove:
++ d->handleMouseMoveEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
++ break;
++ case QEvent::MouseButtonPress:
++ d->handleMousePressEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
++ stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
++ break;
++ case QEvent::MouseButtonRelease:
++ d->handleMouseReleaseEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
++ break;
++ default:
++ break;
++ }
++
++ grabber = localizedEvent.exclusiveGrabber(localizedEvent.points().first());
++ grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber);
++ grabberItem = grabberHandler ? grabberHandler->parentItem() : qmlobject_cast<QQuickItem *>(grabber);
++ grabberDisabled = (grabberItem && !grabberItem->isEnabled()) || (grabberHandler && !grabberHandler->enabled());
++ if ((grabberItem && stealThisEvent && !grabberItem->keepMouseGrab() && grabber != this) || grabberDisabled)
++ pe->setExclusiveGrabber(point, this);
++
++ const bool filtered = stealThisEvent || grabberDisabled;
++ if (filtered)
++ pe->setAccepted(stealThisEvent && grabberItem == this && !grabberDisabled);
++
++ return filtered;
++ } else if (d->timer.isValid()) {
++ d->timer.invalidate();
++ d->fixOffset();
++ }
++ if (pe->type() == QEvent::MouseButtonRelease || (grabberItem && grabberItem->keepMouseGrab() && !grabberDisabled))
++ d->stealMouse = false;
++ return false;
++ }
++
++ return QQuickItem::childMouseEventFilter(i, e);
++ }
++
++ void QQuickPathView::mouseUngrabEvent()
++ {
++ Q_D(QQuickPathView);
++ if (d->stealMouse ||
++ (!d->flicking && d->snapMode != NoSnap && !qFuzzyCompare(qRound(d->offset), d->offset))) {
++ // if our mouse grab has been removed (probably by a Flickable),
++ // or if we should snap but haven't done it, fix our state
++ d->stealMouse = false;
++ setKeepMouseGrab(false);
++ d->timer.invalidate();
++ d->fixOffset();
++ d->setDragging(false);
++ if (!d->tl.isActive())
++ movementEnding();
++ }
++ }
++
++ void QQuickPathView::updatePolish()
++ {
++ QQuickItem::updatePolish();
++ refill();
++ }
++
++ static inline int currentIndexRemainder(int currentIndex, int modelCount) noexcept
++ {
++ if (currentIndex < 0)
++ return modelCount + currentIndex % modelCount;
++ else
++ return currentIndex % modelCount;
++ }
++
++ void QQuickPathView::componentComplete()
++ {
++ Q_D(QQuickPathView);
++ if (d->model && d->ownModel)
++ static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
++
++ QQuickItem::componentComplete();
++
++ if (d->model) {
++ d->modelCount = d->model->count();
++ if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex
++ d->offset = std::fmod(qreal(d->modelCount - currentIndexRemainder(d->currentIndex, d->modelCount)), qreal(d->modelCount));
++ }
++
++ d->createHighlight();
++ d->regenerate();
++ d->updateHighlight();
++ d->updateCurrent();
++
++ if (d->modelCount)
++ emit countChanged();
++ }
++
++ void QQuickPathView::refill()
++ {
++ Q_D(QQuickPathView);
++
++ if (d->inRefill) {
++ d->scheduleLayout();
++ return;
++ }
++
++ d->layoutScheduled = false;
++
++ if (!d->isValid() || !isComponentComplete())
++ return;
++
++ d->inRefill = true;
++
++ bool currentVisible = false;
++ int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
++
++ // first move existing items and remove items off path
++ qCDebug(lcItemViewDelegateLifecycle) << "currentIndex" << d->currentIndex << "offset" << d->offset;
++ QList<QQuickItem*>::iterator it = d->items.begin();
++ while (it != d->items.end()) {
++ QQuickItem *item = *it;
++ int idx = d->model->indexOf(item, nullptr);
++ qreal pos = d->positionOfIndex(idx);
++ if (lcItemViewDelegateLifecycle().isDebugEnabled()) {
++ QQuickText *text = qmlobject_cast<QQuickText*>(item);
++ if (text)
++ qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ": QQuickText" << text->objectName() << QStringView{text->text()}.left(40);
++ else
++ qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ":" << item;
++ }
++ if (pos < 1) {
++ d->updateItem(item, pos);
++ if (idx == d->currentIndex) {
++ currentVisible = true;
++ d->currentItemOffset = pos;
++ }
++ ++it;
++ } else {
++ d->updateItem(item, pos);
++ if (QQuickPathViewAttached *att = d->attached(item))
++ att->setOnPath(pos < 1);
++ if (!d->isInBound(pos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) {
++ qCDebug(lcItemViewDelegateLifecycle) << "release" << idx << "@" << pos << ", !isInBound: lower" << (d->mappedRange - d->mappedCache) << "upper" << (1 + d->mappedCache);
++ d->releaseItem(item);
++ it = d->items.erase(it);
++ } else {
++ ++it;
++ }
++ }
++ }
++
++ bool waiting = false;
++ if (d->modelCount) {
++ // add items as needed
++ if (d->items.size() < count+d->cacheSize) {
++ int endIdx = 0;
++ qreal endPos;
++ int startIdx = 0;
++ qreal startPos = 0;
++ const bool wasEmpty = d->items.isEmpty();
++ if (!wasEmpty) {
++ //Find the beginning and end, items may not be in sorted order
++ endPos = -1;
++ startPos = 2;
++
++ for (QQuickItem * item : std::as_const(d->items)) {
++ int idx = d->model->indexOf(item, nullptr);
++ qreal curPos = d->positionOfIndex(idx);
++ if (curPos > endPos) {
++ endPos = curPos;
++ endIdx = idx;
++ }
++
++ if (curPos < startPos) {
++ startPos = curPos;
++ startIdx = idx;
++ }
++ }
++ } else {
++ if (d->haveHighlightRange
++ && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
++ || d->snapMode != QQuickPathView::NoSnap))
++ startPos = d->highlightRangeStart;
++ // With no items, then "end" is just off the top so we populate via append
++ endIdx = (qRound(d->modelCount - d->offset) - 1) % d->modelCount;
++ endIdx = qMax(-1, endIdx); // endIdx shouldn't be smaller than -1
++ endPos = d->positionOfIndex(endIdx);
++ }
++ //Append
++ int idx = endIdx + 1;
++ if (idx >= d->modelCount)
++ idx = 0;
++ qreal nextPos = d->positionOfIndex(idx);
++ while ((d->isInBound(nextPos, endPos, 1 + d->mappedCache, false) || !d->items.size())
++ && d->items.size() < count + d->cacheSize) {
++ qCDebug(lcItemViewDelegateLifecycle) << "append" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
++ QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
++ if (!item) {
++ waiting = true;
++ break;
++ }
++ if (d->items.contains(item)) {
++ d->releaseItem(item);
++ break; //Otherwise we'd "re-add" it, and get confused
++ }
++ if (d->currentIndex == idx) {
++ currentVisible = true;
++ d->currentItemOffset = nextPos;
++ }
++ d->items.append(item);
++ d->updateItem(item, nextPos);
++ endPos = nextPos;
++ ++idx;
++ if (idx >= d->modelCount)
++ idx = 0;
++ nextPos = d->positionOfIndex(idx);
++ }
++
++ //Prepend
++ idx = (wasEmpty ? d->calcCurrentIndex() : startIdx) - 1;
++
++ if (idx < 0)
++ idx = d->modelCount - 1;
++ nextPos = d->positionOfIndex(idx);
++ while (!waiting && d->isInBound(nextPos, d->mappedRange - d->mappedCache, startPos)
++ && d->items.size() < count+d->cacheSize) {
++ qCDebug(lcItemViewDelegateLifecycle) << "prepend" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
++ QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
++ if (!item) {
++ waiting = true;
++ break;
++ }
++ if (d->items.contains(item)) {
++ d->releaseItem(item);
++ break; //Otherwise we'd "re-add" it, and get confused
++ }
++ if (d->currentIndex == idx) {
++ currentVisible = true;
++ d->currentItemOffset = nextPos;
++ }
++ d->items.prepend(item);
++ d->updateItem(item, nextPos);
++ startIdx = idx;
++ startPos = nextPos;
++ --idx;
++ if (idx < 0)
++ idx = d->modelCount - 1;
++ nextPos = d->positionOfIndex(idx);
++ }
++
++ // In rare cases, when jumping around with pathCount close to modelCount,
++ // new items appear in the middle. This more generic addition iteration handles this
++ // Since this is the rare case, we try append/prepend first and only do this if
++ // there are gaps still left to fill.
++ if (!waiting && d->items.size() < count+d->cacheSize) {
++ qCDebug(lcItemViewDelegateLifecycle) << "Checking for pathview middle inserts, items count was" << d->items.size();
++ idx = startIdx;
++ QQuickItem *lastItem = d->items.at(0);
++ // Walk every index once instead of stopping at endIdx, because a hole can
++ // also sit just outside the populated span. Leave as soon as the view is full.
++ for (int i = 0; i < d->modelCount && d->items.size() < count+d->cacheSize; ++i) {
++ nextPos = d->positionOfIndex(idx);
++ if (d->isInBound(nextPos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) {
++ //This gets the reference from the delegate model, and will not re-create
++ QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
++ if (!item) {
++ waiting = true;
++ break;
++ }
++
++ if (!d->items.contains(item)) { //We found a hole
++ qCDebug(lcItemViewDelegateLifecycle) << "middle insert" << idx << "@" << nextPos
++ << (d->currentIndex == idx ? "current" : "")
++ << "items count was" << d->items.size();
++ if (d->currentIndex == idx) {
++ currentVisible = true;
++ d->currentItemOffset = nextPos;
++ }
++ int lastListIdx = d->items.indexOf(lastItem);
++ d->items.insert(lastListIdx + 1, item);
++ d->updateItem(item, nextPos);
++ } else {
++ d->releaseItem(item);
++ }
++
++ lastItem = item;
++ }
++
++ ++idx;
++ if (idx >= d->modelCount)
++ idx = 0;
++ }
++ }
++ }
++ }
++
++ bool currentChanged = false;
++ if (!currentVisible) {
++ d->currentItemOffset = 1;
++ if (d->currentItem) {
++ d->updateItem(d->currentItem, 1);
++ } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
++ if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
++ currentChanged = true;
++ d->updateItem(d->currentItem, 1);
++ if (QQuickPathViewAttached *att = d->attached(d->currentItem))
++ att->setIsCurrentItem(true);
++ }
++ }
++ } else if (!waiting && !d->currentItem) {
++ if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
++ currentChanged = true;
++ d->currentItem->setFocus(true);
++ if (QQuickPathViewAttached *att = d->attached(d->currentItem))
++ att->setIsCurrentItem(true);
++ }
++ }
++
++ if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
++ d->updateItem(d->highlightItem, d->highlightRangeStart);
++ if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
++ att->setOnPath(true);
++ } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
++ d->updateItem(d->highlightItem, d->currentItemOffset);
++ if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
++ att->setOnPath(currentVisible);
++ }
++ for (QQuickItem *item : std::as_const(d->itemCache))
++ d->releaseItem(item);
++ d->itemCache.clear();
++
++ d->inRefill = false;
++ if (currentChanged)
++ emit currentItemChanged();
++ }
++
++ void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
++ {
++ Q_D(QQuickPathView);
++ if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
++ return;
++
++ if (reset) {
++ d->modelCount = d->model->count();
++ d->regenerate();
++ emit countChanged();
++ return;
++ }
++
++ if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
++ return;
++
++ const int modelCount = d->modelCount;
++ int moveId = -1;
++ int moveOffset = 0;
++ bool currentChanged = false;
++ bool changedOffset = false;
++ for (const QQmlChangeSet::Change &r : changeSet.removes()) {
++ if (moveId == -1 && d->currentIndex >= r.index + r.count) {
++ d->currentIndex -= r.count;
++ currentChanged = true;
++ } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
++ // current item has been removed.
++ if (r.isMove()) {
++ moveId = r.moveId;
++ moveOffset = d->currentIndex - r.index;
++ } else if (d->currentItem) {
++ if (QQuickPathViewAttached *att = d->attached(d->currentItem))
++ att->setIsCurrentItem(true);
++ d->releaseCurrentItem();
++ }
++ d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
++ currentChanged = true;
++ }
++
++ if (r.index > d->currentIndex) {
++ changedOffset = true;
++ d->offset -= r.count;
++ d->offsetAdj -= r.count;
++ }
++ d->modelCount -= r.count;
++ }
++ for (const QQmlChangeSet::Change &i : changeSet.inserts()) {
++ if (d->modelCount) {
++ if (moveId == -1 && i.index <= d->currentIndex) {
++ d->currentIndex += i.count;
++ currentChanged = true;
++ } else {
++ if (moveId != -1 && moveId == i.moveId) {
++ d->currentIndex = i.index + moveOffset;
++ currentChanged = true;
++ }
++ if (i.index > d->currentIndex) {
++ d->offset += i.count;
++ d->offsetAdj += i.count;
++ changedOffset = true;
++ }
++ }
++ }
++ d->modelCount += i.count;
++ }
++
++ d->offset = std::fmod(d->offset, qreal(d->modelCount));
++ if (d->offset < 0)
++ d->offset += d->modelCount;
++ if (d->currentIndex == -1)
++ d->currentIndex = d->calcCurrentIndex();
++
++ d->itemCache += d->items;
++ d->items.clear();
++
++ if (!d->modelCount) {
++ for (QQuickItem * item : std::as_const(d->itemCache))
++ d->releaseItem(item);
++ d->itemCache.clear();
++ d->offset = 0;
++ changedOffset = true;
++ d->tl.reset(d->moveOffset);
++ } else {
++ if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
++ d->offset = std::fmod(qreal(d->modelCount - d->currentIndex), qreal(d->modelCount));
++ changedOffset = true;
++ }
++ d->updateMappedRange();
++ d->scheduleLayout();
++ }
++ if (changedOffset)
++ emit offsetChanged();
++ if (currentChanged)
++ emit currentIndexChanged();
++ if (d->modelCount != modelCount)
++ emit countChanged();
++ }
++
++ void QQuickPathView::destroyingItem(QObject *item)
++ {
++ Q_UNUSED(item);
++ }
++
++ void QQuickPathView::ticked()
++ {
++ Q_D(QQuickPathView);
++ d->updateCurrent();
++ }
++
++ void QQuickPathView::movementEnding()
++ {
++ Q_D(QQuickPathView);
++ if (d->flicking) {
++ d->flicking = false;
++ emit flickingChanged();
++ emit flickEnded();
++ }
++ if (d->moving && !d->stealMouse) {
++ d->moving = false;
++ emit movingChanged();
++ emit movementEnded();
++ }
++ d->moveDirection = d->movementDirection;
++ }
++
++ // find the item closest to the snap position
++ int QQuickPathViewPrivate::calcCurrentIndex()
++ {
++ int current = 0;
++ if (modelCount && model && items.size()) {
++ offset = std::fmod(offset, qreal(modelCount));
++ if (offset < 0)
++ offset += modelCount;
++ current = qRound(qAbs(std::fmod(modelCount - offset, qreal(modelCount))));
++ current = current % modelCount;
++ }
++
++ return current;
++ }
++
++ void QQuickPathViewPrivate::createCurrentItem()
++ {
++ if (requestedIndex != -1)
++ return;
++
++ bool inItems = false;
++ for (QQuickItem *item : std::as_const(items)) {
++ if (model->indexOf(item, nullptr) == currentIndex) {
++ inItems = true;
++ break;
++ }
++ }
++
++ if (inItems) {
++ if ((currentItem = getItem(currentIndex, currentIndex))) {
++ currentItem->setFocus(true);
++ if (QQuickPathViewAttached *att = attached(currentItem))
++ att->setIsCurrentItem(true);
++ }
++ } else if (currentIndex >= 0 && currentIndex < modelCount) {
++ if ((currentItem = getItem(currentIndex, currentIndex))) {
++ updateItem(currentItem, 1);
++ if (QQuickPathViewAttached *att = attached(currentItem))
++ att->setIsCurrentItem(true);
++ }
++ }
++ }
++
++ void QQuickPathViewPrivate::updateCurrent()
++ {
++ Q_Q(QQuickPathView);
++ if (moveReason == SetIndex)
++ return;
++ if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
++ return;
++
++ int idx = calcCurrentIndex();
++ if (model && (idx != currentIndex || !currentItem)) {
++ if (currentItem) {
++ if (QQuickPathViewAttached *att = attached(currentItem))
++ att->setIsCurrentItem(false);
++ releaseCurrentItem();
++ }
++ int oldCurrentIndex = currentIndex;
++ currentIndex = idx;
++ currentItem = nullptr;
++ createCurrentItem();
++ if (oldCurrentIndex != currentIndex)
++ emit q->currentIndexChanged();
++ emit q->currentItemChanged();
++ }
++ }
++
++ void QQuickPathViewPrivate::fixOffsetCallback(void *d)
++ {
++ static_cast<QQuickPathViewPrivate *>(d)->fixOffset();
++ }
++
++ void QQuickPathViewPrivate::fixOffset()
++ {
++ Q_Q(QQuickPathView);
++ if (model && items.size()) {
++ if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
++ || snapMode != QQuickPathView::NoSnap)) {
++ int curr = calcCurrentIndex();
++ if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
++ q->setCurrentIndex(curr);
++ else
++ snapToIndex(curr, Other);
++ }
++ }
++ }
++
++ void QQuickPathViewPrivate::snapToIndex(int index, MovementReason reason)
++ {
++ if (!model || modelCount <= 0)
++ return;
++
++ qreal targetOffset = std::fmod(qreal(modelCount - index), qreal(modelCount));
++ moveReason = reason;
++ offsetAdj = 0;
++ tl.reset(moveOffset);
++ moveOffset.setValue(offset);
++
++ const int duration = highlightMoveDuration;
++
++ const qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
++ const qreal averageItemLength = path->path().length() / count;
++ const qreal threshold = 0.5 / averageItemLength; // if we are within .5 px, we want to immediately assign rather than animate
++
++ if (!duration || qAbs(offset - targetOffset) < threshold || (qFuzzyIsNull(targetOffset) && qAbs(modelCount - offset) < threshold)) {
++ tl.set(moveOffset, targetOffset);
++ } else if (moveDirection == QQuickPathView::Positive || (moveDirection == QQuickPathView::Shortest && targetOffset - offset > modelCount/2)) {
++ qreal distance = modelCount - targetOffset + offset;
++ if (targetOffset > moveOffset) {
++ tl.move(moveOffset, 0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
++ tl.set(moveOffset, modelCount);
++ tl.move(moveOffset, targetOffset, QEasingCurve(qFuzzyIsNull(offset) ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
++ } else {
++ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
++ }
++ } else if (moveDirection == QQuickPathView::Negative || targetOffset - offset <= -modelCount/2) {
++ qreal distance = modelCount - offset + targetOffset;
++ if (targetOffset < moveOffset) {
++ tl.move(moveOffset, modelCount, QEasingCurve(qFuzzyIsNull(targetOffset) ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
++ tl.set(moveOffset, 0);
++ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
++ } else {
++ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
++ }
++ } else {
++ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
++ }
++ }
++
++ QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
++ {
++ return new QQuickPathViewAttached(obj);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickpathview_p.cpp"