diff options
author | Jan Arve Sæther <jan-arve.saether@qt.io> | 2021-03-09 10:13:57 +0100 |
---|---|---|
committer | Jan Arve Sæther <jan-arve.saether@qt.io> | 2021-03-30 10:23:59 +0000 |
commit | ea1681cf2ac36450778552567201662ff1a39ffe (patch) | |
tree | 2afe62e6d68cf7e8f1caa1cce248b8ee2f7843ce /src/quick | |
parent | 36ae81c73f3592f8ebc73a301cf29bf91d076391 (diff) |
DelegateModelGroup: Fix bug where item could be removed from the model
If an item was removed from the DelegateModelGroup before it was
completed it caused subsequent items in the model to be missing in
some cases.
The reason was that while populating the ListView, it iterated with an
index for each item to call createItem() on.
However, createItem() might call onCompleted (which in the case of
QTBUG-86708 removed the item from the DelegateModel), which caused the
next index we called createItem() with to be wrong (it became one step
ahead).
We therefore add a helper class MutableModelIterator, which keeps track
of if a index in the model got removed (and if the iterator index needs
to be adjusted because of that)....
Task-number: QTBUG-86708
Change-Id: I33537b43727aed4f2b9bdda794b011b6684c44b4
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
(cherry picked from commit 0ff9db566c48172c688bf9327fe6a781dc4a1c34)
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
Diffstat (limited to 'src/quick')
-rw-r--r-- | src/quick/items/qquicklistview.cpp | 102 |
1 files changed, 93 insertions, 9 deletions
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 044f863ab1..bdb3b1227f 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -382,6 +382,83 @@ private: } }; +/*! \internal + \brief A helper class for iterating over a model that might change + + When populating the ListView from a model under normal + circumstances, we would iterate over the range of model indices + correspondning to the visual range, and basically call + createItem(index++) in order to create each item. + + This will also emit Component.onCompleted() for each item, which + might do some weird things... For instance, it might remove itself + from the model, and this might change model count and the indices + of the other subsequent entries in the model. + + This class takes such changes to the model into consideration while + iterating, and will adjust the iterator index and keep track of + whether the iterator has reached the end of the range. + + It keeps track of changes to the model by connecting to + QQmlInstanceModel::modelUpdated() from its constructor. + When destroyed, it will automatically disconnect. You can + explicitly disconnect earlier by calling \fn disconnect(). +*/ +class MutableModelIterator { +public: + MutableModelIterator(QQmlInstanceModel *model, int iBegin, int iEnd) + : removedAtIndex(false) + , backwards(iEnd < iBegin) + { + conn = QObject::connect(model, &QQmlInstanceModel::modelUpdated, + [&] (const QQmlChangeSet &changeSet, bool /*reset*/) + { + for (const QQmlChangeSet::Change &rem : changeSet.removes()) { + idxEnd -= rem.count; + if (rem.start() <= index) { + index -= rem.count; + if (index < rem.start() + rem.count) + removedAtIndex = true; // model index was removed + } + } + for (const QQmlChangeSet::Change &ins : changeSet.inserts()) { + idxEnd += ins.count; + if (ins.start() <= index) + index += ins.count; + } + } + ); + index = iBegin; + idxEnd = iEnd; + } + + bool hasNext() const { + return backwards ? index > idxEnd : index < idxEnd; + } + + void next() { index += (backwards ? -1 : +1); } + + ~MutableModelIterator() + { + disconnect(); + } + + void disconnect() + { + if (conn) { + QObject::disconnect(conn); + conn = QMetaObject::Connection(); // set to nullptr + } + } + int index = 0; + int idxEnd; + unsigned removedAtIndex : 1; + unsigned backwards : 1; +private: + QMetaObject::Connection conn; +}; + + //---------------------------------------------------------------------------- bool QQuickListViewPrivate::isContentFlowReversed() const @@ -3570,7 +3647,6 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch if (insertResult->visiblePos.isValid() && pos < insertResult->visiblePos) { // Insert items before the visible item. int insertionIdx = index; - int i = 0; qreal from = tempPos - displayMarginBeginning - buffer; if (insertionIdx < visibleIndex) { @@ -3579,15 +3655,18 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch insertResult->sizeChangesBeforeVisiblePos += count * (averageSize + spacing); } } else { - for (i = count-1; i >= 0 && pos >= from; --i) { + MutableModelIterator it(model, modelIndex + count - 1, modelIndex -1); + for (; it.hasNext() && pos >= from; it.next()) { // item is before first visible e.g. in cache buffer FxViewItem *item = nullptr; - if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) - item->index = modelIndex + i; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(it.index)))) + item->index = it.index; if (!item) - item = createItem(modelIndex + i, QQmlIncubator::Synchronous); + item = createItem(it.index, QQmlIncubator::Synchronous); if (!item) return false; + if (it.removedAtIndex) + continue; visibleAffected = true; visibleItems.insert(insertionIdx, item); @@ -3620,16 +3699,20 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch } } else { - for (int i = 0; i < count && pos <= lastVisiblePos; ++i) { + MutableModelIterator it(model, modelIndex, modelIndex + count); + for (; it.hasNext() && pos <= lastVisiblePos; it.next()) { visibleAffected = true; FxViewItem *item = nullptr; - if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) - item->index = modelIndex + i; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(it.index)))) + item->index = it.index; bool newItem = !item; + it.removedAtIndex = false; if (!item) - item = createItem(modelIndex + i, QQmlIncubator::Synchronous); + item = createItem(it.index, QQmlIncubator::Synchronous); if (!item) return false; + if (it.removedAtIndex) + continue; visibleItems.insert(index, item); if (index == 0) @@ -3650,6 +3733,7 @@ bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &ch pos += item->size() + spacing; ++index; } + it.disconnect(); if (0 < index && index < visibleItems.count()) { FxViewItem *prevItem = visibleItems.at(index - 1); |