No longer apply pending changes when accessing ItemView properties
authorAlan Alpert <aalpert@blackberry.com>
Thu, 11 Apr 2013 23:57:19 +0000 (16:57 -0700)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Fri, 26 Apr 2013 19:17:42 +0000 (21:17 +0200)
Applying changes in the getters can lead to binding loops, and is
currently inconsistently applied. Removing the applyPendingChanges calls
from remaining getters, and adding a forceLayout() function for cases
where the immediate-apply behavior is needed.

Task-number:  QTBUG-30555
Parts-of-patch-by: Albert Astals Cid
Change-Id: I64632601e02f2a53060296ab7739577a749d916f
Reviewed-by: Andrew den Exter <andrew.den.exter@qinetic.com.au>
Reviewed-by: Albert Astals Cid <albert.astals@canonical.com>
src/quick/items/qquickgridview.cpp
src/quick/items/qquickitemsmodule.cpp
src/quick/items/qquickitemview.cpp
src/quick/items/qquickitemview_p.h
src/quick/items/qquicklistview.cpp
tests/auto/qmltest/listview/tst_listview.qml
tests/auto/quick/qquickgridview/tst_qquickgridview.cpp
tests/auto/quick/qquicklistview/data/delayedChanges.qml [new file with mode: 0644]
tests/auto/quick/qquicklistview/data/emptymodel.qml
tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp

index e40d21b..f4e34da 100644 (file)
@@ -2564,6 +2564,22 @@ bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) co
     \b Note: methods should only be called after the Component has completed.
 */
 
+
+/*!
+    \qmlmethod QtQuick2::GridView::forceLayout()
+
+    Responding to changes in the model is usually batched to happen only once
+    per frame. This means that inside script blocks it is possible for the
+    underlying model to have changed, but the GridView has not caught up yet.
+
+    This method forces the GridView to immediately respond to any outstanding
+    changes in the model.
+
+    \since 5.1
+
+    \b Note: methods should only be called after the Component has completed.
+*/
+
 QQuickGridViewAttached *QQuickGridView::qmlAttachedProperties(QObject *obj)
 {
     return new QQuickGridViewAttached(obj);
index 741583a..f5bcf35 100644 (file)
@@ -230,6 +230,9 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
 
     qmlRegisterType<QQuickItem, 1>(uri, 2, 1,"Item");
     qmlRegisterType<QQuickGrid, 1>(uri, 2, 1, "Grid");
+    qmlRegisterUncreatableType<QQuickItemView, 1>(uri, 2, 1, "ItemView", QQuickItemView::tr("ItemView is an abstract base class"));
+    qmlRegisterType<QQuickListView, 1>(uri, 2, 1, "ListView");
+    qmlRegisterType<QQuickGridView, 1>(uri, 2, 1, "GridView");
     qmlRegisterType<QQuickTextEdit, 1>(uri, 2, 1, "TextEdit");
 }
 
index 03e16f2..d774091 100644 (file)
@@ -249,7 +249,6 @@ QQuickItemView::~QQuickItemView()
 QQuickItem *QQuickItemView::currentItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->currentItem ? d->currentItem->item : 0;
 }
 
@@ -379,14 +378,12 @@ int QQuickItemView::count() const
     Q_D(const QQuickItemView);
     if (!d->model)
         return 0;
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->model->count();
 }
 
 int QQuickItemView::currentIndex() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->currentIndex;
 }
 
@@ -496,7 +493,6 @@ QQmlComponent *QQuickItemView::header() const
 QQuickItem *QQuickItemView::headerItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->header ? d->header->item : 0;
 }
 
@@ -532,7 +528,6 @@ QQmlComponent *QQuickItemView::footer() const
 QQuickItem *QQuickItemView::footerItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->footer ? d->footer->item : 0;
 }
 
@@ -559,7 +554,6 @@ void QQuickItemView::setFooter(QQmlComponent *footerComponent)
 QQmlComponent *QQuickItemView::highlight() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->highlightComponent;
 }
 
