aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquicktableview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquicktableview.cpp')
-rw-r--r--src/quick/items/qquicktableview.cpp920
1 files changed, 586 insertions, 334 deletions
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp
index 9cd407b0fd..ac3397d2a9 100644
--- a/src/quick/items/qquicktableview.cpp
+++ b/src/quick/items/qquicktableview.cpp
@@ -150,6 +150,11 @@
must call \l forceLayout. This informs TableView that it needs to use the
provider functions again to recalculate and update the layout.
+ Since Qt 5.13, if you want to hide a specific column, you can return \c 0 from the
+ \l columnWidthProvider for that column. Likewise, you can return 0 from the
+ \l rowHeightProvider to hide a row. If you return a negative number, TableView
+ will fall back to calculate the size based on the delegate items.
+
\note The size of a row or column should be a whole number to avoid
sub-pixel alignment of items.
@@ -217,7 +222,9 @@
know the height of a specific row. The function takes one argument, \c row,
for which the TableView needs to know the height.
- \note The height of a row must always be greater than \c 0.
+ Since Qt 5.13, if you want to hide a specific row, you can return \c 0 height for
+ that row. If you return a negative number, TableView will fall back to
+ calculate the height based on the delegate items.
\sa columnWidthProvider, {Row heights and column widths}
*/
@@ -230,7 +237,9 @@
to know the width of a specific column. The function takes one argument,
\c column, for which the TableView needs to know the width.
- \note The width of a column must always be greater than \c 0.
+ Since Qt 5.13, if you want to hide a specific column, you can return \c 0 width for
+ that column. If you return a negative number, TableView will fall back to
+ calculate the width based on the delegate items.
\sa rowHeightProvider, {Row heights and column widths}
*/
@@ -377,42 +386,41 @@ Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle")
#define Q_TABLEVIEW_ASSERT(cond, output) Q_ASSERT((cond) || [&](){ dumpTable(); qWarning() << "output:" << output; return false;}())
static const Qt::Edge allTableEdges[] = { Qt::LeftEdge, Qt::RightEdge, Qt::TopEdge, Qt::BottomEdge };
-
-static QLine rectangleEdge(const QRect &rect, Qt::Edge tableEdge)
-{
- switch (tableEdge) {
- case Qt::LeftEdge:
- return QLine(rect.topLeft(), rect.bottomLeft());
- case Qt::RightEdge:
- return QLine(rect.topRight(), rect.bottomRight());
- case Qt::TopEdge:
- return QLine(rect.topLeft(), rect.topRight());
- case Qt::BottomEdge:
- return QLine(rect.bottomLeft(), rect.bottomRight());
- }
- return QLine();
-}
-
-static QRect expandedRect(const QRect &rect, Qt::Edge edge, int increment)
-{
- switch (edge) {
- case Qt::LeftEdge:
- return rect.adjusted(-increment, 0, 0, 0);
- case Qt::RightEdge:
- return rect.adjusted(0, 0, increment, 0);
- case Qt::TopEdge:
- return rect.adjusted(0, -increment, 0, 0);
- case Qt::BottomEdge:
- return rect.adjusted(0, 0, 0, increment);
- }
- return QRect();
-}
+static const int kEdgeIndexNotSet = -2;
+static const int kEdgeIndexAtEnd = -3;
const QPoint QQuickTableViewPrivate::kLeft = QPoint(-1, 0);
const QPoint QQuickTableViewPrivate::kRight = QPoint(1, 0);
const QPoint QQuickTableViewPrivate::kUp = QPoint(0, -1);
const QPoint QQuickTableViewPrivate::kDown = QPoint(0, 1);
+QQuickTableViewPrivate::EdgeRange::EdgeRange()
+ : startIndex(kEdgeIndexNotSet)
+ , endIndex(kEdgeIndexNotSet)
+ , size(0)
+{}
+
+bool QQuickTableViewPrivate::EdgeRange::containsIndex(Qt::Edge edge, int index)
+{
+ if (startIndex == kEdgeIndexNotSet)
+ return false;
+
+ if (endIndex == kEdgeIndexAtEnd) {
+ switch (edge) {
+ case Qt::LeftEdge:
+ case Qt::TopEdge:
+ return index <= startIndex;
+ case Qt::RightEdge:
+ case Qt::BottomEdge:
+ return index >= startIndex;
+ }
+ }
+
+ const int s = std::min(startIndex, endIndex);
+ const int e = std::max(startIndex, endIndex);
+ return index >= s && index <= e;
+}
+
QQuickTableViewPrivate::QQuickTableViewPrivate()
: QQuickFlickablePrivate()
{
@@ -428,8 +436,8 @@ QQuickTableViewPrivate::~QQuickTableViewPrivate()
QString QQuickTableViewPrivate::tableLayoutToString() const
{
return QString(QLatin1String("table cells: (%1,%2) -> (%3,%4), item count: %5, table rect: %6,%7 x %8,%9"))
- .arg(loadedTable.topLeft().x()).arg(loadedTable.topLeft().y())
- .arg(loadedTable.bottomRight().x()).arg(loadedTable.bottomRight().y())
+ .arg(leftColumn()).arg(topRow())
+ .arg(rightColumn()).arg(bottomRow())
.arg(loadedItems.count())
.arg(loadedTableOuterRect.x())
.arg(loadedTableOuterRect.y())
@@ -479,6 +487,118 @@ QPoint QQuickTableViewPrivate::cellAtModelIndex(int modelIndex) const
return QPoint(column, row);
}
+int QQuickTableViewPrivate::edgeToArrayIndex(Qt::Edge edge)
+{
+ return int(log2(float(edge)));
+}
+
+void QQuickTableViewPrivate::clearEdgeSizeCache()
+{
+ cachedColumnWidth.startIndex = kEdgeIndexNotSet;
+ cachedRowHeight.startIndex = kEdgeIndexNotSet;
+
+ for (Qt::Edge edge : allTableEdges)
+ cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)].startIndex = kEdgeIndexNotSet;
+}
+
+int QQuickTableViewPrivate::nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge)
+{
+ // Find the next column (or row) around the loaded table that is
+ // visible, and should be loaded next if the content item moves.
+ int startIndex = -1;
+ switch (edge) {
+ case Qt::LeftEdge: startIndex = loadedColumns.firstKey() - 1; break;
+ case Qt::RightEdge: startIndex = loadedColumns.lastKey() + 1; break;
+ case Qt::TopEdge: startIndex = loadedRows.firstKey() - 1; break;
+ case Qt::BottomEdge: startIndex = loadedRows.lastKey() + 1; break;
+ }
+
+ return nextVisibleEdgeIndex(edge, startIndex);
+}
+
+int QQuickTableViewPrivate::nextVisibleEdgeIndex(Qt::Edge edge, int startIndex)
+{
+ // First check if we have already searched for the first visible index
+ // after the given startIndex recently, and if so, return the cached result.
+ // The cached result is valid if startIndex is inside the range between the
+ // startIndex and the first visible index found after it.
+ auto &cachedResult = cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)];
+ if (cachedResult.containsIndex(edge, startIndex))
+ return cachedResult.endIndex;
+
+ // Search for the first column (or row) in the direction of edge that is
+ // visible, starting from the given column (startIndex).
+ int foundIndex = kEdgeIndexNotSet;
+ int testIndex = startIndex;
+
+ switch (edge) {
+ case Qt::LeftEdge: {
+ forever {
+ if (testIndex < 0) {
+ foundIndex = kEdgeIndexAtEnd;
+ break;
+ }
+
+ if (!isColumnHidden(testIndex)) {
+ foundIndex = testIndex;
+ break;
+ }
+
+ --testIndex;
+ }
+ break; }
+ case Qt::RightEdge: {
+ forever {
+ if (testIndex > tableSize.width() - 1) {
+ foundIndex = kEdgeIndexAtEnd;
+ break;
+ }
+
+ if (!isColumnHidden(testIndex)) {
+ foundIndex = testIndex;
+ break;
+ }
+
+ ++testIndex;
+ }
+ break; }
+ case Qt::TopEdge: {
+ forever {
+ if (testIndex < 0) {
+ foundIndex = kEdgeIndexAtEnd;
+ break;
+ }
+
+ if (!isRowHidden(testIndex)) {
+ foundIndex = testIndex;
+ break;
+ }
+
+ --testIndex;
+ }
+ break; }
+ case Qt::BottomEdge: {
+ forever {
+ if (testIndex > tableSize.height() - 1) {
+ foundIndex = kEdgeIndexAtEnd;
+ break;
+ }
+
+ if (!isRowHidden(testIndex)) {
+ foundIndex = testIndex;
+ break;
+ }
+
+ ++testIndex;
+ }
+ break; }
+ }
+
+ cachedResult.startIndex = startIndex;
+ cachedResult.endIndex = foundIndex;
+ return foundIndex;
+}
+
void QQuickTableViewPrivate::updateContentWidth()
{
Q_Q(QQuickTableView);
@@ -489,31 +609,13 @@ void QQuickTableViewPrivate::updateContentWidth()
return;
}
- const qreal thresholdBeforeAdjust = 0.1;
- int currentRightColumn = loadedTable.right();
-
- if (currentRightColumn > contentSizeBenchMarkPoint.x()) {
- contentSizeBenchMarkPoint.setX(currentRightColumn);
-
- const qreal spacing = currentRightColumn * cellSpacing.width();
- qreal currentWidth = loadedTableOuterRect.right();
- const qreal averageCellWidth = (currentWidth - spacing) / (currentRightColumn + 1);
- qreal estimatedWidth = (tableSize.width() * (averageCellWidth + cellSpacing.width())) - cellSpacing.width();
-
- if (currentRightColumn >= tableSize.width() - 1) {
- // We are at the last column, and can set the exact width
- if (!qFuzzyCompare(currentWidth, q->implicitWidth()))
- q->QQuickFlickable::setContentWidth(currentWidth);
- } else if (currentWidth >= q->implicitWidth()) {
- // We are at the estimated width, but there are still more columns
- q->QQuickFlickable::setContentWidth(estimatedWidth);
- } else {
- // Only set a new width if the new estimate is substantially different
- qreal diff = 1 - (estimatedWidth / q->implicitWidth());
- if (qAbs(diff) > thresholdBeforeAdjust)
- q->QQuickFlickable::setContentWidth(estimatedWidth);
- }
- }
+ const int nextColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge);
+ const int columnsRemaining = nextColumn == kEdgeIndexAtEnd ? 0 : tableSize.width() - nextColumn;
+ const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
+ const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
+ const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
+ const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth;
+ q->QQuickFlickable::setContentWidth(estimatedWidth);
}
void QQuickTableViewPrivate::updateContentHeight()
@@ -526,31 +628,13 @@ void QQuickTableViewPrivate::updateContentHeight()
return;
}
- const qreal thresholdBeforeAdjust = 0.1;
- int currentBottomRow = loadedTable.bottom();
-
- if (currentBottomRow > contentSizeBenchMarkPoint.y()) {
- contentSizeBenchMarkPoint.setY(currentBottomRow);
-
- const qreal spacing = currentBottomRow * cellSpacing.height();
- qreal currentHeight = loadedTableOuterRect.bottom();
- const qreal averageCellHeight = (currentHeight - spacing) / (currentBottomRow + 1);
- qreal estimatedHeight = (tableSize.height() * (averageCellHeight + cellSpacing.height())) - cellSpacing.height();
-
- if (currentBottomRow >= tableSize.height() - 1) {
- // We are at the last row, and can set the exact height
- if (!qFuzzyCompare(currentHeight, q->implicitHeight()))
- q->QQuickFlickable::setContentHeight(currentHeight);
- } else if (currentHeight >= q->implicitHeight()) {
- // We are at the estimated height, but there are still more rows
- q->QQuickFlickable::setContentHeight(estimatedHeight);
- } else {
- // Only set a new height if the new estimate is substantially different
- qreal diff = 1 - (estimatedHeight / q->implicitHeight());
- if (qAbs(diff) > thresholdBeforeAdjust)
- q->QQuickFlickable::setContentHeight(estimatedHeight);
- }
- }
+ const int nextRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge);
+ const int rowsRemaining = nextRow == kEdgeIndexAtEnd ? 0 : tableSize.height() - nextRow;
+ const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
+ const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
+ const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
+ const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight;
+ q->QQuickFlickable::setContentHeight(estimatedHeight);
}
void QQuickTableViewPrivate::enforceTableAtOrigin()
@@ -562,24 +646,36 @@ void QQuickTableViewPrivate::enforceTableAtOrigin()
bool layoutNeeded = false;
const qreal flickMargin = 50;
- if (loadedTable.x() == 0 && loadedTableOuterRect.x() > 0) {
- // The table is at the beginning, but not at the edge of the
- // content view. So move the table to origin.
- loadedTableOuterRect.moveLeft(0);
- layoutNeeded = true;
- } else if (loadedTableOuterRect.x() < 0) {
- // The table is outside the beginning of the content view. Move
- // the whole table inside, and make some room for flicking.
- loadedTableOuterRect.moveLeft(loadedTable.x() == 0 ? 0 : flickMargin);
- layoutNeeded = true;
+ const bool noMoreColumns = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge) == kEdgeIndexAtEnd;
+ const bool noMoreRows = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge) == kEdgeIndexAtEnd;
+
+ if (noMoreColumns) {
+ if (!qFuzzyIsNull(loadedTableOuterRect.left())) {
+ // There are no more columns, but the table rect
+ // is not at origin. So we move it there.
+ loadedTableOuterRect.moveLeft(0);
+ layoutNeeded = true;
+ }
+ } else {
+ if (loadedTableOuterRect.left() <= 0) {
+ // The table rect is at origin, or outside. But we still have
+ // more visible columns to the left. So we need to make some
+ // space so that they can be flicked in.
+ loadedTableOuterRect.moveLeft(flickMargin);
+ layoutNeeded = true;
+ }
}
- if (loadedTable.y() == 0 && loadedTableOuterRect.y() > 0) {
- loadedTableOuterRect.moveTop(0);
- layoutNeeded = true;
- } else if (loadedTableOuterRect.y() < 0) {
- loadedTableOuterRect.moveTop(loadedTable.y() == 0 ? 0 : flickMargin);
- layoutNeeded = true;
+ if (noMoreRows) {
+ if (!qFuzzyIsNull(loadedTableOuterRect.top())) {
+ loadedTableOuterRect.moveTop(0);
+ layoutNeeded = true;
+ }
+ } else {
+ if (loadedTableOuterRect.top() <= 0) {
+ loadedTableOuterRect.moveTop(flickMargin);
+ layoutNeeded = true;
+ }
}
if (layoutNeeded) {
@@ -590,47 +686,100 @@ void QQuickTableViewPrivate::enforceTableAtOrigin()
void QQuickTableViewPrivate::updateAverageEdgeSize()
{
- int bottomCell = loadedTable.bottom();
- int rightCell = loadedTable.right();
- qreal accRowSpacing = bottomCell * cellSpacing.height();
- qreal accColumnSpacing = rightCell * cellSpacing.width();
- averageEdgeSize.setHeight((loadedTableOuterRect.bottom() - accRowSpacing) / (bottomCell + 1));
- averageEdgeSize.setWidth((loadedTableOuterRect.right() - accColumnSpacing) / (rightCell + 1));
+ const int loadedRowCount = loadedRows.count();
+ const int loadedColumnCount = loadedColumns.count();
+ const qreal accRowSpacing = (loadedRowCount - 1) * cellSpacing.height();
+ const qreal accColumnSpacing = (loadedColumnCount - 1) * cellSpacing.width();
+ averageEdgeSize.setHeight((loadedTableOuterRect.height() - accRowSpacing) / loadedRowCount);
+ averageEdgeSize.setWidth((loadedTableOuterRect.width() - accColumnSpacing) / loadedColumnCount);
}
void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable()
{
- QRectF topLeftRect = loadedTableItem(loadedTable.topLeft())->geometry();
- QRectF bottomRightRect = loadedTableItem(loadedTable.bottomRight())->geometry();
- loadedTableOuterRect = topLeftRect.united(bottomRightRect);
+ const QPoint topLeft = QPoint(leftColumn(), topRow());
+ const QPoint bottomRight = QPoint(rightColumn(), bottomRow());
+ QRectF topLeftRect = loadedTableItem(topLeft)->geometry();
+ QRectF bottomRightRect = loadedTableItem(bottomRight)->geometry();
+ loadedTableOuterRect = QRectF(topLeftRect.topLeft(), bottomRightRect.bottomRight());
loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft());
}
+void QQuickTableViewPrivate::forceLayout()
+{
+ columnRowPositionsInvalid = true;
+ clearEdgeSizeCache();
+ RebuildOptions rebuildOptions = RebuildOption::None;
+
+ // Go through all columns from first to last, find the columns that used
+ // to be hidden and not loaded, and check if they should become visible
+ // (and vice versa). If there is a change, we need to rebuild.
+ for (int column = leftColumn(); column <= rightColumn(); ++column) {
+ const bool wasVisibleFromBefore = loadedColumns.contains(column);
+ const bool isVisibleNow = !qFuzzyIsNull(getColumnWidth(column));
+ if (wasVisibleFromBefore == isVisibleNow)
+ continue;
+
+ // A column changed visibility. This means that it should
+ // either be loaded or unloaded. So we need a rebuild.
+ qCDebug(lcTableViewDelegateLifecycle) << "Column" << column << "changed visibility to" << isVisibleNow;
+ rebuildOptions.setFlag(RebuildOption::ViewportOnly);
+ if (column == leftColumn()) {
+ // The first loaded column should now be hidden. This means that we
+ // need to calculate which column should now be first instead.
+ rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftColumn);
+ }
+ break;
+ }
+
+ // Go through all rows from first to last, and do the same as above
+ for (int row = topRow(); row <= bottomRow(); ++row) {
+ const bool wasVisibleFromBefore = loadedRows.contains(row);
+ const bool isVisibleNow = !qFuzzyIsNull(getRowHeight(row));
+ if (wasVisibleFromBefore == isVisibleNow)
+ continue;
+
+ // A row changed visibility. This means that it should
+ // either be loaded or unloaded. So we need a rebuild.
+ qCDebug(lcTableViewDelegateLifecycle) << "Row" << row << "changed visibility to" << isVisibleNow;
+ rebuildOptions.setFlag(RebuildOption::ViewportOnly);
+ if (row == topRow())
+ rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftRow);
+ break;
+ }
+
+ if (rebuildOptions)
+ scheduleRebuildTable(rebuildOptions);
+
+ if (polishing) {
+ qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!";
+ q_func()->polish();
+ return;
+ }
+
+ updatePolish();
+}
+
void QQuickTableViewPrivate::syncLoadedTableFromLoadRequest()
{
if (loadRequest.edge() == Qt::Edge(0)) {
// No edge means we're loading the top-left item
- loadedTable = QRect(loadRequest.firstCell(), loadRequest.lastCell());
+ loadedColumns.insert(loadRequest.column(), 0);
+ loadedRows.insert(loadRequest.row(), 0);
return;
}
switch (loadRequest.edge()) {
case Qt::LeftEdge:
- case Qt::TopEdge:
- loadedTable.setTopLeft(loadRequest.firstCell());
- break;
case Qt::RightEdge:
+ loadedColumns.insert(loadRequest.column(), 0);
+ break;
+ case Qt::TopEdge:
case Qt::BottomEdge:
- loadedTable.setBottomRight(loadRequest.lastCell());
+ loadedRows.insert(loadRequest.row(), 0);
break;
}
}
-FxTableItem *QQuickTableViewPrivate::itemNextTo(const FxTableItem *fxTableItem, const QPoint &direction) const
-{
- return loadedTableItem(fxTableItem->cell + direction);
-}
-
FxTableItem *QQuickTableViewPrivate::loadedTableItem(const QPoint &cell) const
{
const int modelIndex = modelIndexAtCell(cell);
@@ -762,39 +911,16 @@ void QQuickTableViewPrivate::unloadItem(const QPoint &cell)
releaseItem(loadedItems.take(modelIndex), reusableFlag);
}
-void QQuickTableViewPrivate::unloadItems(const QLine &items)
-{
- qCDebug(lcTableViewDelegateLifecycle) << items;
-
- if (items.dx()) {
- int y = items.p1().y();
- for (int x = items.p1().x(); x <= items.p2().x(); ++x)
- unloadItem(QPoint(x, y));
- } else {
- int x = items.p1().x();
- for (int y = items.p1().y(); y <= items.p2().y(); ++y)
- unloadItem(QPoint(x, y));
- }
-}
-
bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
{
switch (tableEdge) {
case Qt::LeftEdge:
- if (loadedTable.topLeft().x() == 0)
- return false;
return loadedTableOuterRect.left() > fillRect.left() + cellSpacing.width();
case Qt::RightEdge:
- if (loadedTable.bottomRight().x() >= tableSize.width() - 1)
- return false;
return loadedTableOuterRect.right() < fillRect.right() - cellSpacing.width();
case Qt::TopEdge:
- if (loadedTable.topLeft().y() == 0)
- return false;
return loadedTableOuterRect.top() > fillRect.top() + cellSpacing.height();
case Qt::BottomEdge:
- if (loadedTable.bottomRight().y() >= tableSize.height() - 1)
- return false;
return loadedTableOuterRect.bottom() < fillRect.bottom() - cellSpacing.height();
}
@@ -807,19 +933,19 @@ bool QQuickTableViewPrivate::canUnloadTableEdge(Qt::Edge tableEdge, const QRectF
// they are needed as anchor point for further layouting.
switch (tableEdge) {
case Qt::LeftEdge:
- if (loadedTable.width() <= 1)
+ if (loadedColumns.count() <= 1)
return false;
return loadedTableInnerRect.left() <= fillRect.left();
case Qt::RightEdge:
- if (loadedTable.width() <= 1)
+ if (loadedColumns.count() <= 1)
return false;
return loadedTableInnerRect.right() >= fillRect.right();
case Qt::TopEdge:
- if (loadedTable.height() <= 1)
+ if (loadedRows.count() <= 1)
return false;
return loadedTableInnerRect.top() <= fillRect.top();
case Qt::BottomEdge:
- if (loadedTable.height() <= 1)
+ if (loadedRows.count() <= 1)
return false;
return loadedTableInnerRect.bottom() >= fillRect.bottom();
}
@@ -830,9 +956,14 @@ bool QQuickTableViewPrivate::canUnloadTableEdge(Qt::Edge tableEdge, const QRectF
Qt::Edge QQuickTableViewPrivate::nextEdgeToLoad(const QRectF rect)
{
for (Qt::Edge edge : allTableEdges) {
- if (canLoadTableEdge(edge, rect))
- return edge;
+ if (!canLoadTableEdge(edge, rect))
+ continue;
+ const int nextIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
+ if (nextIndex == kEdgeIndexAtEnd)
+ continue;
+ return edge;
}
+
return Qt::Edge(0);
}
@@ -865,8 +996,10 @@ qreal QQuickTableViewPrivate::sizeHintForColumn(int column)
{
// Find the widest cell in the column, and return its width
qreal columnWidth = 0;
- for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row)
+ for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) {
+ const int row = r.key();
columnWidth = qMax(columnWidth, cellWidth(QPoint(column, row)));
+ }
return columnWidth;
}
@@ -875,8 +1008,10 @@ qreal QQuickTableViewPrivate::sizeHintForRow(int row)
{
// Find the highest cell in the row, and return its height
qreal rowHeight = 0;
- for (int column = loadedTable.left(); column <= loadedTable.right(); ++column)
+ for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) {
+ const int column = c.key();
rowHeight = qMax(rowHeight, cellHeight(QPoint(column, row)));
+ }
return rowHeight;
}
@@ -901,102 +1036,152 @@ void QQuickTableViewPrivate::calculateTableSize()
emit q->rowsChanged();
}
-qreal QQuickTableViewPrivate::resolveColumnWidth(int column)
-{
- Q_TABLEVIEW_ASSERT(column >= loadedTable.left() && column <= loadedTable.right(), column);
- qreal columnWidth = -1;
+qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column)
+{
+ // Return the column width specified by the application, or go
+ // through the loaded items and calculate it as a fallback. For
+ // layouting, the width can never be zero (or negative), as this
+ // can lead us to be stuck in an infinite loop trying to load and
+ // fill out the empty viewport space with empty columns.
+ const qreal explicitColumnWidth = getColumnWidth(column);
+ if (explicitColumnWidth >= 0)
+ return explicitColumnWidth;
+
+ // 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
+ // row you're at when the column is flicked in). The upshot is that you don't have to
+ // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary.
+ qreal columnWidth = sizeHintForColumn(column);
+
+ if (qIsNaN(columnWidth) || columnWidth <= 0) {
+ if (!layoutWarningIssued) {
+ layoutWarningIssued = true;
+ qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
+ }
+ columnWidth = kDefaultRowHeight;
+ }
- if (!columnWidthProvider.isUndefined()) {
- if (columnWidthProvider.isCallable()) {
- auto const columnAsArgument = QJSValueList() << QJSValue(column);
- columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
- if (qIsNaN(columnWidth) || columnWidth <= 0) {
- // The column width needs to be greater than 0, otherwise we never reach the edge
- // while loading/refilling columns. This would cause the application to hang.
- if (!layoutWarningIssued) {
- layoutWarningIssued = true;
- qmlWarning(q_func()) << "columnWidthProvider did not return a valid width for column: " << column;
- }
- columnWidth = kDefaultColumnWidth;
- }
- } else {
- if (!layoutWarningIssued) {
- layoutWarningIssued = true;
- qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function";
- }
- columnWidth = kDefaultColumnWidth;
+ return columnWidth;
+}
+
+qreal QQuickTableViewPrivate::getRowLayoutHeight(int row)
+{
+ // Return the row height specified by the application, or go
+ // through the loaded items and calculate it as a fallback. For
+ // layouting, the height can never be zero (or negative), as this
+ // can lead us to be stuck in an infinite loop trying to load and
+ // fill out the empty viewport space with empty rows.
+ const qreal explicitRowHeight = getRowHeight(row);
+ if (explicitRowHeight >= 0)
+ return explicitRowHeight;
+
+ // 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
+ // column you're at when the row is flicked in). The upshot is that you don't have to
+ // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary.
+ qreal rowHeight = sizeHintForRow(row);
+
+ if (qIsNaN(rowHeight) || rowHeight <= 0) {
+ if (!layoutWarningIssued) {
+ layoutWarningIssued = true;
+ qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
}
+ rowHeight = kDefaultRowHeight;
+ }
+
+ return rowHeight;
+}
+
+qreal QQuickTableViewPrivate::getColumnWidth(int column)
+{
+ // Return the width of the given column, if explicitly set. Return 0 if the column
+ // is hidden, and -1 if the width is not set (which means that the width should
+ // instead be calculated from the implicit size of the delegate items. This function
+ // can be overridden by e.g HeaderView to provide the column widths by other means.
+ const int noExplicitColumnWidth = -1;
+
+ if (cachedColumnWidth.startIndex == column)
+ return cachedColumnWidth.size;
+
+ if (columnWidthProvider.isUndefined())
+ return noExplicitColumnWidth;
+
+ qreal columnWidth = noExplicitColumnWidth;
+
+ if (columnWidthProvider.isCallable()) {
+ auto const columnAsArgument = QJSValueList() << QJSValue(column);
+ columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
+ if (qIsNaN(columnWidth) || columnWidth < 0)
+ columnWidth = noExplicitColumnWidth;
} else {
- // If columnWidthProvider is left unspecified, we just 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 row you're at when the column is flicked in). The upshot is that you don't have to
- // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary.
- columnWidth = sizeHintForColumn(column);
- if (qIsNaN(columnWidth) || columnWidth <= 0) {
- // The column width needs to be greater than 0, otherwise we never reach the edge
- // while loading/refilling columns. This would cause the application to hang.
- if (!layoutWarningIssued) {
- layoutWarningIssued = true;
- qmlWarning(q_func()) << "the delegate's implicitWidth needs to be greater than zero";
- }
- columnWidth = kDefaultColumnWidth;
+ if (!layoutWarningIssued) {
+ layoutWarningIssued = true;
+ qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function";
}
+ columnWidth = noExplicitColumnWidth;
}
+ cachedColumnWidth.startIndex = column;
+ cachedColumnWidth.size = columnWidth;
return columnWidth;
}
-qreal QQuickTableViewPrivate::resolveRowHeight(int row)
+qreal QQuickTableViewPrivate::getRowHeight(int row)
{
- Q_TABLEVIEW_ASSERT(row >= loadedTable.top() && row <= loadedTable.bottom(), row);
- qreal rowHeight = -1;
+ // Return the height of the given row, if explicitly set. Return 0 if the row
+ // is hidden, and -1 if the height is not set (which means that the height should
+ // instead be calculated from the implicit size of the delegate items. This function
+ // can be overridden by e.g HeaderView to provide the row heights by other means.
+ const int noExplicitRowHeight = -1;
- if (!rowHeightProvider.isUndefined()) {
- if (rowHeightProvider.isCallable()) {
- auto const rowAsArgument = QJSValueList() << QJSValue(row);
- rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
- if (qIsNaN(rowHeight) || rowHeight <= 0) {
- // The row height needs to be greater than 0, otherwise we never reach the edge
- // while loading/refilling rows. This would cause the application to hang.
- if (!layoutWarningIssued) {
- layoutWarningIssued = true;
- qmlWarning(q_func()) << "rowHeightProvider did not return a valid height for row: " << row;
- }
- rowHeight = kDefaultRowHeight;
- }
- } else {
- if (!layoutWarningIssued) {
- layoutWarningIssued = true;
- qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function";
- }
- rowHeight = kDefaultRowHeight;
- }
+ if (cachedRowHeight.startIndex == row)
+ return cachedRowHeight.size;
+
+ if (rowHeightProvider.isUndefined())
+ return noExplicitRowHeight;
+
+ qreal rowHeight = noExplicitRowHeight;
+
+ if (rowHeightProvider.isCallable()) {
+ auto const rowAsArgument = QJSValueList() << QJSValue(row);
+ rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
+ if (qIsNaN(rowHeight) || rowHeight < 0)
+ rowHeight = noExplicitRowHeight;
} else {
- // If rowHeightProvider is left unspecified, we just 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 column you're at when the row is flicked in). The upshot is that you don't have to
- // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary.
- rowHeight = sizeHintForRow(row);
- if (qIsNaN(rowHeight) || rowHeight <= 0) {
- if (!layoutWarningIssued) {
- layoutWarningIssued = true;
- qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
- }
- rowHeight = kDefaultRowHeight;
+ if (!layoutWarningIssued) {
+ layoutWarningIssued = true;
+ qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function";
}
+ rowHeight = noExplicitRowHeight;
}
+ cachedRowHeight.startIndex = row;
+ cachedRowHeight.size = rowHeight;
return rowHeight;
}
+bool QQuickTableViewPrivate::isColumnHidden(int column)
+{
+ // A column is hidden if the width is explicit set to zero (either by
+ // using a columnWidthProvider, or by overriding getColumnWidth()).
+ return qFuzzyIsNull(getColumnWidth(column));
+}
+
+bool QQuickTableViewPrivate::isRowHidden(int row)
+{
+ // A row is hidden if the height is explicit set to zero (either by
+ // using a rowHeightProvider, or by overriding getRowHeight()).
+ return qFuzzyIsNull(getRowHeight(row));
+}
+
void QQuickTableViewPrivate::relayoutTable()
{
+ clearEdgeSizeCache();
relayoutTableItems();
syncLoadedTableRectFromLoadedTable();
enforceTableAtOrigin();
- contentSizeBenchMarkPoint = QPoint(-1, -1);
updateContentWidth();
updateContentHeight();
// Return back to updatePolish to loadAndUnloadVisibleEdges()
@@ -1012,11 +1197,13 @@ void QQuickTableViewPrivate::relayoutTableItems()
qreal nextColumnX = loadedTableOuterRect.x();
qreal nextRowY = loadedTableOuterRect.y();
- for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) {
+ const int column = c.key();
// Adjust the geometry of all cells in the current column
- const qreal width = resolveColumnWidth(column);
+ const qreal width = getColumnLayoutWidth(column);
- for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) {
+ const int row = r.key();
auto item = loadedTableItem(QPoint(column, row));
QRectF geometry = item->geometry();
geometry.moveLeft(nextColumnX);
@@ -1024,14 +1211,17 @@ void QQuickTableViewPrivate::relayoutTableItems()
item->setGeometry(geometry);
}
- nextColumnX += width + cellSpacing.width();
+ if (width > 0)
+ nextColumnX += width + cellSpacing.width();
}
- for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) {
+ const int row = r.key();
// Adjust the geometry of all cells in the current row
- const qreal height = resolveRowHeight(row);
+ const qreal height = getRowLayoutHeight(row);
- for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
+ for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) {
+ const int column = c.key();
auto item = loadedTableItem(QPoint(column, row));
QRectF geometry = item->geometry();
geometry.moveTop(nextRowY);
@@ -1039,12 +1229,15 @@ void QQuickTableViewPrivate::relayoutTableItems()
item->setGeometry(geometry);
}
- nextRowY += height + cellSpacing.height();
+ if (height > 0)
+ nextRowY += height + cellSpacing.height();
}
if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
- for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
- for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
+ for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) {
+ const int column = c.key();
+ for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) {
+ const int row = r.key();
QPoint cell = QPoint(column, row);
qCDebug(lcTableViewDelegateLifecycle()) << "relayout item:" << cell << loadedTableItem(cell)->geometry();
}
@@ -1054,66 +1247,82 @@ void QQuickTableViewPrivate::relayoutTableItems()
void QQuickTableViewPrivate::layoutVerticalEdge(Qt::Edge tableEdge)
{
- int column = (tableEdge == Qt::LeftEdge) ? loadedTable.left() : loadedTable.right();
- QPoint neighbourDirection = (tableEdge == Qt::LeftEdge) ? kRight : kLeft;
- qreal width = resolveColumnWidth(column);
-
- for (int row = loadedTable.top(); row <= loadedTable.bottom(); ++row) {
- auto fxTableItem = loadedTableItem(QPoint(column, row));
- auto const neighbourItem = itemNextTo(fxTableItem, neighbourDirection);
-
- QRectF geometry = fxTableItem->geometry();
- geometry.setWidth(width);
- geometry.setHeight(neighbourItem->geometry().height());
- qreal left = tableEdge == Qt::LeftEdge ?
- neighbourItem->geometry().left() - cellSpacing.width() - geometry.width() :
- neighbourItem->geometry().right() + cellSpacing.width();
+ int columnThatNeedsLayout;
+ int neighbourColumn;
+ qreal columnX;
+ qreal columnWidth;
+
+ if (tableEdge == Qt::LeftEdge) {
+ columnThatNeedsLayout = leftColumn();
+ neighbourColumn = loadedColumns.keys().value(1);
+ columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
+ const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
+ columnX = neighbourItem->geometry().left() - cellSpacing.width() - columnWidth;
+ } else {
+ columnThatNeedsLayout = rightColumn();
+ neighbourColumn = loadedColumns.keys().value(loadedColumns.count() - 2);
+ columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
+ const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
+ columnX = neighbourItem->geometry().right() + cellSpacing.width();
+ }
- geometry.moveLeft(left);
- geometry.moveTop(neighbourItem->geometry().top());
+ for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r) {
+ const int row = r.key();
+ auto fxTableItem = loadedTableItem(QPoint(columnThatNeedsLayout, row));
+ auto const neighbourItem = loadedTableItem(QPoint(neighbourColumn, row));
+ const qreal rowY = neighbourItem->geometry().y();
+ const qreal rowHeight = neighbourItem->geometry().height();
- fxTableItem->setGeometry(geometry);
+ fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight));
fxTableItem->setVisible(true);
- qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, row) << fxTableItem->geometry();
+ qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(columnThatNeedsLayout, row) << fxTableItem->geometry();
}
}
void QQuickTableViewPrivate::layoutHorizontalEdge(Qt::Edge tableEdge)
{
- int row = (tableEdge == Qt::TopEdge) ? loadedTable.top() : loadedTable.bottom();
- QPoint neighbourDirection = (tableEdge == Qt::TopEdge) ? kDown : kUp;
- qreal height = resolveRowHeight(row);
-
- for (int column = loadedTable.left(); column <= loadedTable.right(); ++column) {
- auto fxTableItem = loadedTableItem(QPoint(column, row));
- auto const neighbourItem = itemNextTo(fxTableItem, neighbourDirection);
-
- QRectF geometry = fxTableItem->geometry();
- geometry.setWidth(neighbourItem->geometry().width());
- geometry.setHeight(height);
- qreal top = tableEdge == Qt::TopEdge ?
- neighbourItem->geometry().top() - cellSpacing.height() - geometry.height() :
- neighbourItem->geometry().bottom() + cellSpacing.height();
+ int rowThatNeedsLayout;
+ int neighbourRow;
+ qreal rowY;
+ qreal rowHeight;
+
+ if (tableEdge == Qt::TopEdge) {
+ rowThatNeedsLayout = topRow();
+ neighbourRow = loadedRows.keys().value(1);
+ rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
+ const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
+ rowY = neighbourItem->geometry().top() - cellSpacing.height() - rowHeight;
+ } else {
+ rowThatNeedsLayout = bottomRow();
+ neighbourRow = loadedRows.keys().value(loadedRows.count() - 2);
+ rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
+ const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
+ rowY = neighbourItem->geometry().bottom() + cellSpacing.height();
+ }
- geometry.moveTop(top);
- geometry.moveLeft(neighbourItem->geometry().left());
+ for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c) {
+ const int column = c.key();
+ auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
+ auto const neighbourItem = loadedTableItem(QPoint(column, neighbourRow));
+ const qreal columnX = neighbourItem->geometry().x();
+ const qreal columnWidth = neighbourItem->geometry().width();
- fxTableItem->setGeometry(geometry);
+ fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight));
fxTableItem->setVisible(true);
- qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, row) << fxTableItem->geometry();
+ qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, rowThatNeedsLayout) << fxTableItem->geometry();
}
}
void QQuickTableViewPrivate::layoutTopLeftItem()
{
- const QPoint cell = loadRequest.firstCell();
+ const QPoint cell(loadRequest.column(), loadRequest.row());
auto topLeftItem = loadedTableItem(cell);
auto item = topLeftItem->item;
item->setPosition(loadRequest.startPosition());
- item->setSize(QSizeF(resolveColumnWidth(cell.x()), resolveRowHeight(cell.y())));
+ item->setSize(QSizeF(getColumnLayoutWidth(cell.x()), getRowLayoutHeight(cell.y())));
topLeftItem->setVisible(true);
qCDebug(lcTableViewDelegateLifecycle) << "geometry:" << topLeftItem->geometry();
}
@@ -1138,29 +1347,6 @@ void QQuickTableViewPrivate::layoutTableEdgeFromLoadRequest()
}
}
-void QQuickTableViewPrivate::cancelLoadRequest()
-{
- loadRequest.markAsDone();
- model->cancel(modelIndexAtCell(loadRequest.currentCell()));
-
- if (rebuildScheduled) {
- // No reason to rollback already loaded edge items
- // since we anyway are about to reload all items.
- return;
- }
-
- if (loadRequest.atBeginning()) {
- // No items have yet been loaded, so nothing to unload
- return;
- }
-
- QLine rollbackItems;
- rollbackItems.setP1(loadRequest.firstCell());
- rollbackItems.setP2(loadRequest.previousCell());
- qCDebug(lcTableViewDelegateLifecycle()) << "rollback:" << rollbackItems << tableLayoutToString();
- unloadItems(rollbackItems);
-}
-
void QQuickTableViewPrivate::processLoadRequest()
{
Q_TABLEVIEW_ASSERT(loadRequest.isActive(), "");
@@ -1186,9 +1372,22 @@ void QQuickTableViewPrivate::processLoadRequest()
syncLoadedTableRectFromLoadedTable();
if (rebuildState == RebuildState::Done) {
- enforceTableAtOrigin();
- updateContentWidth();
- updateContentHeight();
+ // Loading of this edge was not done as a part of a rebuild, but
+ // instead as an incremental build after e.g a flick.
+ switch (loadRequest.edge()) {
+ case Qt::LeftEdge:
+ case Qt::TopEdge:
+ enforceTableAtOrigin();
+ break;
+ case Qt::RightEdge:
+ updateAverageEdgeSize();
+ updateContentWidth();
+ break;
+ case Qt::BottomEdge:
+ updateAverageEdgeSize();
+ updateContentHeight();
+ break;
+ }
drainReusePoolAfterLoadRequest();
}
@@ -1209,7 +1408,7 @@ void QQuickTableViewPrivate::processRebuildTable()
if (rebuildState == RebuildState::VerifyTable) {
if (loadedItems.isEmpty()) {
- qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model or no delegate";
+ qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded, meaning empty model, all rows or columns hidden, or no delegate";
rebuildState = RebuildState::Done;
return;
}
@@ -1233,14 +1432,14 @@ void QQuickTableViewPrivate::processRebuildTable()
&& reusableFlag == QQmlTableInstanceModel::Reusable);
if (rebuildState == RebuildState::PreloadColumns) {
- if (preload && loadedTable.right() < tableSize.width() - 1)
+ if (preload && nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge) != kEdgeIndexAtEnd)
loadEdge(Qt::RightEdge, QQmlIncubator::AsynchronousIfNested);
if (!moveToNextRebuildState())
return;
}
if (rebuildState == RebuildState::PreloadRows) {
- if (preload && loadedTable.bottom() < tableSize.height() - 1)
+ if (preload && nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge) != kEdgeIndexAtEnd)
loadEdge(Qt::BottomEdge, QQmlIncubator::AsynchronousIfNested);
if (!moveToNextRebuildState())
return;
@@ -1268,19 +1467,26 @@ bool QQuickTableViewPrivate::moveToNextRebuildState()
return true;
}
-void QQuickTableViewPrivate::beginRebuildTable()
+QPoint QQuickTableViewPrivate::calculateNewTopLeft()
{
- if (loadRequest.isActive())
- cancelLoadRequest();
+ const int firstVisibleLeft = nextVisibleEdgeIndex(Qt::RightEdge, 0);
+ const int firstVisibleTop = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
- calculateTableSize();
+ return QPoint(firstVisibleLeft, firstVisibleTop);
+}
- QPoint topLeft;
- QPointF topLeftPos;
+void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos)
+{
+ if (tableSize.isEmpty()) {
+ releaseLoadedItems(QQmlTableInstanceModel::NotReusable);
+ topLeft = QPoint(kEdgeIndexAtEnd, 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);
@@ -1290,7 +1496,7 @@ void QQuickTableViewPrivate::beginRebuildTable()
topLeft.ry() = qBound(0, newRow, tableSize.height() - 1);
topLeftPos.ry() = topLeft.y() * (averageEdgeSize.height() + cellSpacing.height());
} else {
- topLeft.ry() = qBound(0, loadedTable.topLeft().y(), tableSize.height() - 1);
+ topLeft.ry() = qBound(0, topRow(), tableSize.height() - 1);
topLeftPos.ry() = loadedTableOuterRect.topLeft().y();
}
if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) {
@@ -1298,18 +1504,33 @@ void QQuickTableViewPrivate::beginRebuildTable()
topLeft.rx() = qBound(0, newColumn, tableSize.width() - 1);
topLeftPos.rx() = topLeft.x() * (averageEdgeSize.width() + cellSpacing.width());
} else {
- topLeft.rx() = qBound(0, loadedTable.topLeft().x(), tableSize.width() - 1);
+ topLeft.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
topLeftPos.rx() = loadedTableOuterRect.topLeft().x();
}
} else {
Q_TABLEVIEW_UNREACHABLE(rebuildOptions);
}
+}
- loadedTable = QRect();
+void QQuickTableViewPrivate::beginRebuildTable()
+{
+ calculateTableSize();
+
+ QPoint topLeft;
+ QPointF topLeftPos;
+ calculateTopLeft(topLeft, topLeftPos);
+
+ loadedColumns.clear();
+ loadedRows.clear();
loadedTableOuterRect = QRect();
loadedTableInnerRect = QRect();
- contentSizeBenchMarkPoint = QPoint(-1, -1);
columnRowPositionsInvalid = false;
+ clearEdgeSizeCache();
+
+ if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) {
+ // No visible columns or rows, so nothing to load
+ return;
+ }
loadInitialTopLeftItem(topLeft, topLeftPos);
loadAndUnloadVisibleEdges();
@@ -1335,12 +1556,6 @@ void QQuickTableViewPrivate::loadInitialTopLeftItem(const QPoint &cell, const QP
{
Q_TABLEVIEW_ASSERT(loadedItems.isEmpty(), "");
- if (tableSize.isEmpty())
- return;
-
- if (model->count() == 0)
- return;
-
if (tableModel && !tableModel->delegate())
return;
@@ -1352,16 +1567,42 @@ void QQuickTableViewPrivate::loadInitialTopLeftItem(const QPoint &cell, const QP
void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge)
{
- unloadItems(rectangleEdge(loadedTable, edge));
- loadedTable = expandedRect(loadedTable, edge, -1);
- syncLoadedTableRectFromLoadedTable();
+ qCDebug(lcTableViewDelegateLifecycle) << edge;
+
+ switch (edge) {
+ case Qt::LeftEdge:
+ case Qt::RightEdge: {
+ const int column = edge == Qt::LeftEdge ? leftColumn() : rightColumn();
+ for (auto r = loadedRows.cbegin(); r != loadedRows.cend(); ++r)
+ unloadItem(QPoint(column, r.key()));
+ loadedColumns.remove(column);
+ syncLoadedTableRectFromLoadedTable();
+ updateAverageEdgeSize();
+ updateContentWidth();
+ break; }
+ case Qt::TopEdge:
+ case Qt::BottomEdge: {
+ const int row = edge == Qt::TopEdge ? topRow() : bottomRow();
+ for (auto c = loadedColumns.cbegin(); c != loadedColumns.cend(); ++c)
+ unloadItem(QPoint(c.key(), row));
+ loadedRows.remove(row);
+ syncLoadedTableRectFromLoadedTable();
+ updateAverageEdgeSize();
+ updateContentHeight();
+ break; }
+ }
+
qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString();
}
void QQuickTableViewPrivate::loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMode incubationMode)
{
- QLine cellsToLoad = rectangleEdge(expandedRect(loadedTable, edge, 1), edge);
- loadRequest.begin(cellsToLoad, edge, incubationMode);
+ const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
+ qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex;
+
+ const QList<int> visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge)
+ ? loadedRows.keys() : loadedColumns.keys();
+ loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode);
processLoadRequest();
}
@@ -1447,8 +1688,8 @@ void QQuickTableViewPrivate::drainReusePoolAfterLoadRequest()
// in with varying sizes, causing some items not to be resued immediately), we multiply the
// value by 2. Note that we also add an extra +1 to the column count, because the number of
// visible columns will fluctuate between +1/-1 while flicking.
- const int w = loadedTable.width();
- const int h = loadedTable.height();
+ const int w = loadedColumns.count();
+ const int h = loadedRows.count();
const int minTime = int(std::ceil(w > h ? qreal(w + 1) / h : qreal(h + 1) / w));
const int maxTime = minTime * 2;
tableModel->drainReusableItemsPool(maxTime);
@@ -1502,8 +1743,12 @@ void QQuickTableViewPrivate::updatePolish()
if (loadedItems.isEmpty())
return;
- if (columnRowPositionsInvalid)
+ if (columnRowPositionsInvalid) {
relayoutTable();
+ updateAverageEdgeSize();
+ updateContentWidth();
+ updateContentHeight();
+ }
loadAndUnloadVisibleEdges();
}
@@ -1609,6 +1854,12 @@ void QQuickTableViewPrivate::syncRebuildOptions()
rebuildOptions = scheduledRebuildOptions;
scheduledRebuildOptions = RebuildOption::None;
rebuildScheduled = false;
+
+ if (loadedItems.isEmpty()) {
+ // If we have no items from before, we cannot just rebuild the viewport, but need
+ // to rebuild everything, since we have no top-left loaded item to start from.
+ rebuildOptions.setFlag(RebuildOption::All);
+ }
}
void QQuickTableViewPrivate::syncDelegate()
@@ -1787,6 +2038,16 @@ QQuickTableView::QQuickTableView(QQuickItem *parent)
setFlag(QQuickItem::ItemIsFocusScope);
}
+QQuickTableView::~QQuickTableView()
+{
+}
+
+QQuickTableView::QQuickTableView(QQuickTableViewPrivate &dd, QQuickItem *parent)
+ : QQuickFlickable(dd, parent)
+{
+ setFlag(QQuickItem::ItemIsFocusScope);
+}
+
int QQuickTableView::rows() const
{
return d_func()->tableSize.height();
@@ -1936,16 +2197,7 @@ void QQuickTableView::setContentHeight(qreal height)
void QQuickTableView::forceLayout()
{
- Q_D(QQuickTableView);
- d->columnRowPositionsInvalid = true;
-
- if (d->polishing) {
- qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!";
- polish();
- return;
- }
-
- d->updatePolish();
+ d_func()->forceLayout();
}
QQuickTableViewAttached *QQuickTableView::qmlAttachedProperties(QObject *obj)