aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/items/qquicklistview.cpp102
-rw-r--r--tests/auto/qml/qqmldelegatemodel/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/removeFromGroup.qml45
-rw-r--r--tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro13
-rw-r--r--tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp15
5 files changed, 167 insertions, 9 deletions
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index 0c924c0e0e..b954f1b107 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
@@ -3602,7 +3679,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) {
@@ -3611,15 +3687,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);
@@ -3652,16 +3731,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)
@@ -3682,6 +3765,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/CMakeLists.txt b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt
index ef31fa3db3..9020d9e892 100644
--- a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt
+++ b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt
@@ -22,6 +22,7 @@ qt_internal_add_test(tst_qqmldelegatemodel
Qt::Qml
Qt::QmlModelsPrivate
Qt::QmlPrivate
+ Qt::Quick
TESTDATA ${test_data}
)
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
new file mode 100644
index 0000000000..fbd72f6a44
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/qqmldelegatemodel.pro
@@ -0,0 +1,13 @@
+CONFIG += testcase
+TARGET = tst_qqmldelegatemodel
+macos:CONFIG -= app_bundle
+
+QT += qml quick testlib core-private qml-private qmlmodels-private
+
+SOURCES += tst_qqmldelegatemodel.cpp
+
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+OTHER_FILES += data/*
diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp
index 8e6d147028..1a772b8a1e 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"
@@ -43,6 +45,7 @@ private slots:
void valueWithoutCallingObjectFirst_data();
void valueWithoutCallingObjectFirst();
void qtbug_86017();
+ void filterOnGroup_removeWhenCompleted();
};
class AbstractItemModel : public QAbstractItemModel
@@ -150,6 +153,18 @@ void tst_QQmlDelegateModel::qtbug_86017()
QCOMPARE(model->filterGroup(), "selected");
}
+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"