@@ -579,7 +573,6 @@ void QQuickItemView::setHighlight(QQmlComponent *highlightComponent)
 QQuickItem *QQuickItemView::highlightItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->highlight ? d->highlight->item : 0;
 }
 
@@ -965,6 +958,12 @@ QQuickItem *QQuickItemView::itemAt(qreal x, qreal y) const
     return 0;
 }
 
+void QQuickItemView::forceLayout()
+{
+    Q_D(QQuickItemView);
+    d->applyPendingChanges();
+}
+
 void QQuickItemViewPrivate::applyPendingChanges()
 {
     Q_Q(QQuickItemView);
index b0f9106..d7812bc 100644 (file)
@@ -202,6 +202,7 @@ public:
     Q_INVOKABLE QQuickItem *itemAt(qreal x, qreal y) const;
     Q_INVOKABLE void positionViewAtBeginning();
     Q_INVOKABLE void positionViewAtEnd();
+    Q_REVISION(1) Q_INVOKABLE void forceLayout();
 
     virtual void setContentX(qreal pos);
     virtual void setContentY(qreal pos);
index 53dc715..b99fba4 100644 (file)
@@ -3108,6 +3108,21 @@ void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex
     \b Note: methods should only be called after the Component has completed.
 */
 
+/*!
+    \qmlmethod QtQuick2::ListView::forceLayout()
+
+    Responding to changes in the model is usually batched to happen only once
+    per frame. This means that inside script blocks it is possible for the
+    underlying model to have changed, but the ListView has not caught up yet.
+
+    This method forces the ListView to immediately respond to any outstanding
+    changes in the model.
+
+    \since 5.1
+
+    \b Note: methods should only be called after the Component has completed.
+*/
+
 QQuickListViewAttached *QQuickListView::qmlAttachedProperties(QObject *obj)
 {
     return new QQuickListViewAttached(obj);
index cbace62..52384fe 100644 (file)
@@ -38,7 +38,7 @@
 **
 ****************************************************************************/
 
-import QtQuick 2.0
+import QtQuick 2.1
 import QtTest 1.0
 
 Item {
@@ -168,6 +168,7 @@ Item {
             modelalter.currentIndex = 1;
             compare(modelalter.currentItem.text, "AlterModelElement1")
             altermodel.clear()
+            modelalter.forceLayout()
             tryCompare(modelalter.count, 0)
             compare(modelalter.currentItem, null)
         }
index 0c9788a..d0ffba9 100644 (file)
@@ -532,6 +532,7 @@ void tst_QQuickGridView::inserted_defaultLayout(QQuickGridView::Flow flow,
     for (int i=0; i<insertCount; i++)
         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.insertItems(insertIndex, newData);
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->property("count").toInt(), model.count());
 
     // check visibleItems.first() is in correct position
@@ -728,6 +729,7 @@ void tst_QQuickGridView::insertBeforeVisible()
     for (int i=0; i<insertCount; i++)
         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.insertItems(insertIndex, newData);
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->property("count").toInt(), model.count());
 
     // now, moving to the top of the view should position the inserted items correctly
@@ -958,6 +960,7 @@ void tst_QQuickGridView::removed_defaultLayout(QQuickGridView::Flow flow,
         QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
 
     model.removeItems(removeIndex, removeCount);
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->property("count").toInt(), model.count());
 
     QString firstName;
@@ -1245,6 +1248,7 @@ void tst_QQuickGridView::clear()
     QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
 
     model.clear();
+    gridview->forceLayout();
 
     QVERIFY(gridview->count() == 0);
     QVERIFY(gridview->currentItem() == 0);
@@ -1254,6 +1258,7 @@ void tst_QQuickGridView::clear()
 
     // confirm sanity when adding an item to cleared list
     model.addItem("New", "1");
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->count(), 1);
     QVERIFY(gridview->currentItem() != 0);
     QVERIFY(gridview->currentIndex() == 0);
@@ -3483,6 +3488,7 @@ void tst_QQuickGridView::extents()
     QCOMPARE(gridview->originY(), origin_empty.y());
     for (int i=0; i<30; i++)
         model.addItem("Item" + QString::number(i), "");
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->count(), model.count());
     QCOMPARE(gridview->originX(), origin_nonEmpty.x());
     QCOMPARE(gridview->originY(), origin_nonEmpty.y());
