From 93818a359973003531e2a5ef2d55cf58b3d440d9 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 22 Mar 2019 14:43:23 +0100 Subject: QQuickTableView: add new 'syncView' property This property can be set to point to another TableView. If set, this TableView will be synchronized to the other table with respect to flicking, column width, row heights, spacing, etc. This logic is needed as a foundation for the upcoming HeaderView. Upcoming patches will implement this logic (together with autotests) gradually. Change-Id: Ic7dea8e1d1aa46bbb3ea6e795953a65c96c25cc6 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 1dca5bea4a..fc7516ec66 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1855,9 +1855,14 @@ void QQuickTableViewPrivate::syncWithPendingChanges() // such assignments into effect until we're in a state that allows it. Q_Q(QQuickTableView); viewportRect = QRectF(q->contentX(), q->contentY(), q->width(), q->height()); + + // Sync rebuild options first, in case we schedule a rebuild from one of the + // other sync calls above. If so, we need to start a new rebuild from the top. syncRebuildOptions(); + syncModel(); syncDelegate(); + syncSyncView(); } void QQuickTableViewPrivate::syncRebuildOptions() @@ -1922,6 +1927,41 @@ void QQuickTableViewPrivate::syncModel() connectToModel(); } +void QQuickTableViewPrivate::syncSyncView() +{ + Q_Q(QQuickTableView); + + if (assignedSyncView != syncView) { + if (syncView) + syncView->d_func()->syncChildren.removeOne(q); + + if (assignedSyncView) { + QQuickTableView *view = assignedSyncView; + + while (view) { + if (view == q) { + if (!layoutWarningIssued) { + layoutWarningIssued = true; + qmlWarning(q) << "TableView: recursive syncView connection detected!"; + } + syncView = nullptr; + return; + } + view = view->d_func()->syncView; + } + + assignedSyncView->d_func()->syncChildren.append(q); + scheduledRebuildOptions |= RebuildOption::ViewportOnly; + q->polish(); + } + + syncView = assignedSyncView; + } + + syncHorizontally = syncView && assignedSyncDirection & Qt::Horizontal; + syncVertically = syncView && assignedSyncDirection & Qt::Vertical; +} + void QQuickTableViewPrivate::connectToModel() { Q_TABLEVIEW_ASSERT(model, ""); @@ -2214,6 +2254,41 @@ void QQuickTableView::setContentHeight(qreal height) QQuickFlickable::setContentHeight(height); } +QQuickTableView *QQuickTableView::syncView() const +{ + return d_func()->assignedSyncView; +} + +void QQuickTableView::setSyncView(QQuickTableView *view) +{ + Q_D(QQuickTableView); + if (d->assignedSyncView == view) + return; + + d->assignedSyncView = view; + d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly); + + emit syncViewChanged(); +} + +Qt::Orientations QQuickTableView::syncDirection() const +{ + return d_func()->assignedSyncDirection; +} + +void QQuickTableView::setSyncDirection(Qt::Orientations direction) +{ + Q_D(QQuickTableView); + if (d->assignedSyncDirection == direction) + return; + + d->assignedSyncDirection = direction; + if (d->assignedSyncView) + d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly); + + emit syncDirectionChanged(); +} + void QQuickTableView::forceLayout() { d_func()->forceLayout(); -- cgit v1.2.3 From 2821172d514469f99301c3592eec941db9ebc438 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Tue, 9 Apr 2019 11:40:10 +0200 Subject: Doc: improve Example Usage section of TableView - Make two sub-sections: C++ and QML - Add a TableModel example to the QML section Change-Id: Ib391b4c0a78e11f5130944b6ac99e20a5982a453 Reviewed-by: Richard Moe Gustavsen Reviewed-by: Venugopal Shivashankar --- src/quick/items/qquicktableview.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index fc7516ec66..5437b54b78 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -76,14 +76,23 @@ \section1 Example Usage + \section2 C++ Models + The following example shows how to create a model from C++ with multiple columns: - \snippet qml/tableview/tablemodel.cpp 0 + \snippet qml/tableview/cpp-tablemodel.cpp 0 And then how to use it from QML: - \snippet qml/tableview/tablemodel.qml 0 + \snippet qml/tableview/cpp-tablemodel.qml 0 + + \section2 QML Models + + For prototyping and displaying very simple data (from a web API, for + example), \l TableModel can be used: + + \snippet qml/tableview/qml-tablemodel.qml 0 \section1 Reusing items -- cgit v1.2.3 From b68554d4d453eab01203384f14dd2158e520ecd3 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Mon, 25 Mar 2019 12:47:15 +0100 Subject: QQuickTableView: sync geometry properties with syncView Ensure that properties that has to do with the layout stays in sync with the syncView. This is currently rowSpacing, columnSpacing, rowHeight, columnWidth, contentWidth and contentHeight. Change-Id: I5af29d7be6c30cefbfa7d2353f53359907c9405b Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 5437b54b78..82702b1a8f 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -615,6 +615,11 @@ void QQuickTableViewPrivate::updateContentWidth() { Q_Q(QQuickTableView); + if (syncHorizontally) { + q->QQuickFlickable::setContentWidth(syncView->contentWidth()); + return; + } + if (explicitContentWidth.isValid()) { // Don't calculate contentWidth when it // was set explicitly by the application. @@ -634,6 +639,11 @@ void QQuickTableViewPrivate::updateContentHeight() { Q_Q(QQuickTableView); + if (syncVertically) { + q->QQuickFlickable::setContentHeight(syncView->contentHeight()); + return; + } + if (explicitContentHeight.isValid()) { // Don't calculate contentHeight when it // was set explicitly by the application. @@ -1057,6 +1067,11 @@ qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column) if (explicitColumnWidth >= 0) return explicitColumnWidth; + if (syncHorizontally) { + if (syncView->d_func()->loadedColumns.contains(column)) + return syncView->d_func()->getColumnLayoutWidth(column); + } + // Iterate over the currently visible items in the column. The downside // of doing that, is that the column width will then only be based on the implicit // width of the currently loaded items (which can be different depending on which @@ -1086,6 +1101,11 @@ qreal QQuickTableViewPrivate::getRowLayoutHeight(int row) if (explicitRowHeight >= 0) return explicitRowHeight; + if (syncVertically) { + if (syncView->d_func()->loadedRows.contains(row)) + return syncView->d_func()->getRowLayoutHeight(row); + } + // Iterate over the currently visible items in the row. The downside // of doing that, is that the row height will then only be based on the implicit // height of the currently loaded items (which can be different depending on which @@ -1115,6 +1135,9 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) if (cachedColumnWidth.startIndex == column) return cachedColumnWidth.size; + if (syncHorizontally) + return syncView->d_func()->getColumnWidth(column); + if (columnWidthProvider.isUndefined()) return noExplicitColumnWidth; @@ -1149,6 +1172,9 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) if (cachedRowHeight.startIndex == row) return cachedRowHeight.size; + if (syncVertically) + return syncView->d_func()->getRowHeight(row); + if (rowHeightProvider.isUndefined()) return noExplicitRowHeight; @@ -1969,6 +1995,11 @@ void QQuickTableViewPrivate::syncSyncView() syncHorizontally = syncView && assignedSyncDirection & Qt::Horizontal; syncVertically = syncView && assignedSyncDirection & Qt::Vertical; + + if (syncHorizontally) + q->setColumnSpacing(syncView->columnSpacing()); + if (syncVertically) + q->setRowSpacing(syncView->rowSpacing()); } void QQuickTableViewPrivate::connectToModel() -- cgit v1.2.3 From 679642fcfaac25bf56613df0bf25afa11ed0d01b Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 29 Mar 2019 15:33:58 +0100 Subject: QQuickTableView: implement recursive updateTable() Now that a TableView can be inside a syncView hierarchy, we cannot update a table in isolation, but need to coordinate this with the other views. It's especially important that we update a parent syncView before a child syncView, to ensure that the parent has calculated all the necessary columns width and row heights. For that reason, we always update the table views starting from the top. Change-Id: Iba8ae7d28fa0bb2fbbad9f8fc7aa198e15b91872 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 82702b1a8f..8c64c11bb1 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1768,7 +1768,51 @@ void QQuickTableViewPrivate::scheduleRebuildTable(RebuildOptions options) { q_func()->polish(); } +QQuickTableView *QQuickTableViewPrivate::rootSyncView() const +{ + QQuickTableView *root = const_cast(q_func()); + while (QQuickTableView *view = root->d_func()->syncView) + root = view; + return root; +} + void QQuickTableViewPrivate::updatePolish() +{ + // We always start updating from the top of the syncView tree, since + // the layout of a syncView child will depend on the layout of the syncView. + // E.g when a new column is flicked in, the syncView should load and layout + // the column first, before any syncChildren gets a chance to do the same. + Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!"); + rootSyncView()->d_func()->updateTableRecursive(); +} + +bool QQuickTableViewPrivate::updateTableRecursive() +{ + if (polishing) { + // We're already updating the Table in this view, so + // we cannot continue. Signal this back by returning false. + // The caller can then choose to call "polish()" instead, to + // do the update later. + return false; + } + + updateTable(); + + for (auto syncChild : qAsConst(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + syncChild_d->scheduledRebuildOptions |= rebuildOptions; + + const bool updated = syncChild_d->updateTableRecursive(); + if (!updated) + return false; + } + + rebuildOptions = RebuildOption::None; + + return true; +} + +void QQuickTableViewPrivate::updateTable() { // Whenever something changes, e.g viewport moves, spacing is set to a // new value, model changes etc, this function will end up being called. Here -- cgit v1.2.3 From ef6c58722e6a639a6b48efba007b2f59822b826e Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Tue, 9 Apr 2019 14:55:27 +0200 Subject: QQuickTableView: update viewportMoved() to take syncView into account Now that several table views can stay in sync through the syncView parent-child chain, we also need to ensure that the position of the content views stays in sync. This patch will recursively go through all connected views when one of the views are moved and set the same position on them all according to the syncDirection flag. Change-Id: I5a5b8e795426484eeab3771f6c8d4c9b7da046eb Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 150 +++++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 35 deletions(-) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 8c64c11bb1..da097f8888 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1583,6 +1583,11 @@ void QQuickTableViewPrivate::beginRebuildTable() loadedTableInnerRect = QRect(); clearEdgeSizeCache(); + if (syncHorizontally) + setLocalViewportX(syncView->contentX()); + if (syncVertically) + setLocalViewportY(syncView->contentY()); + if (!model) { qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty"; return; @@ -2175,6 +2180,84 @@ void QQuickTableViewPrivate::modelResetCallback() scheduleRebuildTable(RebuildOption::All); } +void QQuickTableViewPrivate::scheduleRebuildIfFastFlick() +{ + Q_Q(QQuickTableView); + + // If the viewport has moved more than one page vertically or horizontally, we switch + // strategy from refilling edges around the current table to instead rebuild the table + // from scratch inside the new viewport. This will greatly improve performance when flicking + // a long distance in one go, which can easily happen when dragging on scrollbars. + + // Check the viewport moved more than one page vertically + if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) { + scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow; + scheduledRebuildOptions |= RebuildOption::ViewportOnly; + } + + // Check the viewport moved more than one page horizontally + if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) { + scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn; + scheduledRebuildOptions |= RebuildOption::ViewportOnly; + } +} + +void QQuickTableViewPrivate::setLocalViewportX(qreal contentX) +{ + // Set the new viewport position if changed, but don't trigger any + // rebuilds or updates. We use this function internally to distinguish + // external flicking from internal sync-ing of the content view. + Q_Q(QQuickTableView); + QBoolBlocker blocker(inSetLocalViewportPos, true); + + if (qFuzzyCompare(contentX, q->contentX())) + return; + + q->setContentX(contentX); +} + +void QQuickTableViewPrivate::setLocalViewportY(qreal contentY) +{ + // Set the new viewport position if changed, but don't trigger any + // rebuilds or updates. We use this function internally to distinguish + // external flicking from internal sync-ing of the content view. + Q_Q(QQuickTableView); + QBoolBlocker blocker(inSetLocalViewportPos, true); + + if (qFuzzyCompare(contentY, q->contentY())) + return; + + q->setContentY(contentY); +} + +void QQuickTableViewPrivate::syncViewportPosRecursive() +{ + Q_Q(QQuickTableView); + QBoolBlocker recursionGuard(inSyncViewportPosRecursive, true); + + if (syncView) { + auto syncView_d = syncView->d_func(); + if (!syncView_d->inSyncViewportPosRecursive) { + if (syncHorizontally) + syncView_d->setLocalViewportX(q->contentX()); + if (syncVertically) + syncView_d->setLocalViewportY(q->contentY()); + syncView_d->syncViewportPosRecursive(); + } + } + + for (auto syncChild : qAsConst(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + if (!syncChild_d->inSyncViewportPosRecursive) { + if (syncChild_d->syncHorizontally) + syncChild_d->setLocalViewportX(q->contentX()); + if (syncChild_d->syncVertically) + syncChild_d->setLocalViewportY(q->contentY()); + syncChild_d->syncViewportPosRecursive(); + } + } +} + QQuickTableView::QQuickTableView(QQuickItem *parent) : QQuickFlickable(*(new QQuickTableViewPrivate), parent) { @@ -2400,45 +2483,42 @@ void QQuickTableView::geometryChanged(const QRectF &newGeometry, const QRectF &o void QQuickTableView::viewportMoved(Qt::Orientations orientation) { Q_D(QQuickTableView); + + // If the new viewport position was set from the setLocalViewportXY() + // functions, we just update the position silently and return. Otherwise, if + // the viewport was flicked by the user, or some other control, we + // recursively sync all the views in the hierarchy to the same position. QQuickFlickable::viewportMoved(orientation); + if (d->inSetLocalViewportPos) + return; - QQuickTableViewPrivate::RebuildOptions options = QQuickTableViewPrivate::RebuildOption::None; + // Move all views in the syncView hierarchy to the same contentX/Y. + // We need to start from this view (and not the root syncView) to + // ensure that we respect all the individual syncDirection flags + // between the individual views in the hierarchy. + d->syncViewportPosRecursive(); - // Check the viewport moved more than one page vertically - if (!d->viewportRect.intersects(QRectF(d->viewportRect.x(), contentY(), 1, height()))) - options |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow; - // Check the viewport moved more than one page horizontally - if (!d->viewportRect.intersects(QRectF(contentX(), d->viewportRect.y(), width(), 1))) - options |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn; - - if (options) { - // When the viewport has moved more than one page vertically or horizontally, we switch - // strategy from refilling edges around the current table to instead rebuild the table - // from scratch inside the new viewport. This will greatly improve performance when flicking - // a long distance in one go, which can easily happen when dragging on scrollbars. - options |= QQuickTableViewPrivate::RebuildOption::ViewportOnly; - d->scheduleRebuildTable(options); - } - - if (d->scheduledRebuildOptions) { - // No reason to do anything, since we're about to rebuild the whole table anyway. - // Besides, calling updatePolish, which will start the rebuild, can easily cause - // binding loops to happen since we usually end up modifying the geometry of the - // viewport (contentItem) as well. - return; - } + auto rootView = d->rootSyncView(); + auto rootView_d = rootView->d_func(); - // Calling polish() will schedule a polish event. But while the user is flicking, several - // mouse events will be handled before we get an updatePolish() call. And the updatePolish() - // call will only see the last mouse position. This results in a stuttering flick experience - // (especially on windows). We improve on this by calling updatePolish() directly. But this - // has the pitfall that we open up for recursive callbacks. E.g while inside updatePolish(), we - // load/unload items, and emit signals. The application can listen to those signals and set a - // new contentX/Y on the flickable. So we need to guard for this, to avoid unexpected behavior. - if (d->polishing) - polish(); - else - d->updatePolish(); + rootView_d->scheduleRebuildIfFastFlick(); + + if (!rootView_d->polishScheduled) { + if (rootView_d->scheduledRebuildOptions) { + // When we need to rebuild, collecting several viewport + // moves and do a single polish gives a quicker UI. + rootView->polish(); + } else { + // Updating the table right away when flicking + // slowly gives a smoother experience. + const bool updated = rootView->d_func()->updateTableRecursive(); + if (!updated) { + // One, or more, of the views are already in an + // update, so we need to wait a cycle. + rootView->polish(); + } + } + } } void QQuickTableViewPrivate::_q_componentFinalized() -- cgit v1.2.3 From 0bcaed279fc303ffd0fd6e77b0ebc83a4519ac74 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Wed, 27 Mar 2019 13:23:31 +0100 Subject: QQuickTableView: update calculateTopLeft() to take syncView into account calculateTopLeft() takes care of finding which cell should be the 'corner stone' that needs to be loaded first when doing a rebuild. When we have a syncView, the top left cell should match the top left cell of the syncView, so the logic needs to change quite a bit to take this into account. Change-Id: Ia0b621a3155bbd113fa37c2ed585f16627d46443 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 129 +++++++++++++++++++++++++++--------- 1 file changed, 97 insertions(+), 32 deletions(-) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index da097f8888..c30d40138e 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1524,48 +1524,103 @@ bool QQuickTableViewPrivate::moveToNextRebuildState() return true; } -QPoint QQuickTableViewPrivate::calculateNewTopLeft() -{ - const int firstVisibleLeft = nextVisibleEdgeIndex(Qt::RightEdge, 0); - const int firstVisibleTop = nextVisibleEdgeIndex(Qt::BottomEdge, 0); - - return QPoint(firstVisibleLeft, firstVisibleTop); -} - -void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos) +void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topLeftPos) { if (tableSize.isEmpty()) { - releaseLoadedItems(QQmlTableInstanceModel::NotReusable); - topLeft = QPoint(kEdgeIndexAtEnd, kEdgeIndexAtEnd); + // There is no cell that can be top left + topLeftCell.rx() = kEdgeIndexAtEnd; + topLeftCell.ry() = kEdgeIndexAtEnd; return; } - if (rebuildOptions & RebuildOption::All) { - qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::All"; - releaseLoadedItems(QQmlTableInstanceModel::NotReusable); - topLeft = calculateNewTopLeft(); - } else if (rebuildOptions & RebuildOption::ViewportOnly) { - qCDebug(lcTableViewDelegateLifecycle()) << "RebuildOption::ViewportOnly"; - releaseLoadedItems(reusableFlag); + if (syncHorizontally || syncVertically) { + const auto syncView_d = syncView->d_func(); - if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) { - const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height())); - topLeft.ry() = qBound(0, newRow, tableSize.height() - 1); - topLeftPos.ry() = topLeft.y() * (averageEdgeSize.height() + cellSpacing.height()); - } else { - topLeft.ry() = qBound(0, topRow(), tableSize.height() - 1); - topLeftPos.ry() = loadedTableOuterRect.topLeft().y(); + if (syncView_d->loadedItems.isEmpty()) { + // The sync view contains no loaded items. This probably means + // that it has not been rebuilt yet. Which also means that + // we cannot rebuild anything before this happens. + topLeftCell.rx() = kEdgeIndexNotSet; + topLeftCell.ry() = kEdgeIndexNotSet; + return; } - if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) { + + // Get sync view top left, and use that as our own top left (if possible) + const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow()); + const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell); + const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft(); + + if (syncHorizontally) { + topLeftCell.rx() = syncViewTopLeftCell.x(); + topLeftPos.rx() = syncViewTopLeftPos.x(); + + if (topLeftCell.x() >= tableSize.width()) { + // Top left is outside our own model. + topLeftCell.rx() = kEdgeIndexAtEnd; + topLeftPos.rx() = kEdgeIndexAtEnd; + } + } + + if (syncVertically) { + topLeftCell.ry() = syncViewTopLeftCell.y(); + topLeftPos.ry() = syncViewTopLeftPos.y(); + + if (topLeftCell.y() >= tableSize.height()) { + // Top left is outside our own model. + topLeftCell.ry() = kEdgeIndexAtEnd; + topLeftPos.ry() = kEdgeIndexAtEnd; + } + } + + if (syncHorizontally && syncVertically) { + // We have a valid top left, so we're done + return; + } + } + + // Since we're not sync-ing both horizontal and vertical, calculate the missing + // dimention(s) ourself. If we rebuild all, we find the first visible top-left + // item starting from cell(0, 0). Otherwise, guesstimate which row or column that + // should be the new top-left given the geometry of the viewport. + + if (!syncHorizontally) { + if (rebuildOptions & RebuildOption::All) { + // Find the first visible column from the beginning + topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0); + if (topLeftCell.x() == kEdgeIndexAtEnd) { + // No visible column found + return; + } + } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) { + // Guesstimate new top left const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width())); - topLeft.rx() = qBound(0, newColumn, tableSize.width() - 1); - topLeftPos.rx() = topLeft.x() * (averageEdgeSize.width() + cellSpacing.width()); + topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1); + topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width()); } else { - topLeft.rx() = qBound(0, leftColumn(), tableSize.width() - 1); + // Keep the current top left, unless it's outside model + topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1); topLeftPos.rx() = loadedTableOuterRect.topLeft().x(); } - } else { - Q_TABLEVIEW_UNREACHABLE(rebuildOptions); + } + + if (!syncVertically) { + if (rebuildOptions & RebuildOption::All) { + // Find the first visible row from the beginning + topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0); + if (topLeftCell.y() == kEdgeIndexAtEnd) { + // No visible row found + return; + } + } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) { + // Guesstimate new top left + const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height())); + topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1); + topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height()); + } else { + // Keep the current top left, unless it's outside model + topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1); + topLeftPos.ry() = loadedTableOuterRect.topLeft().y(); + } } } @@ -1577,6 +1632,11 @@ void QQuickTableViewPrivate::beginRebuildTable() QPointF topLeftPos; calculateTopLeft(topLeft, topLeftPos); + if (rebuildOptions & RebuildOption::All) + releaseLoadedItems(QQmlTableInstanceModel::NotReusable); + else if (rebuildOptions & RebuildOption::ViewportOnly) + releaseLoadedItems(reusableFlag); + loadedColumns.clear(); loadedRows.clear(); loadedTableOuterRect = QRect(); @@ -1604,7 +1664,12 @@ void QQuickTableViewPrivate::beginRebuildTable() } if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) { - qCDebug(lcTableViewDelegateLifecycle()) << "no visible rows or columns, leaving table empty"; + qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty"; + return; + } + + if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) { + qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty"; return; } -- cgit v1.2.3 From 8fb8cfd040a5fa8c4caa5715efe1328da9c49536 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Wed, 24 Apr 2019 11:46:37 +0200 Subject: QQuickTableView: avoid building syncView children before syncView has finished If you put two tables inside an async loader, with one being the syncView for the other, the syncView child will start loading items async simultaneously with the syncView. This is unnecessary, and steals loading resources, since the child will have to rebuild anyway once the syncView has completed loading. So return early from the recursiveUpdateTable call before handling the children if we detect that the parent is not done. Change-Id: I8c0badaf3cfa3a353a650e5f38f381bf9a7b98f9 Reviewed-by: Mitch Curtis --- src/quick/items/qquicktableview.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index c30d40138e..60bf42fb10 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1866,14 +1866,16 @@ bool QQuickTableViewPrivate::updateTableRecursive() return false; } - updateTable(); + const bool updateComplete = updateTable(); + if (!updateComplete) + return false; for (auto syncChild : qAsConst(syncChildren)) { auto syncChild_d = syncChild->d_func(); syncChild_d->scheduledRebuildOptions |= rebuildOptions; - const bool updated = syncChild_d->updateTableRecursive(); - if (!updated) + const bool descendantUpdateComplete = syncChild_d->updateTableRecursive(); + if (!descendantUpdateComplete) return false; } @@ -1882,11 +1884,13 @@ bool QQuickTableViewPrivate::updateTableRecursive() return true; } -void QQuickTableViewPrivate::updateTable() +bool QQuickTableViewPrivate::updateTable() { // Whenever something changes, e.g viewport moves, spacing is set to a // new value, model changes etc, this function will end up being called. Here // we check what needs to be done, and load/unload cells accordingly. + // If we cannot complete the update (because we need to wait for an item + // to load async), we return false. Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!"); QBoolBlocker polishGuard(polishing, true); @@ -1896,25 +1900,27 @@ void QQuickTableViewPrivate::updateTable() // as an atomic operation, which means that we don't continue doing anything else until all // items have been received and laid out. Note that updatePolish is then called once more // after the loadRequest has completed to handle anything that might have occurred in-between. - return; + return false; } if (rebuildState != RebuildState::Done) { processRebuildTable(); - return; + return rebuildState == RebuildState::Done; } syncWithPendingChanges(); if (rebuildState == RebuildState::Begin) { processRebuildTable(); - return; + return rebuildState == RebuildState::Done; } if (loadedItems.isEmpty()) - return; + return !loadRequest.isActive(); loadAndUnloadVisibleEdges(); + + return !loadRequest.isActive(); } void QQuickTableViewPrivate::fixup(QQuickFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent) -- cgit v1.2.3 From d7db2ef07bf6f0f7c39f342aed94d51cca42df11 Mon Sep 17 00:00:00 2001 From: Yulong Bai Date: Wed, 10 Apr 2019 15:00:33 +0200 Subject: Add QQuickTableSectionSizeProvider for QQuickTableView Used to store columnWidths and rowHeights. Change-Id: Id66fba9de05afa2c4df15761fb004b4f046fe103 Reviewed-by: Richard Moe Gustavsen --- src/quick/items/qquicktableview.cpp | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 60bf42fb10..5e7e0db154 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -2625,6 +2625,71 @@ void QQuickTableView::componentComplete() d_func()->registerCallbackWhenBindingsAreEvaluated(); } +class QObjectPrivate; +class QQuickTableSectionSizeProviderPrivate : public QObjectPrivate { +public: + QQuickTableSectionSizeProviderPrivate(); + ~QQuickTableSectionSizeProviderPrivate(); + QHash hash; +}; + +QQuickTableSectionSizeProvider::QQuickTableSectionSizeProvider(QObject *parent) + : QObject (*(new QQuickTableSectionSizeProviderPrivate), parent) +{ +} + +void QQuickTableSectionSizeProvider::setSize(int section, qreal size) +{ + Q_D(QQuickTableSectionSizeProvider); + if (section < 0 || size < 0) { + qmlWarning(this) << "setSize: section or size less than zero"; + return; + } + if (qFuzzyCompare(QQuickTableSectionSizeProvider::size(section), size)) + return; + d->hash.insert(section, size); + emit sizeChanged(); +} + +// return -1.0 if no valid explicit size retrieved +qreal QQuickTableSectionSizeProvider::size(int section) +{ + Q_D(QQuickTableSectionSizeProvider); + auto it = d->hash.find(section); + if (it != d->hash.end()) + return *it; + return -1.0; +} + +// return true if section is valid +bool QQuickTableSectionSizeProvider::resetSize(int section) +{ + Q_D(QQuickTableSectionSizeProvider); + if (d->hash.empty()) + return false; + + auto ret = d->hash.remove(section); + if (ret) + emit sizeChanged(); + return ret; +} + +void QQuickTableSectionSizeProvider::resetAll() +{ + Q_D(QQuickTableSectionSizeProvider); + d->hash.clear(); + emit sizeChanged(); +} + +QQuickTableSectionSizeProviderPrivate::QQuickTableSectionSizeProviderPrivate() + : QObjectPrivate() +{ +} + +QQuickTableSectionSizeProviderPrivate::~QQuickTableSectionSizeProviderPrivate() +{ + +} #include "moc_qquicktableview_p.cpp" QT_END_NAMESPACE -- cgit v1.2.3 From 325e6305b418ffe1dfb9a36c2516c6a8a3de5733 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 15:22:22 +0200 Subject: Move model types into their own library The model types are not part of the core QML runtime and should only be loaded if you explicitly import them. We cannot enforce that in Qt5 as some of them are available from the QtQml import, but we can change it in Qt6. Change-Id: I1e49e84d748e352537ec2d4af901c034c91d038f Reviewed-by: Erik Verbruggen --- src/quick/items/qquicktableview.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/quick/items/qquicktableview.cpp') diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 5e7e0db154..8f5130fc17 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -42,10 +42,10 @@ #include #include -#include -#include +#include +#include #include -#include +#include #include #include -- cgit v1.2.3