diff options
Diffstat (limited to 'src/quick/items/qquicktableview.cpp')
-rw-r--r-- | src/quick/items/qquicktableview.cpp | 373 |
1 files changed, 350 insertions, 23 deletions
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index c7882fb2b1..5adfd20bf0 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -355,6 +355,98 @@ */ /*! + \qmlmethod QtQuick::TableView::positionViewAtCell(point cell, Qt.Alignment alignment, point offset) + + Positions \l contentX and \l contentY such that \a cell is at the position specified by + \a alignment. \a alignment can be an or-ed combination of the following: + + \list + \li Qt.AlignLeft - position the cell at the left of the view. + \li Qt.AlignHCenter - position the cell at the horizontal center of the view. + \li Qt.AlignRight - position the cell at the right of the view. + \li Qt.AlignTop - position the cell at the top of the view. + \li Qt.AlignVCenter - position the cell at the vertical center of the view. + \li Qt.AlignBottom - position the cell at the bottom of the view. + \li Qt.AlignCenter - the same as (Qt.AlignHCenter | Qt.AlignVCenter) + \endlist + + Optionally, you can specify \a offset to move \l contentX and \l contentY an extra number of + pixels beyond the target alignment. E.g if you want to position the view so + that cell [10, 10] ends up at the top-left corner with a 5px margin, you could do: + + \code + positionViewAtCell(Qt.point(10, 10), Qt.AlignLeft | Qt.AlignTop, Qt.point(-5, -5)) + \endcode +*/ + +/*! + \qmlmethod QtQuick::TableView::positionViewAtCell(int column, int row, Qt.Alignment alignment, point offset) + + Convenience for calling \code positionViewAtCell(Qt.point(column, row), alignment, offset) +*/ + +/*! + \qmlmethod QtQuick::TableView::positionViewAtRow(int row, Qt.Alignment alignment, real offset) + + Positions \l contentY such that \a row is at the position specified by + \a alignment. \a alignment can be one of the following: + + \list + \li Qt.AlignTop - position the row at the top of the view. + \li Qt.AlignVCenter - position the cell at the vertical center of the view. + \li Qt.AlignCenter - the same as Qt.AlignVCenter. + \li Qt.AlignBottom - position the cell at the bottom of the view. + \endlist + + Optionally, you can specify \a offset to move \l contentY an extra number of + pixels beyond the target alignment. E.g if you want to position the view so + that row 10 ends up at the bottom with a 5px margin, you could do: + + \code + positionViewAtRow(10, Qt.AlignBottom, 5) + \endcode +*/ + +/*! + \qmlmethod QtQuick::TableView::positionViewAtColumn(int column, Qt.Alignment alignment, real offset) + + Positions \l contentX such that \a column is at the position specified by + \a alignment. \a alignment can be one of the following: + + \list + \li Qt.AlignLeft - position the column at the left of the view. + \li Qt.AlignHCenter - position the column at the horizontal center of the view. + \li Qt.AlignCenter - the same as Qt.AlignVCenter. + \li Qt.AlignRight - position the column at the right of the view. + \endlist + + Optionally, you can specify \a offset to move \l contentX an extra number of + pixels to the side of the target alignment. E.g if you want to position the view so + that column 10 ends up at the left side with a 5px margin, you could do: + + \code + positionViewAtColumn(10, Qt.AlignLeft, -5) + \endcode +*/ + +/*! + \qmlmethod Item QtQuick::TableView::itemAtCell(point cell) + + Returns the delegate item at \a cell if loaded, otherwise \c null. + + \note only the items that are visible in the view are normally loaded. + As soon as a cell is flicked out of the view, the item inside will + either be unloaded or placed in the recycle pool. As such, the return + value should never be stored. +*/ + +/*! + \qmlmethod Item QtQuick::TableView::itemAtCell(int column, int row) + + Convenience for calling \code itemAtCell(Qt.point(column, row)) \endcode +*/ + +/*! \qmlattachedproperty TableView QtQuick::TableView::view This attached property holds the view that manages the delegate instance. @@ -550,10 +642,10 @@ int QQuickTableViewPrivate::nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge) // 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; + case Qt::LeftEdge: startIndex = leftColumn() - 1; break; + case Qt::RightEdge: startIndex = rightColumn() + 1; break; + case Qt::TopEdge: startIndex = topRow() - 1; break; + case Qt::BottomEdge: startIndex = bottomRow() + 1; break; } return nextVisibleEdgeIndex(edge, startIndex); @@ -913,6 +1005,18 @@ void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable() loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft()); } +void QQuickTableViewPrivate::shiftLoadedTableRect(const QPointF newPosition) +{ + // Move the tracked table rects to the new position. For this to + // take visual effect (move the delegate items to be inside the table + // rect), it needs to be followed by a relayoutTableItems(). + // Also note that the position of the viewport needs to be adjusted + // separately for it to overlap the loaded table. + const QPointF innerDiff = loadedTableOuterRect.topLeft() - loadedTableInnerRect.topLeft(); + loadedTableOuterRect.moveTopLeft(newPosition); + loadedTableInnerRect.moveTopLeft(newPosition + innerDiff); +} + QQuickTableViewPrivate::RebuildOptions QQuickTableViewPrivate::checkForVisibilityChanges() { // Go through all columns from first to last, find the columns that used @@ -1298,6 +1402,20 @@ qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column) return columnWidth; } +qreal QQuickTableViewPrivate::getEffectiveRowHeight(int row) const +{ + // Return row height after layout + Q_TABLEVIEW_ASSERT(loadedRows.contains(row), row); + return loadedTableItem(QPoint(leftColumn(), row))->geometry().height(); +} + +qreal QQuickTableViewPrivate::getEffectiveColumnWidth(int column) const +{ + // Return column width after layout + Q_TABLEVIEW_ASSERT(loadedColumns.contains(column), column); + return loadedTableItem(QPoint(column, topRow()))->geometry().width(); +} + qreal QQuickTableViewPrivate::getRowLayoutHeight(int row) { // Return the row height specified by the application, or go @@ -1618,7 +1736,9 @@ void QQuickTableViewPrivate::processLoadRequest() loadRequest.markAsDone(); - qCDebug(lcTableViewDelegateLifecycle()) << "request completed! Table:" << tableLayoutToString(); + qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString(); + qCDebug(lcTableViewDelegateLifecycle()) << "Load request completed!"; + qCDebug(lcTableViewDelegateLifecycle()) << "****************************************"; } void QQuickTableViewPrivate::processRebuildTable() @@ -1642,7 +1762,7 @@ void QQuickTableViewPrivate::processRebuildTable() moveToNextRebuildState(); if (rebuildState == RebuildState::LoadInitalTable) { - beginRebuildTable(); + loadInitialTable(); if (!moveToNextRebuildState()) return; } @@ -1695,7 +1815,10 @@ void QQuickTableViewPrivate::processRebuildTable() } Q_TABLEVIEW_ASSERT(rebuildState == RebuildState::Done, int(rebuildState)); - qCDebug(lcTableViewDelegateLifecycle()) << "rebuild complete:" << q; + qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString(); + qCDebug(lcTableViewDelegateLifecycle()) << "rebuild completed!"; + qCDebug(lcTableViewDelegateLifecycle()) << "################################################"; + qCDebug(lcTableViewDelegateLifecycle()); } bool QQuickTableViewPrivate::moveToNextRebuildState() @@ -1788,10 +1911,16 @@ void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topL const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width())); topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1); topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width()); + } else if (rebuildOptions & RebuildOption::PositionViewAtColumn) { + topLeftCell.rx() = qBound(0, positionViewAtColumn, tableSize.width() - 1); + topLeftPos.rx() = qFloor(topLeftCell.x()) * (averageEdgeSize.width() + cellSpacing.width()); } else { // Keep the current top left, unless it's outside model topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1); - topLeftPos.rx() = loadedTableOuterRect.topLeft().x(); + // We begin by loading the columns where the viewport is at + // now. But will move the whole table and the viewport + // later, when we do a layoutAfterLoadingInitialTable(). + topLeftPos.rx() = loadedTableOuterRect.x(); } } @@ -1808,21 +1937,25 @@ void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topL 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 if (rebuildOptions & RebuildOption::PositionViewAtRow) { + topLeftCell.ry() = qBound(0, positionViewAtRow, tableSize.height() - 1); + topLeftPos.ry() = qFloor(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(); + topLeftPos.ry() = loadedTableOuterRect.y(); } } } -void QQuickTableViewPrivate::beginRebuildTable() +void QQuickTableViewPrivate::loadInitialTable() { updateTableSize(); QPoint topLeft; QPointF topLeftPos; calculateTopLeft(topLeft, topLeftPos); + qCDebug(lcTableViewDelegateLifecycle()) << "initial viewport rect:" << viewportRect; + qCDebug(lcTableViewDelegateLifecycle()) << "initial top left cell:" << topLeft << ", pos:" << topLeftPos; if (!loadedItems.isEmpty()) { if (rebuildOptions & RebuildOption::All) @@ -1845,15 +1978,17 @@ void QQuickTableViewPrivate::beginRebuildTable() loadedTableInnerRect = QRect(); clearEdgeSizeCache(); - if (syncHorizontally) { + if (syncHorizontally) setLocalViewportX(syncView->contentX()); - viewportRect.moveLeft(syncView->d_func()->viewportRect.left()); - } - if (syncVertically) { + if (syncVertically) setLocalViewportY(syncView->contentY()); - viewportRect.moveTop(syncView->d_func()->viewportRect.top()); - } + + if (!syncHorizontally && rebuildOptions & RebuildOption::PositionViewAtColumn) + setLocalViewportX(topLeftPos.x()); + + if (!syncVertically && rebuildOptions & RebuildOption::PositionViewAtRow) + setLocalViewportY(topLeftPos.y()); if (!model) { qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty"; @@ -1911,6 +2046,74 @@ void QQuickTableViewPrivate::layoutAfterLoadingInitialTable() } updateExtents(); + adjustViewportXAccordingToAlignment(); + adjustViewportYAccordingToAlignment(); +} + +void QQuickTableViewPrivate::adjustViewportXAccordingToAlignment() +{ + // Check if we are supposed to position the viewport at a certain column + if (!rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn)) + return; + // The requested column might have been hidden or is outside model bounds + if (positionViewAtColumn != leftColumn()) + return; + + const float columnWidth = getEffectiveColumnWidth(positionViewAtColumn); + + switch (positionViewAtColumnAlignment) { + case Qt::AlignLeft: + setLocalViewportX(loadedTableOuterRect.left() + positionViewAtColumnOffset); + break; + case Qt::AlignHCenter: + setLocalViewportX(loadedTableOuterRect.left() + - (viewportRect.width() / 2) + + (columnWidth / 2) + + positionViewAtColumnOffset); + break; + case Qt::AlignRight: + setLocalViewportX(loadedTableOuterRect.left() + - viewportRect.width() + + columnWidth + + positionViewAtColumnOffset); + break; + default: + Q_TABLEVIEW_UNREACHABLE("options are checked in setter"); + break; + } +} + +void QQuickTableViewPrivate::adjustViewportYAccordingToAlignment() +{ + // Check if we are supposed to position the viewport at a certain row + if (!rebuildOptions.testFlag(RebuildOption::PositionViewAtRow)) + return; + // The requested row might have been hidden or is outside model bounds + if (positionViewAtRow != topRow()) + return; + + const float rowHeight = getEffectiveRowHeight(positionViewAtRow); + + switch (positionViewAtRowAlignment) { + case Qt::AlignTop: + setLocalViewportY(loadedTableOuterRect.top() + positionViewAtRowOffset); + break; + case Qt::AlignVCenter: + setLocalViewportY(loadedTableOuterRect.top() + - (viewportRect.height() / 2) + + (rowHeight / 2) + + positionViewAtRowOffset); + break; + case Qt::AlignBottom: + setLocalViewportY(loadedTableOuterRect.top() + - viewportRect.height() + + rowHeight + + positionViewAtRowOffset); + break; + default: + Q_TABLEVIEW_UNREACHABLE("options are checked in setter"); + break; + } } void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge) @@ -1944,7 +2147,7 @@ void QQuickTableViewPrivate::loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMo const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge); qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex; - const QList<int> visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge) + const auto visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge) ? loadedRows.keys() : loadedColumns.keys(); loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode); processLoadRequest(); @@ -2081,9 +2284,15 @@ bool QQuickTableViewPrivate::updateTableRecursive() if (!updateComplete) return false; - for (auto syncChild : qAsConst(syncChildren)) { + const auto children = syncChildren; + for (auto syncChild : children) { auto syncChild_d = syncChild->d_func(); - syncChild_d->scheduledRebuildOptions |= rebuildOptions; + const int mask = + RebuildOption::PositionViewAtRow | + RebuildOption::PositionViewAtColumn | + RebuildOption::CalculateNewTopLeftRow | + RebuildOption::CalculateNewTopLeftColumn; + syncChild_d->scheduledRebuildOptions |= rebuildOptions & ~mask; const bool descendantUpdateComplete = syncChild_d->updateTableRecursive(); if (!descendantUpdateComplete) @@ -2151,15 +2360,17 @@ void QQuickTableViewPrivate::fixup(QQuickFlickablePrivate::AxisData &data, qreal QQuickFlickablePrivate::fixup(data, minExtent, maxExtent); } -int QQuickTableViewPrivate::resolveImportVersion() +QTypeRevision QQuickTableViewPrivate::resolveImportVersion() { const auto data = QQmlData::get(q_func()); if (!data || !data->propertyCache) - return 0; + return QTypeRevision::zero(); const auto cppMetaObject = data->propertyCache->firstCppMetaObject(); const auto qmlTypeView = QQmlMetaType::qmlType(cppMetaObject); - return qmlTypeView.minorVersion(); + + // TODO: did we rather want qmlTypeView.revision() here? + return qmlTypeView.metaObjectRevision(); } void QQuickTableViewPrivate::createWrapperModel() @@ -2234,6 +2445,7 @@ void QQuickTableViewPrivate::syncWithPendingChanges() syncModel(); syncDelegate(); syncSyncView(); + syncPositionView(); syncRebuildOptions(); } @@ -2257,6 +2469,12 @@ void QQuickTableViewPrivate::syncRebuildOptions() } else if (rebuildOptions.testFlag(RebuildOption::ViewportOnly)) { rebuildOptions.setFlag(RebuildOption::LayoutOnly, false); } + + if (rebuildOptions.testFlag(RebuildOption::PositionViewAtRow)) + rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftRow, false); + + if (rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn)) + rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftColumn, false); } void QQuickTableViewPrivate::syncDelegate() @@ -2373,6 +2591,16 @@ void QQuickTableViewPrivate::syncSyncView() } } +void QQuickTableViewPrivate::syncPositionView() +{ + // Only positionViewAtRow/positionViewAtColumn are critical + // to sync before a rebuild to avoid them being overwritten + // by the setters while building. The other position properties + // can change without it causing trouble. + positionViewAtRow = assignedPositionViewAtRow; + positionViewAtColumn = assignedPositionViewAtColumn; +} + void QQuickTableViewPrivate::connectToModel() { Q_Q(QQuickTableView); @@ -2542,6 +2770,10 @@ void QQuickTableViewPrivate::setLocalViewportX(qreal contentX) return; q->setContentX(contentX); + + // Keep our own viewportRect in sync + viewportRect.moveLeft(contentX); + qCDebug(lcTableViewDelegateLifecycle) << "viewportRect adjusted to:" << viewportRect; } void QQuickTableViewPrivate::setLocalViewportY(qreal contentY) @@ -2556,6 +2788,10 @@ void QQuickTableViewPrivate::setLocalViewportY(qreal contentY) return; q->setContentY(contentY); + + // Keep our own viewportRect in sync + viewportRect.moveTop(contentY); + qCDebug(lcTableViewDelegateLifecycle) << "viewportRect adjusted to:" << viewportRect; } void QQuickTableViewPrivate::syncViewportPosRecursive() @@ -2833,6 +3069,97 @@ void QQuickTableView::setSyncDirection(Qt::Orientations direction) emit syncDirectionChanged(); } +void QQuickTableView::positionViewAtCell(const QPoint &cell, Qt::Alignment alignment, const QPointF &offset) +{ + positionViewAtRow(cell.y(), alignment & Qt::AlignVertical_Mask, offset.y()); + positionViewAtColumn(cell.x(), alignment & Qt::AlignHorizontal_Mask, offset.x()); +} + +void QQuickTableView::positionViewAtCell(int column, int row, Qt::Alignment alignment, const QPointF &offset) +{ + positionViewAtCell(QPoint(column, row), alignment, offset); +} + +void QQuickTableView::positionViewAtRow(int row, Qt::Alignment alignment, qreal offset) +{ + Q_D(QQuickTableView); + + if (d->syncVertically) { + d->syncView->positionViewAtRow(row, alignment, offset); + return; + } + + // Clean up flags, in case it has + // Qt::AlignCenter set (which we allow) + Qt::Alignment adjustedAlignment = alignment; + adjustedAlignment.setFlag(Qt::AlignHCenter, false); + if (!int(adjustedAlignment)) + adjustedAlignment.setFlag(Qt::AlignTop); + + switch (adjustedAlignment) { + case Qt::AlignTop: + case Qt::AlignVCenter: + case Qt::AlignBottom: + break; + default: + qmlWarning(this) << "Unsupported alignment. Use AlignTop, AlignVCenter, or AlignBottom"; + return; + } + + d->assignedPositionViewAtRow = row; + d->positionViewAtRowAlignment = adjustedAlignment; + d->positionViewAtRowOffset = offset; + d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly | + QQuickTableViewPrivate::RebuildOption::PositionViewAtRow); +} + +void QQuickTableView::positionViewAtColumn(int column, Qt::Alignment alignment, qreal offset) +{ + Q_D(QQuickTableView); + + if (d->syncHorizontally) { + d->syncView->positionViewAtColumn(column, alignment, offset); + return; + } + + // Clean up flags, in case it has + // Qt::AlignCenter set (which we allow) + Qt::Alignment adjustedAlignment = alignment; + adjustedAlignment.setFlag(Qt::AlignVCenter, false); + if (!int(adjustedAlignment)) + adjustedAlignment.setFlag(Qt::AlignLeft); + + switch (adjustedAlignment) { + case Qt::AlignLeft: + case Qt::AlignHCenter: + case Qt::AlignRight: + break; + default: + qmlWarning(this) << "Unsupported alignment. Use AlignLeft, AlignHCenter, or AlignRight"; + return; + } + + d->assignedPositionViewAtColumn = column; + d->positionViewAtColumnAlignment = adjustedAlignment; + d->positionViewAtColumnOffset = offset; + d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly | + QQuickTableViewPrivate::RebuildOption::PositionViewAtColumn); +} + +QQuickItem *QQuickTableView::itemAtCell(const QPoint &cell) const +{ + Q_D(const QQuickTableView); + const int modelIndex = d->modelIndexAtCell(cell); + if (!d->loadedItems.contains(modelIndex)) + return nullptr; + return d->loadedItems.value(modelIndex)->item; +} + +QQuickItem *QQuickTableView::itemAtCell(int column, int row) const +{ + return itemAtCell(QPoint(column, row)); +} + void QQuickTableView::forceLayout() { d_func()->forceLayout(); |