@@ -3942,9 +3948,9 @@ void tst_QQuickGridView::onAdd()
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
 
-    QObject *object = window->rootObject();
-    object->setProperty("width", window->width());
-    object->setProperty("height", window->height());
+    QQuickGridView *gridview = qobject_cast<QQuickGridView*>(window->rootObject());
+    gridview->setProperty("width", window->width());
+    gridview->setProperty("height", window->height());
     qApp->processEvents();
 
     QList<QPair<QString, QString> > items;
@@ -3952,10 +3958,11 @@ void tst_QQuickGridView::onAdd()
         items << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.addItems(items);
 
-    QTRY_COMPARE(model.count(), qobject_cast<QQuickGridView*>(window->rootObject())->count());
+    gridview->forceLayout();
+    QTRY_COMPARE(model.count(), gridview->count());
     qApp->processEvents();
 
-    QVariantList result = object->property("addedDelegates").toList();
+    QVariantList result = gridview->property("addedDelegates").toList();
     QTRY_COMPARE(result.count(), items.count());
     for (int i=0; i<items.count(); i++)
         QCOMPARE(result[i].toString(), items[i].first);
@@ -3999,11 +4006,12 @@ void tst_QQuickGridView::onRemove()
     ctxt->setContextProperty("delegateWidth", delegateWidth);
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
-    QObject *object = window->rootObject();
+    QQuickGridView *gridview = qobject_cast<QQuickGridView*>(window->rootObject());
 
     model.removeItems(indexToRemove, removeCount);
-    QTRY_COMPARE(model.count(), qobject_cast<QQuickGridView*>(window->rootObject())->count());
-    QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
+    gridview->forceLayout();
+    QTRY_COMPARE(model.count(), gridview->count());
+    QCOMPARE(gridview->property("removedDelegateCount"), QVariant(removeCount));
 
     releaseView(window);
 }
@@ -4151,6 +4159,7 @@ void tst_QQuickGridView::margins()
         gridview->setContentX(-400);
         QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
         model.removeItems(0, 4);
+        gridview->forceLayout();
         QTRY_COMPARE(model.count(), gridview->count());
         gridview->setContentX(-240+50);
         gridview->returnToBounds();
@@ -4458,6 +4467,7 @@ void tst_QQuickGridView::unaligned()
 
     // removing
     model.removeItems(7, 10);
