// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QQUICKTABLEVIEW_P_P_H #define QQUICKTABLEVIEW_P_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qquicktableview_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcTableViewDelegateLifecycle) static const qreal kDefaultRowHeight = 50; static const qreal kDefaultColumnWidth = 50; static const int kEdgeIndexNotSet = -2; static const int kEdgeIndexAtEnd = -3; class FxTableItem; class QQuickTableSectionSizeProviderPrivate; /*! \internal * TableView uses QQuickTableViewHoverHandler to track where the pointer is * on top of the table, and change the cursor at the places where a drag * would start a resize of a row or a column. */ class QQuickTableViewHoverHandler : public QQuickHoverHandler { Q_OBJECT public: QQuickTableViewHoverHandler(QQuickTableView *view); inline bool isHoveringGrid() const { return m_row != -1 || m_column != -1; }; int m_row = -1; int m_column = -1; friend class QQuickTableViewPrivate; protected: void handleEventPoint(QPointerEvent *event, QEventPoint &point) override; }; class QQuickTableViewPointerHandler : public QQuickSinglePointHandler { Q_OBJECT public: enum State { Listening, // the pointer is not being pressed between the cells Tracking, // the pointer is being pressed between the cells DraggingStarted, // dragging started Dragging, // a drag is ongoing DraggingFinished // dragging was finished }; QQuickTableViewPointerHandler(QQuickTableView *view); State m_state = Listening; State state() { return m_state; } protected: bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override; }; /*! \internal * TableView uses QQuickTableViewResizeHandler to enable the user to resize * rows and columns. By using a custom pointer handler, we can get away with * using a single pointer handler for the whole content item, rather than * e.g having to split it up into multiple items with drag handlers placed * between the cells. */ class QQuickTableViewResizeHandler : public QQuickTableViewPointerHandler { Q_OBJECT public: QQuickTableViewResizeHandler(QQuickTableView *view); int m_row = -1; qreal m_rowStartY = -1; qreal m_rowStartHeight = -1; int m_column = -1; qreal m_columnStartX = -1; qreal m_columnStartWidth = -1; void updateState(QEventPoint &point); void updateDrag(QPointerEvent *event, QEventPoint &point); friend class QQuickTableViewPrivate; protected: void handleEventPoint(QPointerEvent *event, QEventPoint &point) override; void onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *ev, QEventPoint &point) override; }; class QQuickTableViewSectionDragHandler : public QQuickTableViewPointerHandler { Q_OBJECT public: QQuickTableViewSectionDragHandler(QQuickTableView *view); ~QQuickTableViewSectionDragHandler(); void grabSection(); void handleDrag(QQuickDragEvent *event); void handleDrop(QQuickDragEvent *event); void handleDragDropAction(Qt::DropAction action); void setSectionOrientation(Qt::Orientation orientation) { m_sectionOrientation = orientation; } friend class QQuickTableViewPrivate; protected: void handleEventPoint(QPointerEvent *event, QEventPoint &point) override; private: void resetDragData(); void resetSectionOverlay(); QSharedPointer m_grabResult; QPointer m_drag; int m_source = -1; int m_destination = -1; QPointer m_dropArea; Qt::Orientation m_sectionOrientation; QPointF m_dragPoint; QSizeF m_step = QSizeF(1, 1); QTimer m_scrollTimer; }; /*! \internal * QQuickTableViewTapHandler used to handle tap events explicitly for table view */ class QQuickTableViewTapHandler : public QQuickTapHandler { Q_OBJECT public: explicit QQuickTableViewTapHandler(QQuickTableView *view); bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override; friend class QQuickTableViewPrivate; }; class Q_QUICK_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate, public QQuickSelectable { public: Q_DECLARE_PUBLIC(QQuickTableView) class TableEdgeLoadRequest { // Whenever we need to load new rows or columns in the // table, we fill out a TableEdgeLoadRequest. // TableEdgeLoadRequest is just a struct that keeps track // of which cells that needs to be loaded, and which cell // the table is currently loading. The loading itself is // done by QQuickTableView. public: void begin(const QPoint &cell, const QPointF &pos, QQmlIncubator::IncubationMode incubationMode) { Q_ASSERT(!m_active); m_active = true; m_edge = Qt::Edge(0); m_mode = incubationMode; m_edgeIndex = cell.x(); m_visibleCellsInEdge.clear(); m_visibleCellsInEdge.append(cell.y()); m_currentIndex = 0; m_startPos = pos; qCDebug(lcTableViewDelegateLifecycle()) << "begin top-left:" << toString(); } void begin(Qt::Edge edgeToLoad, int edgeIndex, const QVector visibleCellsInEdge, QQmlIncubator::IncubationMode incubationMode) { Q_ASSERT(!m_active); m_active = true; m_edge = edgeToLoad; m_edgeIndex = edgeIndex; m_visibleCellsInEdge = visibleCellsInEdge; m_mode = incubationMode; m_currentIndex = 0; qCDebug(lcTableViewDelegateLifecycle()) << "begin:" << toString(); } inline void markAsDone() { m_active = false; } inline bool isActive() const { return m_active; } inline QPoint currentCell() const { return cellAt(m_currentIndex); } inline bool hasCurrentCell() const { return m_currentIndex < m_visibleCellsInEdge.size(); } inline void moveToNextCell() { ++m_currentIndex; } inline Qt::Edge edge() const { return m_edge; } inline int row() const { return cellAt(0).y(); } inline int column() const { return cellAt(0).x(); } inline QQmlIncubator::IncubationMode incubationMode() const { return m_mode; } inline QPointF startPosition() const { return m_startPos; } QString toString() const { QString str; QDebug dbg(&str); dbg.nospace() << "TableSectionLoadRequest(" << "edge:" << m_edge << ", edgeIndex:" << m_edgeIndex << ", incubation:"; switch (m_mode) { case QQmlIncubator::Asynchronous: dbg << "Asynchronous"; break; case QQmlIncubator::AsynchronousIfNested: dbg << "AsynchronousIfNested"; break; case QQmlIncubator::Synchronous: dbg << "Synchronous"; break; } return str; } private: Qt::Edge m_edge = Qt::Edge(0); QVector m_visibleCellsInEdge; int m_edgeIndex = 0; int m_currentIndex = 0; bool m_active = false; QQmlIncubator::IncubationMode m_mode = QQmlIncubator::AsynchronousIfNested; QPointF m_startPos; inline QPoint cellAt(int index) const { return !m_edge || (m_edge & (Qt::LeftEdge | Qt::RightEdge)) ? QPoint(m_edgeIndex, m_visibleCellsInEdge[index]) : QPoint(m_visibleCellsInEdge[index], m_edgeIndex); } }; class EdgeRange { public: EdgeRange(); bool containsIndex(Qt::Edge edge, int index); int startIndex; int endIndex; qreal size; }; enum class RebuildState { Begin = 0, LoadInitalTable, VerifyTable, LayoutTable, CancelOvershoot, UpdateContentSize, PreloadColumns, PreloadRows, MovePreloadedItemsToPool, Done }; enum class SectionState { Idle = 0, Moving }; enum class RebuildOption { None = 0, All = 0x1, LayoutOnly = 0x2, ViewportOnly = 0x4, CalculateNewTopLeftRow = 0x8, CalculateNewTopLeftColumn = 0x10, CalculateNewContentWidth = 0x20, CalculateNewContentHeight = 0x40, PositionViewAtRow = 0x80, PositionViewAtColumn = 0x100, }; Q_DECLARE_FLAGS(RebuildOptions, RebuildOption) public: QQuickTableViewPrivate(); ~QQuickTableViewPrivate() override; static inline QQuickTableViewPrivate *get(QQuickTableView *q) { return q->d_func(); } void updatePolish() override; void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override; public: QHash loadedItems; // model, tableModel and modelVariant all point to the same model. modelVariant // is the model assigned by the user. And tableModel is the wrapper model we create // around it. But if the model is an instance model directly, we cannot wrap it, so // we need a pointer for that case as well. QQmlInstanceModel* model = nullptr; QPointer tableModel = nullptr; QVariant modelVariant; // When the applications assignes a new model or delegate to the view, we keep them // around until we're ready to take them into use (syncWithPendingChanges). QVariant assignedModel = QVariant(int(0)); QQmlGuard assignedDelegate; // loadedRows/Columns describes the rows and columns that are currently loaded (from top left // row/column to bottom right row/column). loadedTableOuterRect describes the actual // pixels that all the loaded delegate items cover, and is matched agains the viewport to determine when // we need to fill up with more rows/columns. loadedTableInnerRect describes the pixels // that the loaded table covers if you remove one row/column on each side of the table, and // is used to determine rows/columns that are no longer visible and can be unloaded. QMinimalFlatSet loadedColumns; QMinimalFlatSet loadedRows; QRectF loadedTableOuterRect; QRectF loadedTableInnerRect; QPointF origin = QPointF(0, 0); QSizeF endExtent = QSizeF(0, 0); QRectF viewportRect = QRectF(0, 0, -1, -1); QSize tableSize; RebuildState rebuildState = RebuildState::Done; RebuildOptions rebuildOptions = RebuildOption::All; RebuildOptions scheduledRebuildOptions = RebuildOption::All; TableEdgeLoadRequest loadRequest; QSizeF cellSpacing = QSizeF(0, 0); QQmlTableInstanceModel::ReusableFlag reusableFlag = QQmlTableInstanceModel::Reusable; bool blockItemCreatedCallback = false; mutable bool layoutWarningIssued = false; bool polishing = false; bool syncVertically = false; bool syncHorizontally = false; bool inSetLocalViewportPos = false; bool inSyncViewportPosRecursive = false; bool inUpdateContentSize = false; bool animate = true; bool keyNavigationEnabled = true; bool pointerNavigationEnabled = true; bool alternatingRows = true; bool resizableColumns = false; bool resizableRows = false; #if QT_CONFIG(cursor) bool m_cursorSet = false; #endif // isTransposed is currently only used by HeaderView. // Consider making it public. bool isTransposed = false; bool warnNoSelectionModel = true; QJSValue rowHeightProvider; QJSValue columnWidthProvider; mutable EdgeRange cachedNextVisibleEdgeIndex[4]; mutable EdgeRange cachedColumnWidth; mutable EdgeRange cachedRowHeight; // TableView uses contentWidth/height to report the size of the table (this // will e.g make scrollbars written for Flickable work out of the box). This // value is continuously calculated, and will change/improve as more columns // are loaded into view. At the same time, we want to open up for the // possibility that the application can set the content width explicitly, in // case it knows what the exact width should be from the start. We therefore // override the contentWidth/height properties from QQuickFlickable, to be able // to implement this combined behavior. This also lets us lazy build the table // if the application needs to know the content size early on. QQmlNullableValue explicitContentWidth; QQmlNullableValue explicitContentHeight; QSizeF averageEdgeSize; QPointer assignedSyncView; QPointer syncView; QList > syncChildren; Qt::Orientations assignedSyncDirection = Qt::Horizontal | Qt::Vertical; QPointer selectionModel; QQuickTableView::SelectionBehavior selectionBehavior = QQuickTableView::SelectCells; QQuickTableView::SelectionMode selectionMode = QQuickTableView::ExtendedSelection; QItemSelectionModel::SelectionFlag selectionFlag = QItemSelectionModel::NoUpdate; std::function selectableCallbackFunction; bool inSelectionModelUpdate = false; int assignedPositionViewAtRowAfterRebuild = 0; int assignedPositionViewAtColumnAfterRebuild = 0; int positionViewAtRowAfterRebuild = 0; int positionViewAtColumnAfterRebuild = 0; qreal positionViewAtRowOffset = 0; qreal positionViewAtColumnOffset = 0; QRectF positionViewAtRowSubRect; QRectF positionViewAtColumnSubRect; Qt::Alignment positionViewAtRowAlignment = Qt::AlignTop; Qt::Alignment positionViewAtColumnAlignment = Qt::AlignLeft; QQuickPropertyAnimation positionXAnimation; QQuickPropertyAnimation positionYAnimation; QPoint selectionStartCell = {-1, -1}; QPoint selectionEndCell = {-1, -1}; QItemSelection existingSelection; QMargins edgesBeforeRebuild; QSize tableSizeBeforeRebuild; int currentRow = -1; int currentColumn = -1; QHash explicitColumnWidths; QHash explicitRowHeights; QQuickTableViewHoverHandler *hoverHandler = nullptr; QQuickTableViewResizeHandler *resizeHandler = nullptr; QQuickTableViewSectionDragHandler *sectionDragHandler = nullptr; QQuickTableViewPointerHandler *activePtrHandler = nullptr; QQmlTableInstanceModel *editModel = nullptr; QQuickItem *editItem = nullptr; QPersistentModelIndex editIndex; QQuickTableView::EditTriggers editTriggers = QQuickTableView::DoubleTapped | QQuickTableView::EditKeyPressed; #ifdef QT_DEBUG QString forcedIncubationMode = qEnvironmentVariable("QT_TABLEVIEW_INCUBATION_MODE"); #endif struct SectionData { int index = -1; int prevIndex = -1; }; QList visualIndices[Qt::Vertical]; QList logicalIndices[Qt::Vertical]; SectionState m_sectionState = SectionState::Idle; public: void init(); QQuickTableViewAttached *getAttachedObject(const QObject *object) const; int modelIndexAtCell(const QPoint &cell) const; QPoint cellAtModelIndex(int modelIndex) const; int modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex = true) const; inline bool cellIsValid(const QPoint &cell) const { return cell.x() != -1 && cell.y() != -1; } qreal sizeHintForColumn(int column) const; qreal sizeHintForRow(int row) const; QSize calculateTableSize(); void updateTableSize(); inline bool isColumnHidden(int column) const; inline bool isRowHidden(int row) const; qreal getColumnLayoutWidth(int column); qreal getRowLayoutHeight(int row); qreal getColumnWidth(int column) const; qreal getRowHeight(int row) const; qreal getEffectiveRowY(int row) const; qreal getEffectiveRowHeight(int row) const; qreal getEffectiveColumnX(int column) const; qreal getEffectiveColumnWidth(int column) const; qreal getAlignmentContentX(int column, Qt::Alignment alignment, const qreal offset, const QRectF &subRect); qreal getAlignmentContentY(int row, Qt::Alignment alignment, const qreal offset, const QRectF &subRect); int topRow() const { return *loadedRows.cbegin(); } int bottomRow() const { return *loadedRows.crbegin(); } int leftColumn() const { return *loadedColumns.cbegin(); } int rightColumn() const { return *loadedColumns.crbegin(); } QQuickTableView *rootSyncView() const; bool updateTableRecursive(); bool updateTable(); void relayoutTableItems(); void layoutVerticalEdge(Qt::Edge tableEdge); void layoutHorizontalEdge(Qt::Edge tableEdge); void layoutTopLeftItem(); void layoutTableEdgeFromLoadRequest(); void updateContentWidth(); void updateContentHeight(); void updateAverageColumnWidth(); void updateAverageRowHeight(); RebuildOptions checkForVisibilityChanges(); void forceLayout(bool immediate); void updateExtents(); void syncLoadedTableRectFromLoadedTable(); void syncLoadedTableFromLoadRequest(); void shiftLoadedTableRect(const QPointF newPosition); int nextVisibleEdgeIndex(Qt::Edge edge, int startIndex) const; int nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge) const; inline bool atTableEnd(Qt::Edge edge) const { return nextVisibleEdgeIndexAroundLoadedTable(edge) == kEdgeIndexAtEnd; } inline bool atTableEnd(Qt::Edge edge, int startIndex) const { return nextVisibleEdgeIndex(edge, startIndex) == kEdgeIndexAtEnd; } inline int edgeToArrayIndex(Qt::Edge edge) const; void clearEdgeSizeCache(); bool canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const; bool canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const; Qt::Edge nextEdgeToLoad(const QRectF rect); Qt::Edge nextEdgeToUnload(const QRectF rect); qreal cellWidth(const QPoint &cell) const; qreal cellHeight(const QPoint &cell) const; FxTableItem *loadedTableItem(const QPoint &cell) const; FxTableItem *createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode); FxTableItem *loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode); void releaseItem(FxTableItem *fxTableItem, QQmlTableInstanceModel::ReusableFlag reusableFlag); void releaseLoadedItems(QQmlTableInstanceModel::ReusableFlag reusableFlag); void unloadItem(const QPoint &cell); void loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMode incubationMode); void unloadEdge(Qt::Edge edge); void loadAndUnloadVisibleEdges(QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested); void drainReusePoolAfterLoadRequest(); void processLoadRequest(); void processRebuildTable(); bool moveToNextRebuildState(); void calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos); void loadInitialTable(); void layoutAfterLoadingInitialTable(); void adjustViewportXAccordingToAlignment(); void adjustViewportYAccordingToAlignment(); void cancelOvershootAfterLayout(); void scheduleRebuildTable(QQuickTableViewPrivate::RebuildOptions options); #if QT_CONFIG(cursor) void updateCursor(); #endif void updateEditItem(); void updateContentSize(); QTypeRevision resolveImportVersion(); void createWrapperModel(); QAbstractItemModel *qaim(QVariant modelAsVariant) const; virtual void initItemCallback(int modelIndex, QObject *item); virtual void itemCreatedCallback(int modelIndex, QObject *object); virtual void itemPooledCallback(int modelIndex, QObject *object); virtual void itemReusedCallback(int modelIndex, QObject *object); virtual void modelUpdated(const QQmlChangeSet &changeSet, bool reset); virtual void syncWithPendingChanges(); virtual void syncDelegate(); virtual QVariant modelImpl() const; virtual void setModelImpl(const QVariant &newModel); virtual void syncModel(); virtual void syncSyncView(); virtual void syncPositionView(); virtual QAbstractItemModel *selectionSourceModel(); inline void syncRebuildOptions(); void connectToModel(); void disconnectFromModel(); void rowsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); void columnsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column); void rowsInsertedCallback(const QModelIndex &parent, int begin, int end); void rowsRemovedCallback(const QModelIndex &parent, int begin, int end); void columnsInsertedCallback(const QModelIndex &parent, int begin, int end); void columnsRemovedCallback(const QModelIndex &parent, int begin, int end); void layoutChangedCallback(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); void modelResetCallback(); bool compareModel(const QVariant& model1, const QVariant& model2) const; void positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF()); void positionViewAtColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF()); bool scrollToRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF()); bool scrollToColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF()); void scheduleRebuildIfFastFlick(); void setLocalViewportX(qreal contentX); void setLocalViewportY(qreal contentY); void syncViewportRect(); void syncViewportPosRecursive(); bool selectedInSelectionModel(const QPoint &cell) const; void selectionChangedInSelectionModel(const QItemSelection &selected, const QItemSelection &deselected); void updateSelectedOnAllDelegateItems(); void setSelectedOnDelegateItem(const QModelIndex &modelIndex, bool select); bool currentInSelectionModel(const QPoint &cell) const; void currentChangedInSelectionModel(const QModelIndex ¤t, const QModelIndex &previous); void setCurrentOnDelegateItem(const QModelIndex &index, bool isCurrent); void updateCurrentRowAndColumn(); void fetchMoreData(); void _q_componentFinalized(); void registerCallbackWhenBindingsAreEvaluated(); inline QString tableLayoutToString() const; void dumpTable() const; void setRequiredProperty(const char *property, const QVariant &value, int serializedModelIndex, QObject *object, bool init); void handleTap(const QQuickHandlerPoint &point); void setCurrentIndexFromTap(const QPointF &pos); void setCurrentIndex(const QPoint &cell); bool setCurrentIndexFromKeyEvent(QKeyEvent *e); bool canEdit(const QModelIndex tappedIndex, bool warn); bool editFromKeyEvent(QKeyEvent *e); // QQuickSelectable QQuickItem *selectionPointerHandlerTarget() const override; bool startSelection(const QPointF &pos, Qt::KeyboardModifiers modifiers) override; void setSelectionStartPos(const QPointF &pos) override; void setSelectionEndPos(const QPointF &pos) override; void clearSelection() override; void normalizeSelection() override; QRectF selectionRectangle() const override; QSizeF scrollTowardsPoint(const QPointF &pos, const QSizeF &step) override; void setCallback(std::function func) override; void cancelSelectionTracking(); QPoint clampedCellAtPos(const QPointF &pos) const; virtual void updateSelection(const QRect &oldSelection, const QRect &newSelection); QRect selection() const; // ---------------- // Section drag handler void initSectionDragHandler(Qt::Orientation orientation); void destroySectionDragHandler(); inline void setActivePointerHandler(QQuickTableViewPointerHandler *handler) { activePtrHandler = handler; } inline QQuickTableViewPointerHandler* activePointerHandler() const { return activePtrHandler; } // Row/Column reordering void moveSection(int source , int destination, Qt::Orientations orientation); void initializeIndexMapping(); void clearIndexMapping(); void clearSection(Qt::Orientations orientation); virtual int logicalRowIndex(const int visualIndex) const; virtual int logicalColumnIndex(const int visualIndex) const; virtual int visualRowIndex(const int logicalIndex) const; virtual int visualColumnIndex(const int logicalIndex) const; void setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay); }; class FxTableItem : public QQuickItemViewFxItem { public: FxTableItem(QQuickItem *item, QQuickTableView *table, bool own) : QQuickItemViewFxItem(item, own, QQuickTableViewPrivate::get(table)) { } qreal position() const override { return 0; } qreal endPosition() const override { return 0; } qreal size() const override { return 0; } qreal sectionSize() const override { return 0; } bool contains(qreal, qreal) const override { return false; } QPoint cell; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickTableViewPrivate::RebuildOptions) QT_END_NAMESPACE #endif