From e8570bd1af74724b6fae1ac8a2d8bbdfee7a6504 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 13 Dec 2017 12:17:14 +0100 Subject: QQuickItemView: Clear pending changes when refilling Generally the bufferedChanges are an "extension" of the currentChanges, which can just not be applied at the moment because we are in a layout phase. If we regenerate or clear the whole view in the mean time, the bufferedChanges become just as invalid as the currentChanges. On top of that, refilling can trigger further changes, part of which will be applied during the refilling. As that leaves us in an inconsistent state, we need to loop the refilling until no further changes are generated. As the changes might affect items that are already visible, and therefore not subject to refilling, we need to clear all the items before refilling in this case. In QTBUG-46488 things are added in the onCompleted callback of the delegates (by expanding the tree view, which translates into adding rows to the list view). Depending on where you add the new items, the list view might pick them up when iterating the model on refill() or it might create delegates for the same model entry twice. So, if that happens we need to discard the result and refill again. Task-number: QTBUG-46488 Change-Id: Ie4e0a731f7feda6aa962b6cb9a6cd5c3bf90486e Reviewed-by: Richard Moe Gustavsen --- src/quick/items/qquickitemview.cpp | 76 +++++++++--------- .../quick/qquicklistview/data/addoncompleted.qml | 90 ++++++++++++++++++++++ .../quick/qquicklistview/tst_qquicklistview.cpp | 32 ++++++++ 3 files changed, 163 insertions(+), 35 deletions(-) create mode 100644 tests/auto/quick/qquicklistview/data/addoncompleted.qml diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index 10f6c63170..856070634c 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -1751,6 +1751,7 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex) void QQuickItemViewPrivate::clear() { currentChanges.reset(); + bufferedChanges.reset(); timeline.clear(); releaseVisibleItems(); @@ -1808,51 +1809,56 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) if (!isValid() || !q->isComponentComplete()) return; - bufferPause.stop(); - currentChanges.reset(); + do { + bufferPause.stop(); + if (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges()) { + currentChanges.reset(); + bufferedChanges.reset(); + releaseVisibleItems(); + } - int prevCount = itemCount; - itemCount = model->count(); - qreal bufferFrom = from - buffer; - qreal bufferTo = to + buffer; - qreal fillFrom = from; - qreal fillTo = to; - - bool added = addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, false); - bool removed = removeNonVisibleItems(bufferFrom, bufferTo); - - if (requestedIndex == -1 && buffer && bufferMode != NoBuffer) { - if (added) { - // We've already created a new delegate this frame. - // Just schedule a buffer refill. - bufferPause.start(); - } else { - if (bufferMode & BufferAfter) - fillTo = bufferTo; - if (bufferMode & BufferBefore) - fillFrom = bufferFrom; - added |= addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, true); + int prevCount = itemCount; + itemCount = model->count(); + qreal bufferFrom = from - buffer; + qreal bufferTo = to + buffer; + qreal fillFrom = from; + qreal fillTo = to; + + bool added = addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, false); + bool removed = removeNonVisibleItems(bufferFrom, bufferTo); + + if (requestedIndex == -1 && buffer && bufferMode != NoBuffer) { + if (added) { + // We've already created a new delegate this frame. + // Just schedule a buffer refill. + bufferPause.start(); + } else { + if (bufferMode & BufferAfter) + fillTo = bufferTo; + if (bufferMode & BufferBefore) + fillFrom = bufferFrom; + added |= addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, true); + } } - } - if (added || removed) { - markExtentsDirty(); - updateBeginningEnd(); - visibleItemsChanged(); - updateHeader(); - updateFooter(); - updateViewport(); - } + if (added || removed) { + markExtentsDirty(); + updateBeginningEnd(); + visibleItemsChanged(); + updateHeader(); + updateFooter(); + updateViewport(); + } - if (prevCount != itemCount) - emit q->countChanged(); + if (prevCount != itemCount) + emit q->countChanged(); + } while (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges()); } void QQuickItemViewPrivate::regenerate(bool orientationChanged) { Q_Q(QQuickItemView); if (q->isComponentComplete()) { - currentChanges.reset(); if (orientationChanged) { delete header; header = 0; diff --git a/tests/auto/quick/qquicklistview/data/addoncompleted.qml b/tests/auto/quick/qquicklistview/data/addoncompleted.qml new file mode 100644 index 0000000000..57265cb2c0 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/addoncompleted.qml @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.9 + +Rectangle { + width: 640 + height: 480 + color: "green" + + ListModel { + id: listModel + ListElement { name: "a" } + ListElement { name: "b" } + ListElement { name: "c" } + ListElement { name: "d" } + ListElement { name: "e" } + ListElement { name: "f" } + ListElement { name: "g" } + ListElement { name: "h" } + ListElement { name: "i" } + ListElement { name: "j" } + } + + ListView { + anchors.fill: parent + model: listModel + objectName: "view" + + delegate: Rectangle { + height: 15 + width: 15 + color: "blue" + objectName: name + Component.onCompleted: { + if (name.length === 1 && listModel.get(index + 1).name.length === 1) { + for (var i = 0; i < 10; ++i) + listModel.insert(index + 1, {name: name + i}); + } + } + } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 73d831ecdd..a7583129f9 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -264,6 +264,8 @@ private slots: void QTBUG_34576_velocityZero(); void QTBUG_61537_modelChangesAsync(); + void addOnCompleted(); + private: template void items(const QUrl &source); template void changed(const QUrl &source); @@ -8735,6 +8737,36 @@ void tst_QQuickListView::QTBUG_61537_modelChangesAsync() QCOMPARE(reportedCount, actualCount); } +void tst_QQuickListView::addOnCompleted() +{ + QScopedPointer window(createView()); + window->setSource(testFileUrl("addoncompleted.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickListView *listview = findItem(window->rootObject(), "view"); + QTRY_VERIFY(listview != 0); + + QQuickItem *contentItem = listview->contentItem(); + QTRY_VERIFY(contentItem != 0); + + qreal y = -1; + for (char name = 'a'; name <= 'j'; ++name) { + for (int num = 9; num >= 0; --num) { + const QString objName = QString::fromLatin1("%1%2").arg(name).arg(num); + QQuickItem *item = findItem(contentItem, objName); + if (!item) { + QVERIFY(name >= 'd'); + y = 9999999; + } else { + const qreal newY = item->y(); + QVERIFY2(newY > y, objName.toUtf8().constData()); + y = newY; + } + } + } +} + QTEST_MAIN(tst_QQuickListView) #include "tst_qquicklistview.moc" -- cgit v1.2.3