diff options
-rw-r--r-- | src/quick/items/qquicklistview.cpp | 102 | ||||
-rw-r--r-- | tests/auto/qml/qqmldelegatemodel/data/removeFromGroup.qml | 45 | ||||
-rw-r--r-- | tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp | 15 |
4 files changed, 154 insertions, 10 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); diff --git a/tests/auto/qml/qqmldelegatemodel/data/removeFromGroup.qml b/tests/auto/qml/qqmldelegatemodel/data/removeFromGroup.qml new file mode 100644 index 0000000000..4ae1a8aacc --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/removeFromGroup.qml @@ -0,0 +1,45 @@ +import QtQuick 2.8 +import QtQml.Models 2.1 + +Item { + id: root + width: 200 + height: 200 + + DelegateModel { + id: visualModel + model: ListModel { + id: myLM + ListElement { + name: "Apple" + } + ListElement { + name: "Banana" + } + ListElement { + name: "Orange" + } + } + filterOnGroup: "selected" + groups: [ + DelegateModelGroup { + name: "selected" + includeByDefault: true + } + ] + delegate: Text { + Component.onCompleted: { + if (index === 1) { + DelegateModel.inSelected = false + } + } + text: index + ": " + model.name + } + } + + // Needs an actual ListView in order for the DelegateModel to instantiate all items + ListView { + model: visualModel + anchors.fill: parent + } +} diff --git a/tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro b/tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro index 7fdd3ab5f1..fbd72f6a44 100644 --- a/tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro +++ b/tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro @@ -2,7 +2,7 @@ CONFIG += testcase TARGET = tst_qqmldelegatemodel macos:CONFIG -= app_bundle -QT += qml testlib core-private qml-private qmlmodels-private +QT += qml quick testlib core-private qml-private qmlmodels-private SOURCES += tst_qqmldelegatemodel.cpp diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 87f42c0c8a..71550a50f3 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -29,6 +29,8 @@ #include <QtTest/qtest.h> #include <QtQml/qqmlcomponent.h> #include <QtQmlModels/private/qqmldelegatemodel_p.h> +#include <QtQuick/qquickview.h> +#include <QtQuick/qquickitem.h> #include "../../shared/util.h" @@ -42,6 +44,7 @@ public: private slots: void valueWithoutCallingObjectFirst_data(); void valueWithoutCallingObjectFirst(); + void filterOnGroup_removeWhenCompleted(); }; class AbstractItemModel : public QAbstractItemModel @@ -134,6 +137,18 @@ void tst_QQmlDelegateModel::valueWithoutCallingObjectFirst() QCOMPARE(model->variantValue(index, role), expectedValue); } +void tst_QQmlDelegateModel::filterOnGroup_removeWhenCompleted() +{ + QQuickView view(testFileUrl("removeFromGroup.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QQuickItem *root = view.rootObject(); + QVERIFY(root); + QQmlDelegateModel *model = root->findChild<QQmlDelegateModel*>(); + QVERIFY(model); + QTest::qWaitFor([=]{ return model->count() == 2; } ); +} + QTEST_MAIN(tst_QQmlDelegateModel) #include "tst_qqmldelegatemodel.moc" |