+    gridview->forceLayout();
     QTRY_COMPARE(model.count(), gridview->count());
     for (int i = 0; i < 18; ++i) {
         QQuickItem *item = 0;
@@ -4663,6 +4673,7 @@ void tst_QQuickGridView::addTransitions()
     // start animation
     if (!newData.isEmpty()) {
         model.insertItems(insertionIndex, newData);
+        gridview->forceLayout();
         QTRY_COMPARE(model.count(), gridview->count());
     }
 
@@ -4866,6 +4877,7 @@ void tst_QQuickGridView::moveTransitions()
 
     // start animation
     model.moveItems(moveFrom, moveTo, moveCount);
+    gridview->forceLayout();
 
     QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
     QTRY_COMPARE(gridview->property("displaceTransitionsDone").toInt(),
@@ -5114,6 +5126,7 @@ void tst_QQuickGridView::removeTransitions()
 
     // start animation
     model.removeItems(removalIndex, removalCount);
+    gridview->forceLayout();
     QTRY_COMPARE(model.count(), gridview->count());
 
     if (shouldAnimateTargets || expectedDisplacedIndexes.isValid()) {
@@ -5327,6 +5340,7 @@ void tst_QQuickGridView::displacedTransitions()
         case ListChange::Polish:
             break;
     }
+    gridview->forceLayout();
 
     QVariantList resultTargetIndexes = gridview->property("displacedTargetIndexes").toList();
     QVariantList resultTargetItems = gridview->property("displacedTargetItems").toList();
@@ -5534,6 +5548,7 @@ void tst_QQuickGridView::multipleTransitions()
                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
                     targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
                 model.insertItems(changes[i].index, targetItems);
+                gridview->forceLayout();
                 QTRY_COMPARE(model.count(), gridview->count());
                 if (i == changes.count() - 1) {
                     QTRY_VERIFY(!gridview->property("runningAddTargets").toBool());
@@ -5545,6 +5560,7 @@ void tst_QQuickGridView::multipleTransitions()
             }
             case ListChange::Removed:
                 model.removeItems(changes[i].index, changes[i].count);
+                gridview->forceLayout();
                 QTRY_COMPARE(model.count(), gridview->count());
                 if (i == changes.count() - 1) {
                     QTRY_VERIFY(!gridview->property("runningRemoveTargets").toBool());
@@ -5555,6 +5571,7 @@ void tst_QQuickGridView::multipleTransitions()
                 break;
             case ListChange::Moved:
                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+                gridview->forceLayout();
                 QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
                 if (i == changes.count() - 1) {
                     QTRY_VERIFY(!gridview->property("runningMoveTargets").toBool());
@@ -5566,16 +5583,17 @@ void tst_QQuickGridView::multipleTransitions()
             case ListChange::SetCurrent:
                 gridview->setCurrentIndex(changes[i].index);
                 QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                gridview->forceLayout();
                 break;
             case ListChange::SetContentY:
                 gridview->setContentY(changes[i].pos);
                 QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                gridview->forceLayout();
                 break;
             case ListChange::Polish:
                 break;
         }
     }
-    QCOMPARE(gridview->count(), model.count());
 
     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
     int firstVisibleIndex = -1;
@@ -5586,6 +5604,7 @@ void tst_QQuickGridView::multipleTransitions()
             break;
         }
     }
+    QTRY_COMPARE(gridview->count(), model.count());
     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
 
     // verify all items moved to the correct final positions
