diff options
-rw-r--r-- | src/quick/items/qquicktableview.cpp | 113 | ||||
-rw-r--r-- | src/quick/items/qquicktableview_p_p.h | 20 | ||||
-rw-r--r-- | tests/auto/quick/qquicktableview/tst_qquicktableview.cpp | 42 |
3 files changed, 134 insertions, 41 deletions
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index fd8af19805..8dce0149f6 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -805,7 +805,7 @@ void QQuickTableViewPrivate::cancelLoadRequest() loadRequest.markAsDone(); model->cancel(modelIndexAtCell(loadRequest.currentCell())); - if (tableInvalid) { + if (rebuildState == RebuildState::NotStarted) { // No reason to rollback already loaded edge items // since we anyway are about to reload all items. return; @@ -857,13 +857,77 @@ void QQuickTableViewPrivate::processLoadRequest() qCDebug(lcTableViewDelegateLifecycle()) << "request completed! Table:" << tableLayoutToString(); } +void QQuickTableViewPrivate::processRebuildTable() +{ + moveToNextRebuildState(); + + if (rebuildState == RebuildState::LoadInitalTable) { + beginRebuildTable(); + if (!moveToNextRebuildState()) + return; + } + + if (rebuildState == RebuildState::VerifyTable) { + if (loadedItems.isEmpty()) { + qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model or no delegate"; + rebuildState = RebuildState::Done; + return; + } + if (!moveToNextRebuildState()) + return; + } + + if (rebuildState == RebuildState::LayoutTable) { + layoutAfterLoadingInitialTable(); + if (!moveToNextRebuildState()) + return; + } + + if (rebuildState == RebuildState::LoadAndUnloadAfterLayout) { + loadAndUnloadVisibleEdges(); + if (!moveToNextRebuildState()) + return; + } + + if (rebuildState == RebuildState::PreloadColumns) { + if (loadedTable.right() < tableSize.width() - 1) + loadEdge(Qt::RightEdge, QQmlIncubator::AsynchronousIfNested); + if (!moveToNextRebuildState()) + return; + } + + if (rebuildState == RebuildState::PreloadRows) { + if (loadedTable.bottom() < tableSize.height() - 1) + loadEdge(Qt::BottomEdge, QQmlIncubator::AsynchronousIfNested); + if (!moveToNextRebuildState()) + return; + } + + if (rebuildState == RebuildState::MovePreloadedItemsToPool) { + while (Qt::Edge edge = nextEdgeToUnload(viewportRect)) + unloadEdge(edge); + if (!moveToNextRebuildState()) + return; + } + + Q_TABLEVIEW_ASSERT(rebuildState == RebuildState::Done, int(rebuildState)); +} + +bool QQuickTableViewPrivate::moveToNextRebuildState() +{ + if (loadRequest.isActive()) { + // Items are still loading async, which means + // that the current state is not yet done. + return false; + } + rebuildState = RebuildState(int(rebuildState) + 1); + qCDebug(lcTableViewDelegateLifecycle()) << int(rebuildState); + return true; +} + void QQuickTableViewPrivate::beginRebuildTable() { Q_Q(QQuickTableView); - qCDebug(lcTableViewDelegateLifecycle()); - - tableInvalid = false; - tableRebuilding = true; releaseLoadedItems(); loadedTable = QRect(); @@ -879,21 +943,16 @@ void QQuickTableViewPrivate::beginRebuildTable() loadAndUnloadVisibleEdges(); } -void QQuickTableViewPrivate::endRebuildTable() +void QQuickTableViewPrivate::layoutAfterLoadingInitialTable() { - tableRebuilding = false; - - if (rowHeightProvider.isNull() && columnWidthProvider.isNull()) { - // Since we have no size providers, we need to calculate the size - // of each row and column based on the size of the delegate items. + if (rowHeightProvider.isNull() || columnWidthProvider.isNull()) { + // Since we don't have both size providers, we need to calculate the + // size of each row and column based on the size of the delegate items. // This couldn't be done while we were loading the initial rows and // columns, since during the process, we didn't have all the items - // available yet for the calculation. So we mark that it needs to be - // done now, from within updatePolish(). - columnRowPositionsInvalid = true; + // available yet for the calculation. So we do it now. + relayoutTable(); } - - qCDebug(lcTableViewDelegateLifecycle()) << tableLayoutToString(); } void QQuickTableViewPrivate::loadInitialTopLeftItem() @@ -1020,7 +1079,7 @@ void QQuickTableViewPrivate::drainReusePoolAfterLoadRequest() } void QQuickTableViewPrivate::invalidateTable() { - tableInvalid = true; + rebuildState = RebuildState::NotStarted; if (loadRequest.isActive()) cancelLoadRequest(); q_func()->polish(); @@ -1056,19 +1115,13 @@ void QQuickTableViewPrivate::updatePolish() if (!viewportRect.isValid()) return; - if (tableInvalid) { - beginRebuildTable(); - if (loadRequest.isActive()) - return; + if (rebuildState != RebuildState::Done) { + processRebuildTable(); + return; } - if (tableRebuilding) - endRebuildTable(); - - if (loadedItems.isEmpty()) { - qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model or no delegate"; + if (loadedItems.isEmpty()) return; - } if (columnRowPositionsInvalid) relayoutTable(); @@ -1499,7 +1552,8 @@ qreal QQuickTableView::explicitContentWidth() const { Q_D(const QQuickTableView); - if (d->tableInvalid && d->explicitContentWidth.isNull) { + if (d->rebuildState == QQuickTableViewPrivate::RebuildState::NotStarted + && d->explicitContentWidth.isNull) { // The table is pending to be rebuilt. Since we don't // know the contentWidth before this is done, we do the // rebuild now, instead of waiting for the polish event. @@ -1523,7 +1577,8 @@ qreal QQuickTableView::explicitContentHeight() const { Q_D(const QQuickTableView); - if (d->tableInvalid && d->explicitContentHeight.isNull) { + if (d->rebuildState == QQuickTableViewPrivate::RebuildState::NotStarted + && d->explicitContentHeight.isNull) { // The table is pending to be rebuilt. Since we don't // know the contentHeight before this is done, we do the // rebuild now, instead of waiting for the polish event. diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 936d8b8207..53fd936195 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -163,6 +163,18 @@ public: } }; + enum class RebuildState { + NotStarted = 0, + LoadInitalTable, + VerifyTable, + LayoutTable, + LoadAndUnloadAfterLayout, + PreloadColumns, + PreloadRows, + MovePreloadedItemsToPool, + Done + }; + public: QQuickTableViewPrivate(); ~QQuickTableViewPrivate() override; @@ -196,6 +208,7 @@ public: QSize tableSize; + RebuildState rebuildState = RebuildState::NotStarted; TableEdgeLoadRequest loadRequest; QPoint contentSizeBenchMarkPoint = QPoint(-1, -1); @@ -205,8 +218,6 @@ public: QQmlTableInstanceModel::ReusableFlag reusableFlag = QQmlTableInstanceModel::Reusable; bool blockItemCreatedCallback = false; - bool tableInvalid = false; - bool tableRebuilding = false; bool columnRowPositionsInvalid = false; bool layoutWarningIssued = false; bool polishing = false; @@ -289,8 +300,11 @@ public: void drainReusePoolAfterLoadRequest(); void cancelLoadRequest(); void processLoadRequest(); + + void processRebuildTable(); + bool moveToNextRebuildState(); void beginRebuildTable(); - void endRebuildTable(); + void layoutAfterLoadingInitialTable(); void invalidateTable(); void invalidateColumnRowPositions(); diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 69bed8be43..09c2850247 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -85,6 +85,7 @@ private slots: void setAndGetModel(); void emptyModel_data(); void emptyModel(); + void checkPreload(); void checkZeroSizedDelegate(); void checkImplicitSizeDelegate(); void checkColumnWidthWithoutProvider(); @@ -191,6 +192,22 @@ void tst_QQuickTableView::emptyModel() QCOMPARE(tableViewPrivate->loadedItems.count(), 0); } +void tst_QQuickTableView::checkPreload() +{ + // Check that the reuse pool is filled up with one extra row and + // column (pluss corner) after rebuilding the table. + LOAD_TABLEVIEW("plaintableview.qml"); + + auto model = TestModelAsVariant(100, 100); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + QSize visibleTableSize = tableViewPrivate->loadedTable.size(); + int expectedPoolSize = visibleTableSize.height() + visibleTableSize.width() + 1; + QCOMPARE(tableViewPrivate->tableModel->poolSize(), expectedPoolSize); +} + void tst_QQuickTableView::checkZeroSizedDelegate() { // Check that if we assign a delegate with empty width and height, we @@ -1127,9 +1144,12 @@ void tst_QQuickTableView::checkRowColumnCount() WAIT_UNTIL_POLISHED; - const int tableViewCount = tableViewPrivate->loadedItems.count(); + // We expect that the number of created items after start-up should match + //the size of the visible table, pluss one extra preloaded row and column. + const QSize visibleTableSize = tableViewPrivate->loadedTable.size(); const int qmlCountAfterInit = view->rootObject()->property(maxDelegateCountProp).toInt(); - QCOMPARE(tableViewCount, qmlCountAfterInit); + const int expectedCount = (visibleTableSize.width() + 1) * (visibleTableSize.height() + 1); + QCOMPARE(qmlCountAfterInit, expectedCount); // This test will keep track of the maximum number of delegate items TableView // had to show at any point while flicking (in countingtableview.qml). Because @@ -1304,6 +1324,7 @@ void tst_QQuickTableView::checkIfDelegatesAreReused() LOAD_TABLEVIEW("countingtableview.qml"); const qreal delegateWidth = 100; + const qreal delegateHeight = 50; const int pageFlickCount = 4; auto model = TestModelAsVariant(100, 100); @@ -1312,13 +1333,20 @@ void tst_QQuickTableView::checkIfDelegatesAreReused() WAIT_UNTIL_POLISHED; - // Flick half an item to the left, to force one extra column to load before we start. + // Flick half an item to the side, to force one extra row and column to load before we start. // This will make things less complicated below, when checking how many times the items // have been reused (all items will then report the same number). tableView->setContentX(delegateWidth / 2); - tableView->polish(); + tableView->setContentY(delegateHeight / 2); + QCOMPARE(tableViewPrivate->tableModel->poolSize(), 0); - WAIT_UNTIL_POLISHED; + // Some items have already been pooled and reused after we moved the content view, because + // we preload one extra row and column at start-up. So reset the count-properties back to 0 + // before we continue. + for (auto fxItem : tableViewPrivate->loadedItems) { + fxItem->item->setProperty("pooledCount", 0); + fxItem->item->setProperty("reusedCount", 0); + } const int visibleColumnCount = tableViewPrivate->loadedTable.width(); const int visibleRowCount = tableViewPrivate->loadedTable.height(); @@ -1327,10 +1355,6 @@ void tst_QQuickTableView::checkIfDelegatesAreReused() for (int column = 1; column <= (visibleColumnCount * pageFlickCount); ++column) { // Flick columns to the left (and add one pixel to ensure the left column is completely out) tableView->setContentX((delegateWidth * column) + 1); - tableView->polish(); - - WAIT_UNTIL_POLISHED; - // Check that the number of delegate items created so far is what we expect. const int delegatesCreatedCount = view->rootObject()->property(kDelegatesCreatedCountProp).toInt(); int expectedCount = delegateCountAfterInit + (reuseItems ? 0 : visibleRowCount * column); |