diff options
Diffstat (limited to 'src/quick/items/qquickitemview.cpp')
-rw-r--r-- | src/quick/items/qquickitemview.cpp | 458 |
1 files changed, 289 insertions, 169 deletions
diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index 21d644dad5..1518a484d3 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQuick module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 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 #include "qquickitemview_p_p.h" #include "qquickitemviewfxitem_p_p.h" @@ -57,8 +21,6 @@ FxViewItem::FxViewItem(QQuickItem *i, QQuickItemView *v, bool own, QQuickItemVie , view(v) , attached(attached) { - if (attached) // can be null for default components (see createComponentItem) - attached->setView(view); } QQuickItemViewChangeSet::QQuickItemViewChangeSet() @@ -162,7 +124,7 @@ QQuickItemView::QQuickItemView(QQuickFlickablePrivate &dd, QQuickItem *parent) QQuickItemView::~QQuickItemView() { Q_D(QQuickItemView); - d->clear(); + d->clear(true); if (d->ownModel) delete d->model; delete d->header; @@ -197,6 +159,10 @@ void QQuickItemView::setModel(const QVariant &m) disconnect(d->model, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); disconnect(d->model, SIGNAL(createdItem(int,QObject*)), this, SLOT(createdItem(int,QObject*))); disconnect(d->model, SIGNAL(destroyingItem(QObject*)), this, SLOT(destroyingItem(QObject*))); + if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->model)) { + disconnect(delegateModel, SIGNAL(itemPooled(int,QObject*)), this, SLOT(onItemPooled(int,QObject*))); + disconnect(delegateModel, SIGNAL(itemReused(int,QObject*)), this, SLOT(onItemReused(int,QObject*))); + } } QQmlInstanceModel *oldModel = d->model; @@ -232,24 +198,35 @@ void QQuickItemView::setModel(const QVariant &m) connect(d->model, SIGNAL(createdItem(int,QObject*)), this, SLOT(createdItem(int,QObject*))); connect(d->model, SIGNAL(initItem(int,QObject*)), this, SLOT(initItem(int,QObject*))); connect(d->model, SIGNAL(destroyingItem(QObject*)), this, SLOT(destroyingItem(QObject*))); + if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->model)) { + connect(delegateModel, SIGNAL(itemPooled(int,QObject*)), this, SLOT(onItemPooled(int,QObject*))); + connect(delegateModel, SIGNAL(itemReused(int,QObject*)), this, SLOT(onItemReused(int,QObject*))); + } if (isComponentComplete()) { d->updateSectionCriteria(); d->refill(); - d->currentIndex = -1; + /* Setting currentIndex to -2 ensures that we always enter the "currentIndex changed" + code path in setCurrentIndex, updating bindings depending on currentIndex.*/ + d->currentIndex = -2; setCurrentIndex(d->model->count() > 0 ? 0 : -1); d->updateViewport(); +#if QT_CONFIG(quick_viewtransitions) if (d->transitioner && d->transitioner->populateTransition) { d->transitioner->setPopulateTransitionEnabled(true); d->forceLayoutPolish(); } +#endif } connect(d->model, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(modelUpdated(QQmlChangeSet,bool))); + if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) + QObjectPrivate::connect(dataModel, &QQmlDelegateModel::delegateChanged, d, &QQuickItemViewPrivate::applyDelegateChange); emit countChanged(); } emit modelChanged(); + d->moveReason = QQuickItemViewPrivate::Other; } QQmlComponent *QQuickItemView::delegate() const @@ -277,22 +254,6 @@ void QQuickItemView::setDelegate(QQmlComponent *delegate) if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) { int oldCount = dataModel->count(); dataModel->setDelegate(delegate); - if (isComponentComplete()) { - d->releaseVisibleItems(); - d->releaseItem(d->currentItem); - d->currentItem = nullptr; - d->updateSectionCriteria(); - d->refill(); - d->moveReason = QQuickItemViewPrivate::SetIndex; - d->updateCurrent(d->currentIndex); - if (d->highlight && d->currentItem) { - if (d->autoHighlight) - d->resetHighlightPosition(); - d->updateTrackedItem(); - } - d->moveReason = QQuickItemViewPrivate::Other; - d->updateViewport(); - } if (oldCount != dataModel->count()) emit countChanged(); } @@ -599,6 +560,7 @@ void QQuickItemView::setHighlightRangeMode(HighlightRangeMode mode) d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; if (isComponentComplete()) { d->updateViewport(); + d->moveReason = QQuickItemViewPrivate::Other; d->fixupPosition(); } emit highlightRangeModeChanged(); @@ -621,8 +583,10 @@ void QQuickItemView::setPreferredHighlightBegin(qreal start) d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; if (isComponentComplete()) { d->updateViewport(); - if (!isMoving() && !isFlicking()) + if (!isMoving() && !isFlicking()) { + d->moveReason = QQuickItemViewPrivate::Other; d->fixupPosition(); + } } emit preferredHighlightBeginChanged(); } @@ -636,8 +600,10 @@ void QQuickItemView::resetPreferredHighlightBegin() d->highlightRangeStart = 0; if (isComponentComplete()) { d->updateViewport(); - if (!isMoving() && !isFlicking()) + if (!isMoving() && !isFlicking()) { + d->moveReason = QQuickItemViewPrivate::Other; d->fixupPosition(); + } } emit preferredHighlightBeginChanged(); } @@ -658,8 +624,10 @@ void QQuickItemView::setPreferredHighlightEnd(qreal end) d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; if (isComponentComplete()) { d->updateViewport(); - if (!isMoving() && !isFlicking()) + if (!isMoving() && !isFlicking()) { + d->moveReason = QQuickItemViewPrivate::Other; d->fixupPosition(); + } } emit preferredHighlightEndChanged(); } @@ -673,8 +641,10 @@ void QQuickItemView::resetPreferredHighlightEnd() d->highlightRangeEnd = 0; if (isComponentComplete()) { d->updateViewport(); - if (!isMoving() && !isFlicking()) + if (!isMoving() && !isFlicking()) { + d->moveReason = QQuickItemViewPrivate::Other; d->fixupPosition(); + } } emit preferredHighlightEndChanged(); } @@ -694,6 +664,29 @@ void QQuickItemView::setHighlightMoveDuration(int duration) } } +bool QQuickItemView::reuseItems() const +{ + return bool(d_func()->reusableFlag == QQmlDelegateModel::Reusable); +} + +void QQuickItemView::setReuseItems(bool reuse) +{ + Q_D(QQuickItemView); + if (reuseItems() == reuse) + return; + + d->reusableFlag = reuse ? QQmlDelegateModel::Reusable : QQmlDelegateModel::NotReusable; + + if (!reuse && d->model) { + // When we're told to not reuse items, we + // immediately, as documented, drain the pool. + d->model->drainReusableItemsPool(0); + } + + emit reuseItemsChanged(); +} + +#if QT_CONFIG(quick_viewtransitions) QQuickTransition *QQuickItemView::populateTransition() const { Q_D(const QQuickItemView); @@ -821,6 +814,7 @@ void QQuickItemView::setDisplacedTransition(QQuickTransition *transition) emit displacedTransitionChanged(); } } +#endif void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode) { @@ -848,7 +842,7 @@ void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode) setPosition(qMin(itemPos, maxExtent)); // now release the reference to all the old visible items. for (FxViewItem *item : oldVisible) - releaseItem(item); + releaseItem(item, reusableFlag); item = visibleItem(idx); } if (item) { @@ -951,6 +945,13 @@ QQuickItem *QQuickItemView::itemAt(qreal x, qreal y) const return item ? item->item : nullptr; } +QQuickItem *QQuickItemView::itemAtIndex(int index) const +{ + Q_D(const QQuickItemView); + const FxViewItem *item = d->visibleItem(index); + return item ? item->item : nullptr; +} + void QQuickItemView::forceLayout() { Q_D(QQuickItemView); @@ -967,7 +968,7 @@ void QQuickItemViewPrivate::applyPendingChanges() int QQuickItemViewPrivate::findMoveKeyIndex(QQmlChangeSet::MoveKey key, const QVector<QQmlChangeSet::Change> &changes) const { - for (int i=0; i<changes.count(); i++) { + for (int i=0; i<changes.size(); i++) { for (int j=changes[i].index; j<changes[i].index + changes[i].count; j++) { if (changes[i].moveKey(j) == key) return j; @@ -1082,11 +1083,28 @@ qreal QQuickItemViewPrivate::calculatedMaxExtent() const return maxExtent; } +void QQuickItemViewPrivate::applyDelegateChange() +{ + releaseVisibleItems(QQmlDelegateModel::NotReusable); + releaseCurrentItem(QQmlDelegateModel::NotReusable); + updateSectionCriteria(); + refill(); + moveReason = QQuickItemViewPrivate::SetIndex; + updateCurrent(currentIndex); + if (highlight && currentItem) { + if (autoHighlight) + resetHighlightPosition(); + updateTrackedItem(); + } + moveReason = QQuickItemViewPrivate::Other; + updateViewport(); +} + // for debugging only void QQuickItemViewPrivate::checkVisible() const { int skip = 0; - for (int i = 0; i < visibleItems.count(); ++i) { + for (int i = 0; i < visibleItems.size(); ++i) { FxViewItem *item = visibleItems.at(i); if (item->index == -1) { ++skip; @@ -1133,11 +1151,13 @@ void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometry // don't allow item movement transitions to trigger a re-layout and // start new transitions bool prevInLayout = inLayout; +#if QT_CONFIG(quick_viewtransitions) if (!inLayout) { FxViewItem *actualItem = transitioner ? visibleItem(currentIndex) : nullptr; if (actualItem && actualItem->transitionRunning()) inLayout = true; } +#endif updateHighlight(); inLayout = prevInLayout; } @@ -1150,17 +1170,20 @@ void QQuickItemView::destroyRemoved() { Q_D(QQuickItemView); +#if QT_CONFIG(quick_viewtransitions) bool hasRemoveTransition = false; bool hasRemoveTransitionAsTarget = false; if (d->transitioner) { hasRemoveTransition = d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false); hasRemoveTransitionAsTarget = d->transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, true); } +#endif for (QList<FxViewItem*>::Iterator it = d->visibleItems.begin(); it != d->visibleItems.end();) { FxViewItem *item = *it; if (item->index == -1 && (!item->attached || item->attached->delayRemove() == false)) { +#if QT_CONFIG(quick_viewtransitions) if (hasRemoveTransitionAsTarget) { // don't remove from visibleItems until next layout() d->runDelayedRemoveTransition = true; @@ -1169,9 +1192,12 @@ void QQuickItemView::destroyRemoved() } else { if (hasRemoveTransition) d->runDelayedRemoveTransition = true; - d->releaseItem(item); +#endif + d->releaseItem(item, d->reusableFlag); it = d->visibleItems.erase(it); +#if QT_CONFIG(quick_viewtransitions) } +#endif } else { ++it; } @@ -1186,8 +1212,10 @@ void QQuickItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset) Q_D(QQuickItemView); if (reset) { cancelFlick(); +#if QT_CONFIG(quick_viewtransitions) if (d->transitioner) d->transitioner->setPopulateTransitionEnabled(true); +#endif d->moveReason = QQuickItemViewPrivate::SetIndex; d->regenerate(); if (d->highlight && d->currentItem) { @@ -1197,8 +1225,10 @@ void QQuickItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset) } d->moveReason = QQuickItemViewPrivate::Other; emit countChanged(); +#if QT_CONFIG(quick_viewtransitions) if (d->transitioner && d->transitioner->populateTransition) d->forceLayoutPolish(); +#endif } else { if (d->inLayout) { d->bufferedChanges.prepare(d->currentIndex, d->itemCount); @@ -1236,7 +1266,9 @@ void QQuickItemView::trackedPositionChanged() return; } - if (d->moveReason == QQuickItemViewPrivate::SetIndex) { + const bool needMoveToTrackHighlight = d->autoHighlight || d->highlightRange != NoHighlightRange; + + if (d->moveReason == QQuickItemViewPrivate::SetIndex && needMoveToTrackHighlight) { qreal trackedPos = d->trackedItem->position(); qreal trackedSize = d->trackedItem->size(); qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position(); @@ -1311,13 +1343,13 @@ void QQuickItemView::trackedPositionChanged() } } -void QQuickItemView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +void QQuickItemView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QQuickItemView); d->markExtentsDirty(); if (isComponentComplete() && (d->isValid() || !d->visibleItems.isEmpty())) d->forceLayoutPolish(); - QQuickFlickable::geometryChanged(newGeometry, oldGeometry); + QQuickFlickable::geometryChange(newGeometry, oldGeometry); } qreal QQuickItemView::minYExtent() const @@ -1338,7 +1370,7 @@ qreal QQuickItemView::maxYExtent() const { Q_D(const QQuickItemView); if (d->layoutOrientation() == Qt::Horizontal) - return height(); + return QQuickFlickable::maxYExtent(); if (d->vData.maxExtentDirty) { d->maxExtent = d->maxExtentForAxis(d->vData, false); @@ -1366,7 +1398,7 @@ qreal QQuickItemView::maxXExtent() const { Q_D(const QQuickItemView); if (d->layoutOrientation() == Qt::Vertical) - return width(); + return QQuickFlickable::maxXExtent(); if (d->hData.maxExtentDirty) { d->maxExtent = d->maxExtentForAxis(d->hData, true); @@ -1434,8 +1466,10 @@ void QQuickItemView::componentComplete() d->updateFooter(); d->updateViewport(); d->setPosition(d->contentStartOffset()); +#if QT_CONFIG(quick_viewtransitions) if (d->transitioner) d->transitioner->setPopulateTransitionEnabled(true); +#endif if (d->isValid()) { d->refill(); @@ -1463,7 +1497,6 @@ QQuickItemViewPrivate::QQuickItemViewPrivate() , buffer(QML_VIEW_DEFAULTCACHEBUFFER), bufferMode(BufferBefore | BufferAfter) , displayMarginBeginning(0), displayMarginEnd(0) , layoutDirection(Qt::LeftToRight), verticalLayoutDirection(QQuickItemView::TopToBottom) - , moveReason(Other) , visibleIndex(0) , currentIndex(-1), currentItem(nullptr) , trackedItem(nullptr), requestedIndex(-1) @@ -1472,7 +1505,9 @@ QQuickItemViewPrivate::QQuickItemViewPrivate() , highlightRangeStart(0), highlightRangeEnd(0) , highlightMoveDuration(150) , headerComponent(nullptr), header(nullptr), footerComponent(nullptr), footer(nullptr) +#if QT_CONFIG(quick_viewtransitions) , transitioner(nullptr) +#endif , minExtent(0), maxExtent(0) , ownModel(false), wrap(false) , keyNavigationEnabled(true) @@ -1480,7 +1515,10 @@ QQuickItemViewPrivate::QQuickItemViewPrivate() , inLayout(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false) , haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false) , fillCacheBuffer(false), inRequest(false) - , runDelayedRemoveTransition(false), delegateValidated(false) +#if QT_CONFIG(quick_viewtransitions) + , runDelayedRemoveTransition(false) +#endif + , delegateValidated(false), isClearing(false) { bufferPause.addAnimationChangeListener(this, QAbstractAnimationJob::Completion); bufferPause.setLoopCount(1); @@ -1489,9 +1527,11 @@ QQuickItemViewPrivate::QQuickItemViewPrivate() QQuickItemViewPrivate::~QQuickItemViewPrivate() { +#if QT_CONFIG(quick_viewtransitions) if (transitioner) transitioner->setChangeListener(nullptr); delete transitioner; +#endif } bool QQuickItemViewPrivate::isValid() const @@ -1549,8 +1589,8 @@ int QQuickItemViewPrivate::findLastVisibleIndex(int defaultValue) const } FxViewItem *QQuickItemViewPrivate::visibleItem(int modelIndex) const { - if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) { - for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) { + if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.size()) { + for (int i = modelIndex - visibleIndex; i < visibleItems.size(); ++i) { FxViewItem *item = visibleItems.at(i); if (item->index == modelIndex) return item; @@ -1559,15 +1599,13 @@ FxViewItem *QQuickItemViewPrivate::visibleItem(int modelIndex) const { return nullptr; } -// should rename to firstItemInView() to avoid confusion with other "*visible*" methods -// that don't look at the view position and size -FxViewItem *QQuickItemViewPrivate::firstVisibleItem() const { +FxViewItem *QQuickItemViewPrivate::firstItemInView() const { const qreal pos = isContentFlowReversed() ? -position()-size() : position(); for (FxViewItem *item : visibleItems) { if (item->index != -1 && item->endPosition() > pos) return item; } - return visibleItems.count() ? visibleItems.first() : 0; + return visibleItems.size() ? visibleItems.first() : 0; } int QQuickItemViewPrivate::findLastIndexInView() const @@ -1586,9 +1624,9 @@ int QQuickItemViewPrivate::findLastIndexInView() const // e.g. doing a removal animation int QQuickItemViewPrivate::mapFromModel(int modelIndex) const { - if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.count()) + if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.size()) return -1; - for (int i = 0; i < visibleItems.count(); ++i) { + for (int i = 0; i < visibleItems.size(); ++i) { FxViewItem *item = visibleItems.at(i); if (item->index == modelIndex) return i; @@ -1615,8 +1653,7 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex) if (currentItem) { if (currentItem->attached) currentItem->attached->setIsCurrentItem(false); - releaseItem(currentItem); - currentItem = nullptr; + releaseCurrentItem(reusableFlag); currentIndex = modelIndex; emit q->currentIndexChanged(); emit q->currentItemChanged(); @@ -1652,31 +1689,36 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex) if (oldCurrentItem != currentItem && (!oldCurrentItem || !currentItem || oldCurrentItem->item != currentItem->item)) emit q->currentItemChanged(); - releaseItem(oldCurrentItem); + releaseItem(oldCurrentItem, reusableFlag); } -void QQuickItemViewPrivate::clear() +void QQuickItemViewPrivate::clear(bool onDestruction) { Q_Q(QQuickItemView); + + isClearing = true; + auto cleanup = qScopeGuard([this] { isClearing = false; }); + currentChanges.reset(); bufferedChanges.reset(); timeline.clear(); - releaseVisibleItems(); + releaseVisibleItems(QQmlInstanceModel::NotReusable); visibleIndex = 0; - for (FxViewItem *item : qAsConst(releasePendingTransition)) { +#if QT_CONFIG(quick_viewtransitions) + for (FxViewItem *item : std::as_const(releasePendingTransition)) { item->releaseAfterTransition = false; - releaseItem(item); + releaseItem(item, QQmlInstanceModel::NotReusable); } releasePendingTransition.clear(); +#endif - auto oldCurrentItem = currentItem; - releaseItem(currentItem); - currentItem = nullptr; - if (oldCurrentItem) + const bool hadCurrentItem = currentItem != nullptr; + releaseCurrentItem(QQmlDelegateModel::NotReusable); + if (hadCurrentItem) emit q->currentItemChanged(); - createHighlight(); + createHighlight(onDestruction); trackedItem = nullptr; if (requestedIndex >= 0) { @@ -1719,6 +1761,8 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) Q_Q(QQuickItemView); if (!model || !model->isValid() || !q->isComponentComplete()) return; + if (q->size().isEmpty() && visibleItems.isEmpty()) + return; if (!model->count()) { updateHeader(); updateFooter(); @@ -1728,10 +1772,10 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) do { bufferPause.stop(); - if (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges()) { + if (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges() || currentChanges.active) { currentChanges.reset(); bufferedChanges.reset(); - releaseVisibleItems(); + releaseVisibleItems(reusableFlag); } int prevCount = itemCount; @@ -1742,7 +1786,6 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) qreal fillTo = to; bool added = addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, false); - bool removed = removeNonVisibleItems(bufferFrom, bufferTo); if (requestedIndex == -1 && buffer && bufferMode != NoBuffer) { if (added) { @@ -1758,6 +1801,8 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) } } + bool removed = removeNonVisibleItems(bufferFrom, bufferTo); + if (added || removed) { markExtentsDirty(); updateBeginningEnd(); @@ -1770,6 +1815,7 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) if (prevCount != itemCount) emit q->countChanged(); } while (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges()); + storeFirstVisibleItemPosition(); } void QQuickItemViewPrivate::regenerate(bool orientationChanged) @@ -1814,23 +1860,27 @@ void QQuickItemViewPrivate::layout() // viewBounds contains bounds before any add/remove/move operation to the view QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height()); - if (!isValid() && !visibleItems.count()) { + if (!isValid() && !visibleItems.size()) { clear(); setPosition(contentStartOffset()); updateViewport(); +#if QT_CONFIG(quick_viewtransitions) if (transitioner) transitioner->setPopulateTransitionEnabled(false); +#endif inLayout = false; return; } +#if QT_CONFIG(quick_viewtransitions) if (runDelayedRemoveTransition && transitioner && transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false)) { // assume that any items moving now are moving due to the remove - if they schedule // a different transition, that will override this one anyway - for (int i=0; i<visibleItems.count(); i++) + for (int i=0; i<visibleItems.size(); i++) visibleItems[i]->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false); } +#endif ChangeResult insertionPosChanges; ChangeResult removalPosChanges; @@ -1844,20 +1894,25 @@ void QQuickItemViewPrivate::layout() } forceLayout = false; +#if QT_CONFIG(quick_viewtransitions) if (transitioner && transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) { // Give the view one more chance to refill itself, // in case its size is changed such that more delegates become visible after component completed refill(); - for (FxViewItem *item : qAsConst(visibleItems)) { + for (FxViewItem *item : std::as_const(visibleItems)) { if (!item->transitionScheduledOrRunning()) item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::PopulateTransition, true); } } +#endif updateSections(); layoutVisibleItems(); + storeFirstVisibleItemPosition(); +#if QT_CONFIG(quick_viewtransitions) int lastIndexInView = findLastIndexInView(); +#endif refill(); markExtentsDirty(); updateHighlight(); @@ -1872,6 +1927,7 @@ void QQuickItemViewPrivate::layout() updateViewport(); updateUnrequestedPositions(); +#if QT_CONFIG(quick_viewtransitions) if (transitioner) { // items added in the last refill() may need to be transitioned in - e.g. a remove // causes items to slide up into view @@ -1883,33 +1939,43 @@ void QQuickItemViewPrivate::layout() prepareVisibleItemTransitions(); - for (auto it = releasePendingTransition.begin(); it != releasePendingTransition.end(); ) { - auto old_count = releasePendingTransition.count(); - auto success = prepareNonVisibleItemTransition(*it, viewBounds); - // prepareNonVisibleItemTransition() may invalidate iterators while in fast flicking - // invisible animating items are kicked in or out the viewPort - // use old_count to test if the abrupt erasure occurs - if (old_count > releasePendingTransition.count()) { + // We cannot use iterators here as erasing from a container invalidates them. + for (int i = 0, count = releasePendingTransition.size(); i < count;) { + auto success = prepareNonVisibleItemTransition(releasePendingTransition[i], viewBounds); + // prepareNonVisibleItemTransition() may remove items while in fast flicking. + // Invisible animating items are kicked in or out the viewPort. + // Recheck count to test if the item got removed. In that case the same index points + // to a different item now. + const int old_count = count; + count = releasePendingTransition.size(); + if (old_count > count) continue; - } + if (!success) { - releaseItem(*it); - it = releasePendingTransition.erase(it); - continue; + releaseItem(releasePendingTransition[i], reusableFlag); + releasePendingTransition.remove(i); + --count; + } else { + ++i; } - ++it; } - for (int i=0; i<visibleItems.count(); i++) + for (int i=0; i<visibleItems.size(); i++) visibleItems[i]->startTransition(transitioner); - for (int i=0; i<releasePendingTransition.count(); i++) + for (int i=0; i<releasePendingTransition.size(); i++) releasePendingTransition[i]->startTransition(transitioner); transitioner->setPopulateTransitionEnabled(false); transitioner->resetTargetLists(); } +#endif + + if (!currentItem) + updateCurrent(currentIndex); +#if QT_CONFIG(quick_viewtransitions) runDelayedRemoveTransition = false; +#endif inLayout = false; } @@ -1926,29 +1992,29 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult updateUnrequestedIndexes(); - FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? *visibleItems.constBegin() : 0; + FxViewItem *prevVisibleItemsFirst = visibleItems.size() ? *visibleItems.constBegin() : nullptr; int prevItemCount = itemCount; - int prevVisibleItemsCount = visibleItems.count(); + int prevVisibleItemsCount = visibleItems.size(); bool visibleAffected = false; bool viewportChanged = !currentChanges.pendingChanges.removes().isEmpty() || !currentChanges.pendingChanges.inserts().isEmpty(); - FxViewItem *prevFirstVisible = firstVisibleItem(); - QQmlNullableValue<qreal> prevViewPos; - int prevFirstVisibleIndex = -1; - if (prevFirstVisible) { - prevViewPos = prevFirstVisible->position(); - prevFirstVisibleIndex = prevFirstVisible->index; + FxViewItem *prevFirstItemInView = firstItemInView(); + QQmlNullableValue<qreal> prevFirstItemInViewPos; + int prevFirstItemInViewIndex = -1; + if (prevFirstItemInView) { + prevFirstItemInViewPos = prevFirstItemInView->position(); + prevFirstItemInViewIndex = prevFirstItemInView->index; } - qreal prevVisibleItemsFirstPos = visibleItems.count() ? visibleItems.constFirst()->position() : 0.0; + qreal prevVisibleItemsFirstPos = visibleItems.size() ? firstVisibleItemPosition : 0.0; - totalInsertionResult->visiblePos = prevViewPos; - totalRemovalResult->visiblePos = prevViewPos; + totalInsertionResult->visiblePos = prevFirstItemInViewPos; + totalRemovalResult->visiblePos = prevFirstItemInViewPos; const QVector<QQmlChangeSet::Change> &removals = currentChanges.pendingChanges.removes(); const QVector<QQmlChangeSet::Change> &insertions = currentChanges.pendingChanges.inserts(); - ChangeResult insertionResult(prevViewPos); - ChangeResult removalResult(prevViewPos); + ChangeResult insertionResult(prevFirstItemInViewPos); + ChangeResult removalResult(prevFirstItemInViewPos); int removedCount = 0; for (const QQmlChangeSet::Change &r : removals) { @@ -1957,7 +2023,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult visibleAffected = true; if (!visibleAffected && needsRefillForAddedOrRemovedIndex(r.index)) visibleAffected = true; - const int correctedFirstVisibleIndex = prevFirstVisibleIndex - removalResult.countChangeBeforeVisible; + const int correctedFirstVisibleIndex = prevFirstItemInViewIndex - removalResult.countChangeBeforeVisible; if (correctedFirstVisibleIndex >= 0 && r.index < correctedFirstVisibleIndex) { if (r.index + r.count < correctedFirstVisibleIndex) removalResult.countChangeBeforeVisible += r.count; @@ -1965,6 +2031,7 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult removalResult.countChangeBeforeVisible += (correctedFirstVisibleIndex - r.index); } } +#if QT_CONFIG(quick_viewtransitions) if (runDelayedRemoveTransition) { QQmlChangeSet::Change removal; for (QList<FxViewItem*>::Iterator it = visibleItems.begin(); it != visibleItems.end();) { @@ -1978,21 +2045,23 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult } } } +#endif *totalRemovalResult += removalResult; if (!removals.isEmpty()) { updateVisibleIndex(); // set positions correctly for the next insertion if (!insertions.isEmpty()) { - repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult); + repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstItemInView, &insertionResult, &removalResult); layoutVisibleItems(removals.first().index); + storeFirstVisibleItemPosition(); } } QList<FxViewItem *> newItems; QList<MovedItem> movingIntoView; - for (int i=0; i<insertions.count(); i++) { + for (int i=0; i<insertions.size(); i++) { bool wasEmpty = visibleItems.isEmpty(); if (applyInsertionChange(insertions[i], &insertionResult, &newItems, &movingIntoView)) visibleAffected = true; @@ -2003,25 +2072,27 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult *totalInsertionResult += insertionResult; // set positions correctly for the next insertion - if (i < insertions.count() - 1) { - repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult); + if (i < insertions.size() - 1) { + repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstItemInView, &insertionResult, &removalResult); layoutVisibleItems(insertions[i].index); + storeFirstVisibleItemPosition(); } itemCount += insertions[i].count; } - for (FxViewItem *item : qAsConst(newItems)) { + for (FxViewItem *item : std::as_const(newItems)) { if (item->attached) item->attached->emitAdd(); } +#if QT_CONFIG(quick_viewtransitions) // for each item that was moved directly into the view as a result of a move(), // find the index it was moved from in order to set its initial position, so that we // can transition it from this "original" position to its new position in the view if (transitioner && transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, true)) { - for (const MovedItem &m : qAsConst(movingIntoView)) { + for (const MovedItem &m : std::as_const(movingIntoView)) { int fromIndex = findMoveKeyIndex(m.moveKey, removals); if (fromIndex >= 0) { - if (prevFirstVisibleIndex >= 0 && fromIndex < prevFirstVisibleIndex) + if (prevFirstItemInViewIndex >= 0 && fromIndex < prevFirstItemInViewIndex) repositionItemAt(m.item, fromIndex, -totalInsertionResult->sizeChangesAfterVisiblePos); else repositionItemAt(m.item, fromIndex, totalInsertionResult->sizeChangesAfterVisiblePos); @@ -2029,16 +2100,19 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult } } } +#endif // reposition visibleItems.first() correctly so that the content y doesn't jump if (removedCount != prevVisibleItemsCount) - repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult); + repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstItemInView, &insertionResult, &removalResult); // Whatever removed/moved items remain are no longer visible items. +#if QT_CONFIG(quick_viewtransitions) prepareRemoveTransitions(¤tChanges.removedItems); - for (QHash<QQmlChangeSet::MoveKey, FxViewItem *>::Iterator it = currentChanges.removedItems.begin(); +#endif + for (auto it = currentChanges.removedItems.begin(); it != currentChanges.removedItems.end(); ++it) { - releaseItem(it.value()); + releaseItem(it.value(), reusableFlag); } currentChanges.removedItems.clear(); @@ -2046,10 +2120,9 @@ bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult if (currentChanges.currentRemoved && currentItem) { if (currentItem->item && currentItem->attached) currentItem->attached->setIsCurrentItem(false); - auto oldCurrentItem = currentItem; - releaseItem(currentItem); - currentItem = nullptr; - if (oldCurrentItem) + const bool hadCurrentItem = currentItem != nullptr; + releaseCurrentItem(reusableFlag); + if (hadCurrentItem) emit q->currentItemChanged(); } if (!currentIndexCleared) @@ -2074,7 +2147,7 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QQmlChangeSet::Change &remo Q_Q(QQuickItemView); bool visibleAffected = false; - if (visibleItems.count() && removal.index + removal.count > visibleItems.constLast()->index) { + if (visibleItems.size() && removal.index + removal.count > visibleItems.constLast()->index) { if (removal.index > visibleItems.constLast()->index) removeResult->countChangeAfterVisibleItems += removal.count; else @@ -2092,10 +2165,12 @@ bool QQuickItemViewPrivate::applyRemovalChange(const QQmlChangeSet::Change &remo } else if (item->index >= removal.index + removal.count) { // after removed items item->index -= removal.count; +#if QT_CONFIG(quick_viewtransitions) if (removal.isMove()) item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, false); else item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false); +#endif ++it; } else { // removed item @@ -2128,11 +2203,13 @@ void QQuickItemViewPrivate::removeItem(FxViewItem *item, const QQmlChangeSet::Ch removeResult->sizeChangesAfterVisiblePos += item->size(); } if (removal.isMove()) { - currentChanges.removedItems.insert(removal.moveKey(item->index), item); + currentChanges.removedItems.replace(removal.moveKey(item->index), item); +#if QT_CONFIG(quick_viewtransitions) item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, true); +#endif } else { // track item so it is released later - currentChanges.removedItems.insertMulti(QQmlChangeSet::MoveKey(), item); + currentChanges.removedItems.insert(QQmlChangeSet::MoveKey(), item); } if (!removeResult->changedFirstItem && item == *visibleItems.constBegin()) removeResult->changedFirstItem = true; @@ -2152,7 +2229,7 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs const QQmlNullableValue<qreal> prevViewPos = insertionResult->visiblePos; // reposition visibleItems.first() correctly so that the content y doesn't jump - if (visibleItems.count()) { + if (visibleItems.size()) { if (prevVisibleItemsFirst && insertionResult->changedFirstItem) resetFirstItemPosition(prevVisibleItemsFirstPos); @@ -2183,6 +2260,7 @@ void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirs } } +#if QT_CONFIG(quick_viewtransitions) void QQuickItemViewPrivate::createTransitioner() { if (!transitioner) { @@ -2199,19 +2277,18 @@ void QQuickItemViewPrivate::prepareVisibleItemTransitions() // must call for every visible item to init or discard transitions QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height()); - for (int i=0; i<visibleItems.count(); i++) + for (int i=0; i<visibleItems.size(); i++) visibleItems[i]->prepareTransition(transitioner, viewBounds); } -void QQuickItemViewPrivate::prepareRemoveTransitions(QHash<QQmlChangeSet::MoveKey, FxViewItem *> *removedItems) +void QQuickItemViewPrivate::prepareRemoveTransitions(QMultiHash<QQmlChangeSet::MoveKey, FxViewItem *> *removedItems) { if (!transitioner) return; if (transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, true) || transitioner->canTransition(QQuickItemViewTransitioner::RemoveTransition, false)) { - for (QHash<QQmlChangeSet::MoveKey, FxViewItem *>::Iterator it = removedItems->begin(); - it != removedItems->end(); ) { + for (auto it = removedItems->begin(); it != removedItems->end(); ) { bool isRemove = it.key().moveId < 0; if (isRemove) { FxViewItem *item = *it; @@ -2252,13 +2329,14 @@ bool QQuickItemViewPrivate::prepareNonVisibleItemTransition(FxViewItem *item, co void QQuickItemViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitionableItem *item) { - for (int i=0; i<releasePendingTransition.count(); i++) { + for (int i=0; i<releasePendingTransition.size(); i++) { if (releasePendingTransition.at(i)->transitionableItem == item) { - releaseItem(releasePendingTransition.takeAt(i)); + releaseItem(releasePendingTransition.takeAt(i), reusableFlag); return; } } } +#endif /* This may return 0 if the item is being created asynchronously. @@ -2272,17 +2350,21 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::Inc if (requestedIndex == modelIndex && incubationMode == QQmlIncubator::Asynchronous) return nullptr; - for (int i=0; i<releasePendingTransition.count(); i++) { +#if QT_CONFIG(quick_viewtransitions) + for (int i=0; i<releasePendingTransition.size(); i++) { if (releasePendingTransition.at(i)->index == modelIndex && !releasePendingTransition.at(i)->isPendingRemoval()) { releasePendingTransition[i]->releaseAfterTransition = false; return releasePendingTransition.takeAt(i); } } +#endif inRequest = true; - QObject* object = model->object(modelIndex, incubationMode); + // The model will run this same range check internally but produce a warning and return nullptr. + // Since we handle this result graciously in our code, we preempt this warning by checking the range ourselves. + QObject* object = modelIndex < model->count() ? model->object(modelIndex, incubationMode) : nullptr; QQuickItem *item = qmlobject_cast<QQuickItem*>(object); if (!item) { @@ -2360,23 +2442,47 @@ void QQuickItemView::destroyingItem(QObject *object) } } -bool QQuickItemViewPrivate::releaseItem(FxViewItem *item) +void QQuickItemView::onItemPooled(int modelIndex, QObject *object) +{ + Q_UNUSED(modelIndex); + + if (auto *attached = d_func()->getAttachedObject(object)) + emit attached->pooled(); +} + +void QQuickItemView::onItemReused(int modelIndex, QObject *object) +{ + Q_UNUSED(modelIndex); + + if (auto *attached = d_func()->getAttachedObject(object)) + emit attached->reused(); +} + +bool QQuickItemViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag) { Q_Q(QQuickItemView); - if (!item || !model) + if (!item) return true; if (trackedItem == item) trackedItem = nullptr; item->trackGeometry(false); - QQmlInstanceModel::ReleaseFlags flags = model->release(item->item); - if (item->item) { - if (flags == 0) { + QQmlInstanceModel::ReleaseFlags flags = {}; + if (model && item->item) { + flags = model->release(item->item, reusableFlag); + if (!flags) { // item was not destroyed, and we no longer reference it. - QQuickItemPrivate::get(item->item)->setCulled(true); - unrequestedItems.insert(item->item, model->indexOf(item->item, q)); + if (item->item->parentItem() == contentItem) { + // Only cull the item if its parent item is still our contentItem. + // One case where this can happen is moving an item out of one ObjectModel and into another. + QQuickItemPrivate::get(item->item)->setCulled(true); + } + if (!isClearing) + unrequestedItems.insert(item->item, model->indexOf(item->item, q)); } else if (flags & QQmlInstanceModel::Destroyed) { item->item->setParentItem(nullptr); + } else if (flags & QQmlInstanceModel::Pooled) { + item->setVisible(false); } } delete item; @@ -2394,17 +2500,14 @@ QQuickItem *QQuickItemViewPrivate::createComponentItem(QQmlComponent *component, QQuickItem *item = nullptr; if (component) { - QQmlContext *creationContext = component->creationContext(); - QQmlContext *context = new QQmlContext( - creationContext ? creationContext : qmlContext(q)); - QObject *nobj = component->beginCreate(context); - if (nobj) { - QQml_setParent_noEvent(context, nobj); + QQmlContext *context = component->creationContext(); + if (!context) + context = qmlContext(q); + + if (QObject *nobj = component->beginCreate(context)) { item = qobject_cast<QQuickItem *>(nobj); if (!item) delete nobj; - } else { - delete context; } } else if (createDefault) { item = new QQuickItem; @@ -2414,18 +2517,35 @@ QQuickItem *QQuickItemViewPrivate::createComponentItem(QQmlComponent *component, item->setZ(zValue); QQml_setParent_noEvent(item, q->contentItem()); item->setParentItem(q->contentItem()); + + initializeComponentItem(item); } if (component) component->completeCreate(); return item; } +/*! + \internal + + Allows derived classes to do any initialization required for \a item + before completeCreate() is called on it. For example, any attached + properties required by the item can be set. + + This is similar to initItem(), but as that has logic specific to + delegate items, we use a separate function for non-delegates. +*/ +void QQuickItemViewPrivate::initializeComponentItem(QQuickItem *item) const +{ + Q_UNUSED(item); +} + void QQuickItemViewPrivate::updateTrackedItem() { Q_Q(QQuickItemView); FxViewItem *item = currentItem; if (highlight) - item = highlight; + item = highlight.get(); trackedItem = item; if (trackedItem) |