diff --git a/tests/auto/quick/qquicklistview/data/delayedChanges.qml b/tests/auto/quick/qquicklistview/data/delayedChanges.qml
new file mode 100644 (file)
index 0000000..590af39
--- /dev/null
@@ -0,0 +1,39 @@
+import QtQuick 2.1
+
+Item {
+    width: 400
+    height: 400
+    function takeTwo()
+    {
+        listView.model.remove(0);
+        listView.model.remove(0);
+    }
+    function takeTwo_sync()
+    {
+        listView.model.remove(0);
+        listView.forceLayout();
+        listView.model.remove(0);
+        listView.forceLayout();
+    }
+
+    ListView {
+        id: listView
+        height: parent.height
+        width: 400
+        model: 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" }
+        }
+        delegate: Text {
+            text: index + listView.count
+        }
+    }
+}
index 16bcd3f..3feec69 100644 (file)
@@ -1,4 +1,4 @@
-import QtQuick 2.0
+import QtQuick 2.1
 Rectangle {
     ListModel {
         id: model
@@ -12,11 +12,13 @@ Rectangle {
     }
     function remove() {
         model.remove(0)
+        list.forceLayout()
         isCurrentItemNull = list.currentItem === null //check no seg fault
     }
 
     function add() {
         model.append({name: "hello"})
+        list.forceLayout()
         isCurrentItemNull = list.currentItem === null
     }
     property bool isCurrentItemNull
index f8c7de6..bec61ea 100644 (file)
@@ -208,10 +208,11 @@ private slots:
     void parentBinding();
     void defaultHighlightMoveDuration();
     void accessEmptyCurrentItem_QTBUG_30227();
+    void delayedChanges_QTBUG_30555();
 
 private:
-    template <class T> void items(const QUrl &source, bool forceLayout);
-    template <class T> void changed(const QUrl &source, bool forceLayout);
+    template <class T> void items(const QUrl &source);
+    template <class T> void changed(const QUrl &source);
     template <class T> void inserted(const QUrl &source);
     template <class T> void inserted_more(QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
     template <class T> void removed(const QUrl &source, bool animated);
@@ -329,7 +330,7 @@ void tst_QQuickListView::cleanupTestCase()
 }
 
 template <class T>
-void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
+void tst_QQuickListView::items(const QUrl &source)
 {
     QQuickView *window = createView();
 
@@ -349,6 +350,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
 
     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
@@ -359,6 +361,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
     QTRY_VERIFY(listview->highlightItem() != 0);
     QTRY_COMPARE(listview->count(), model.count());
     QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
+    listview->forceLayout();
     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
 
     // current item should be first item
@@ -398,8 +401,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
     ctxt->setContextProperty("testModel", &model2);
 
     // Force a layout, necessary if ListView is completed before VisualDataModel.
-    if (forceLayout)
-        QCOMPARE(listview->property("count").toInt(), 0);
+    listview->forceLayout();
 
     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
     QTRY_VERIFY(itemCount == 0);
@@ -413,7 +415,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
 
 
 template <class T>
-void tst_QQuickListView::changed(const QUrl &source, bool forceLayout)
+void tst_QQuickListView::changed(const QUrl &source)
 {
     QQuickView *window = createView();
 
@@ -431,15 +433,15 @@ void tst_QQuickListView::changed(const QUrl &source, bool forceLayout)
     window->setSource(source);
     qApp->processEvents();
 
-    QQuickFlickable *listview = findItem<QQuickFlickable>(window->rootObject(), "list");
+    QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
     // Force a layout, necessary if ListView is completed before VisualDataModel.
-    if (forceLayout)
-        QCOMPARE(listview->property("count").toInt(), model.count());
+    listview->forceLayout();
 
     model.modifyItem(1, "Will", "9876");
     QQuickText *name = findItem<QQuickText>(contentItem, "textName", 1);
@@ -579,22 +581,24 @@ void tst_QQuickListView::inserted_more(QQuickItemView::VerticalLayoutDirection v
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
-    bool waitForPolish = (contentY != 0);
     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
         listview->setVerticalLayoutDirection(verticalLayoutDirection);
         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
         contentY = -listview->height() - contentY;
     }
     listview->setContentY(contentY);
-    if (waitForPolish)
-        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
 
     QList<QPair<QString, QString> > newData;
     for (int i=0; i<insertCount; i++)
         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.insertItems(insertIndex, newData);
+
+    //Wait for polish (updates list to the model changes)
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
     QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
+
     // check visibleItems.first() is in correct position
     QQuickItem *item0 = findItem<QQuickItem>(contentItem, "wrapper", 0);
     QVERIFY(item0);
@@ -1019,17 +1023,17 @@ void tst_QQuickListView::removed_more(const QUrl &source, QQuickItemView::Vertic
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
-    bool waitForPolish = (contentY != 0);
     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
         listview->setVerticalLayoutDirection(verticalLayoutDirection);
         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
         contentY = -listview->height() - contentY;
     }
     listview->setContentY(contentY);
-    if (waitForPolish)
-        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
 
     model.removeItems(removeIndex, removeCount);
+    //Wait for polish (updates list to the model changes)
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
     QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
     // check visibleItems.first() is in correct position
@@ -1219,6 +1223,7 @@ void tst_QQuickListView::clear(const QUrl &source, QQuickItemView::VerticalLayou
 
     // confirm sanity when adding an item to cleared list
     model.addItem("New", "1");
+    listview->forceLayout();
     QTRY_VERIFY(listview->count() == 1);
     QVERIFY(listview->currentItem() != 0);
     QVERIFY(listview->currentIndex() == 0);
@@ -1945,6 +1950,7 @@ void tst_QQuickListView::sections(const QUrl &source)
 
     // Remove section boundary
     model.removeItem(5);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     // New section header created
@@ -1953,6 +1959,7 @@ void tst_QQuickListView::sections(const QUrl &source)
     QTRY_COMPARE(item->height(), 40.0);
 
     model.insertItem(3, "New Item", "0");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     // Section header moved
@@ -1966,6 +1973,7 @@ void tst_QQuickListView::sections(const QUrl &source)
 
     // insert item which will become a section header
     model.insertItem(6, "Replace header", "1");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     item = findItem<QQuickItem>(contentItem, "wrapper", 6);
@@ -2061,6 +2069,7 @@ void tst_QQuickListView::sectionsDelegate()
 
     // remove section boundary
     model.removeItem(5);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     for (int i = 0; i < 3; ++i) {
         QQuickItem *item = findItem<QQuickItem>(contentItem,
@@ -2288,6 +2297,7 @@ void tst_QQuickListView::sectionsPositioning()
     listview->setContentY(120);
     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
     model.removeItem(5);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     for (int i = 1; i < 3; ++i) {
         QQuickItem *item = findVisibleChild(contentItem,
@@ -2485,7 +2495,8 @@ void tst_QQuickListView::currentIndex_delayedItemCreation()
     QTRY_VERIFY(contentItem != 0);
 
     QSignalSpy spy(listview, SIGNAL(currentItemChanged()));
-    QCOMPARE(listview->currentIndex(), 0);
+    //QCOMPARE(listview->currentIndex(), 0);
+    listview->forceLayout();
     QTRY_COMPARE(spy.count(), 1);
 
     releaseView(window);
@@ -2536,6 +2547,7 @@ void tst_QQuickListView::currentIndex()
     for (int i = 0; i < 30; i++)
         model.addItem("Item" + QString::number(i), QString::number(i));
     ctxt->setContextProperty("testModel", &model);
+    listview->forceLayout();
 
     QCOMPARE(listview->currentIndex(), 0);
     QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
@@ -2679,6 +2691,7 @@ void tst_QQuickListView::keyNavigation()
         QTRY_COMPARE(listview->currentIndex(), i+1);
     }
     QTest::keyRelease(window, forwardsKey);
+    listview->forceLayout();
     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
     QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
     QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
@@ -2689,6 +2702,7 @@ void tst_QQuickListView::keyNavigation()
         QTRY_COMPARE(listview->currentIndex(), i-1);
     }
     QTest::keyRelease(window, backwardsKey);
+    listview->forceLayout();
     QTRY_COMPARE(listview->currentIndex(), 0);
     QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
     QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
@@ -3123,6 +3137,7 @@ void tst_QQuickListView::resetModel()
     strings << "four" << "five" << "six" << "seven";
     model.setStringList(strings);
 
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.rowCount());
 
     for (int i = 0; i < model.rowCount(); ++i) {
@@ -3470,6 +3485,7 @@ void tst_QQuickListView::header()
     QCOMPARE(item->position(), firstDelegatePos);
 
     model.clear();
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     QCOMPARE(header->position(), initialHeaderPos); // header should stay where it is
     if (orientation == QQuickListView::Vertical)
@@ -3865,6 +3881,7 @@ void tst_QQuickListView::extents()
     QCOMPARE(listview->originY(), origin_empty.y());
     for (int i=0; i<30; i++)
         model.addItem("Item" + QString::number(i), "");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     QCOMPARE(listview->originX(), origin_nonEmpty.x());
     QCOMPARE(listview->originY(), origin_nonEmpty.y());
@@ -4262,6 +4279,7 @@ void tst_QQuickListView::resizeFirstDelegate()
 
     for (int i = 0; i < 10; i++)
         model.addItem("Item" + QString::number(i), "");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     item = findItem<QQuickItem>(contentItem, "wrapper", 1);
@@ -4461,6 +4479,7 @@ void tst_QQuickListView::indexAt_itemAt()
 void tst_QQuickListView::incrementalModel()
 {
     QQuickView *window = createView();
+    QSKIP("QTBUG-30716");
 
     IncrementalModel model;
     QQmlContext *ctxt = window->rootContext();
@@ -4471,14 +4490,17 @@ void tst_QQuickListView::incrementalModel()
 
     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), 20);
 
     listview->positionViewAtIndex(10, QQuickListView::Beginning);
 
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), 25);
 
     delete window;
@@ -4503,18 +4525,19 @@ void tst_QQuickListView::onAdd()
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
 
-    QObject *object = window->rootObject();
-    object->setProperty("width", window->width());
-    object->setProperty("height", window->height());
+    QQuickListView* listview = qobject_cast<QQuickListView*>(window->rootObject());
+    listview->setProperty("width", window->width());
+    listview->setProperty("height", window->height());
     qApp->processEvents();
 
     QList<QPair<QString, QString> > items;
     for (int i=0; i<itemsToAdd; i++)
         items << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.addItems(items);
-    QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
+    listview->forceLayout();
+    QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
-    QVariantList result = object->property("addedDelegates").toList();
+    QVariantList result = listview->property("addedDelegates").toList();
     QCOMPARE(result.count(), items.count());
     for (int i=0; i<items.count(); i++)
         QCOMPARE(result[i].toString(), items[i].first);
@@ -4557,12 +4580,13 @@ void tst_QQuickListView::onRemove()
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
 
-    QObject *object = window->rootObject();
+    QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject());
 
     model.removeItems(indexToRemove, removeCount);
-    QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
+    listview->forceLayout();
+    QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
-    QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
+    QCOMPARE(listview->property("removedDelegateCount"), QVariant(removeCount));
 
     releaseView(window);
 }
@@ -4741,6 +4765,7 @@ void tst_QQuickListView::margins()
     // and originY is updated
     listview->setContentY(100);
     model.removeItem(1);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     listview->setContentY(-50);
     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
@@ -4979,22 +5004,22 @@ void tst_QQuickListView::snapToItem()
 
 void tst_QQuickListView::qAbstractItemModel_package_items()
 {
-    items<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
+    items<QaimModel>(testFileUrl("listviewtest-package.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_items()
 {
-    items<QaimModel>(testFileUrl("listviewtest.qml"), false);
+    items<QaimModel>(testFileUrl("listviewtest.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_package_changed()
 {
-    changed<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
+    changed<QaimModel>(testFileUrl("listviewtest-package.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_changed()
 {
-    changed<QaimModel>(testFileUrl("listviewtest.qml"), false);
+    changed<QaimModel>(testFileUrl("listviewtest.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_package_inserted()
@@ -5555,6 +5580,7 @@ void tst_QQuickListView::populateTransitions()
 
     // clear the model
     window->rootContext()->setContextProperty("testModel", QVariant());
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), 0);
     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
     listview->setProperty("countPopulateTransitions", 0);
@@ -5683,6 +5709,7 @@ void tst_QQuickListView::addTransitions()
     if (!newData.isEmpty()) {
         model.insertItems(insertionIndex, newData);
         QTRY_COMPARE(model.count(), listview->count());
+        listview->forceLayout();
     }
 
     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
@@ -6291,6 +6318,7 @@ void tst_QQuickListView::displacedTransitions()
         case ListChange::Polish:
             break;
     }
+    listview->forceLayout();
 
     QVariantList resultTargetIndexes = listview->property("displacedTargetIndexes").toList();
     QVariantList resultTargetItems = listview->property("displacedTargetItems").toList();
@@ -6539,6 +6567,7 @@ void tst_QQuickListView::multipleTransitions()
                 break;
         }
     }
+    listview->forceLayout();
     QCOMPARE(listview->count(), model.count());
 
     // verify all items moved to the correct final positions
@@ -6822,6 +6851,7 @@ void tst_QQuickListView::accessEmptyCurrentItem_QTBUG_30227()
 
     QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QMetaObject::invokeMethod(window->rootObject(), "remove");
     QVERIFY(window->rootObject()->property("isCurrentItemNull").toBool());
@@ -6830,6 +6860,26 @@ void tst_QQuickListView::accessEmptyCurrentItem_QTBUG_30227()
     QVERIFY(!window->rootObject()->property("isCurrentItemNull").toBool());
 }
 
+void tst_QQuickListView::delayedChanges_QTBUG_30555()
+{
+    QQuickView *window = createView();
+    window->setSource(testFileUrl("delayedChanges.qml"));
+
+    QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
+    QTRY_VERIFY(listview != 0);
+
+    QCOMPARE(listview->count(), 10);
+
+    //Takes two just like in the bug report
+    QMetaObject::invokeMethod(window->rootObject(), "takeTwo");
+    QTRY_COMPARE(listview->count(), 8);
+
+    QMetaObject::invokeMethod(window->rootObject(), "takeTwo_sync");
+    QCOMPARE(listview->count(), 6);
+
+    delete window;
+}
+
 QTEST_MAIN(tst_QQuickListView)
 
 #include "tst_qquicklistview.moc"
index d16bf81..d374d71 100644 (file)
@@ -615,6 +615,7 @@ void tst_qquickvisualdatamodel::childChanged()
     vdm->setRootIndex(QVariant::fromValue(model.indexFromItem(model.item(1,0))));
     QCOMPARE(listview->count(), 1);
 
+    listview->forceLayout();
     QQuickText *name = findItem<QQuickText>(contentItem, "display", 0);
     QVERIFY(name);
     QCOMPARE(name->text(), QString("Row 2 Child Item"));
@@ -628,6 +629,7 @@ void tst_qquickvisualdatamodel::childChanged()
     model.item(1,0)->appendRow(new QStandardItem(QLatin1String("Row 2 Child Item 2")));
     QCOMPARE(listview->count(), 2);
 
+    listview->forceLayout();
     name = findItem<QQuickText>(contentItem, "display", 1);
     QVERIFY(name != 0);
     QCOMPARE(name->text(), QString("Row 2 Child Item 2"));
@@ -638,6 +640,8 @@ void tst_qquickvisualdatamodel::childChanged()
 
     vdm->setRootIndex(QVariant::fromValue(QModelIndex()));
     QCOMPARE(listview->count(), 3);
+
+    listview->forceLayout();
     name = findItem<QQuickText>(contentItem, "display", 0);
     QVERIFY(name);
     QCOMPARE(name->text(), QString("Row 1 Item"));
@@ -989,6 +993,8 @@ void tst_qquickvisualdatamodel::packagesDestroyed()
     QQuickItem *rightContent = rightview->contentItem();
     QTRY_VERIFY(rightContent != 0);
 
+    leftview->forceLayout();
+    rightview->forceLayout();
     QCOMPARE(leftview->currentIndex(), 0);
     QCOMPARE(rightview->currentIndex(), 0);
 
@@ -3548,6 +3554,7 @@ void tst_qquickvisualdatamodel::resolve()
     evaluate<void>(visualModel, setupExpression);
     QCOMPARE(evaluate<int>(listView, "count"), unresolvedCount);
 
+    listView->forceLayout();
     evaluate<void>(visualModel, resolveExpression);
 
     QCOMPARE(evaluate<int>(listView, "count"), inItems ? visualCount : modelCount);