1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #ifndef QQUICKTABLEVIEW_P_P_H |
41 | #define QQUICKTABLEVIEW_P_P_H |
42 | |
43 | // |
44 | // W A R N I N G |
45 | // ------------- |
46 | // |
47 | // This file is not part of the Qt API. It exists purely as an |
48 | // implementation detail. This header file may change from version to |
49 | // version without notice, or even be removed. |
50 | // |
51 | // We mean it. |
52 | // |
53 | |
54 | #include "qquicktableview_p.h" |
55 | |
56 | #include <QtCore/qtimer.h> |
57 | #include <QtQmlModels/private/qqmltableinstancemodel_p.h> |
58 | #include <QtQml/private/qqmlincubator_p.h> |
59 | #include <QtQmlModels/private/qqmlchangeset_p.h> |
60 | #include <QtQml/qqmlinfo.h> |
61 | |
62 | #include <QtQuick/private/qquickflickable_p_p.h> |
63 | #include <QtQuick/private/qquickitemviewfxitem_p_p.h> |
64 | |
65 | QT_BEGIN_NAMESPACE |
66 | |
67 | Q_DECLARE_LOGGING_CATEGORY(lcTableViewDelegateLifecycle) |
68 | |
69 | static const qreal kDefaultRowHeight = 50; |
70 | static const qreal kDefaultColumnWidth = 50; |
71 | |
72 | class FxTableItem; |
73 | class QQuickTableSectionSizeProviderPrivate; |
74 | |
75 | class Q_QUICK_PRIVATE_EXPORT QQuickTableSectionSizeProvider : public QObject { |
76 | Q_OBJECT |
77 | |
78 | public: |
79 | QQuickTableSectionSizeProvider(QObject *parent=nullptr); |
80 | void setSize(int section, qreal size); |
81 | qreal size(int section); |
82 | bool resetSize(int section); |
83 | void resetAll(); |
84 | |
85 | Q_SIGNALS: |
86 | void sizeChanged(); |
87 | |
88 | private: |
89 | Q_DISABLE_COPY(QQuickTableSectionSizeProvider) |
90 | Q_DECLARE_PRIVATE(QQuickTableSectionSizeProvider) |
91 | }; |
92 | |
93 | class Q_QUICK_PRIVATE_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate |
94 | { |
95 | Q_DECLARE_PUBLIC(QQuickTableView) |
96 | |
97 | public: |
98 | class TableEdgeLoadRequest |
99 | { |
100 | // Whenever we need to load new rows or columns in the |
101 | // table, we fill out a TableEdgeLoadRequest. |
102 | // TableEdgeLoadRequest is just a struct that keeps track |
103 | // of which cells that needs to be loaded, and which cell |
104 | // the table is currently loading. The loading itself is |
105 | // done by QQuickTableView. |
106 | |
107 | public: |
108 | void begin(const QPoint &cell, const QPointF &pos, QQmlIncubator::IncubationMode incubationMode) |
109 | { |
110 | Q_ASSERT(!m_active); |
111 | m_active = true; |
112 | m_edge = Qt::Edge(0); |
113 | m_mode = incubationMode; |
114 | m_edgeIndex = cell.x(); |
115 | m_visibleCellsInEdge.clear(); |
116 | m_visibleCellsInEdge.append(t: cell.y()); |
117 | m_currentIndex = 0; |
118 | m_startPos = pos; |
119 | qCDebug(lcTableViewDelegateLifecycle()) << "begin top-left:" << toString(); |
120 | } |
121 | |
122 | void begin(Qt::Edge edgeToLoad, int edgeIndex, const QList<int> visibleCellsInEdge, QQmlIncubator::IncubationMode incubationMode) |
123 | { |
124 | Q_ASSERT(!m_active); |
125 | m_active = true; |
126 | m_edge = edgeToLoad; |
127 | m_edgeIndex = edgeIndex; |
128 | m_visibleCellsInEdge = visibleCellsInEdge; |
129 | m_mode = incubationMode; |
130 | m_currentIndex = 0; |
131 | qCDebug(lcTableViewDelegateLifecycle()) << "begin:" << toString(); |
132 | } |
133 | |
134 | inline void markAsDone() { m_active = false; } |
135 | inline bool isActive() { return m_active; } |
136 | |
137 | inline QPoint currentCell() { return cellAt(index: m_currentIndex); } |
138 | inline bool hasCurrentCell() { return m_currentIndex < m_visibleCellsInEdge.count(); } |
139 | inline void moveToNextCell() { ++m_currentIndex; } |
140 | |
141 | inline Qt::Edge edge() { return m_edge; } |
142 | inline int row() { return cellAt(index: 0).y(); } |
143 | inline int column() { return cellAt(index: 0).x(); } |
144 | inline QQmlIncubator::IncubationMode incubationMode() { return m_mode; } |
145 | |
146 | inline QPointF startPosition() { return m_startPos; } |
147 | |
148 | QString toString() |
149 | { |
150 | QString str; |
151 | QDebug dbg(&str); |
152 | dbg.nospace() << "TableSectionLoadRequest(" << "edge:" |
153 | << m_edge << ", edgeIndex:" << m_edgeIndex << ", incubation:" ; |
154 | |
155 | switch (m_mode) { |
156 | case QQmlIncubator::Asynchronous: |
157 | dbg << "Asynchronous" ; |
158 | break; |
159 | case QQmlIncubator::AsynchronousIfNested: |
160 | dbg << "AsynchronousIfNested" ; |
161 | break; |
162 | case QQmlIncubator::Synchronous: |
163 | dbg << "Synchronous" ; |
164 | break; |
165 | } |
166 | |
167 | return str; |
168 | } |
169 | |
170 | private: |
171 | Qt::Edge m_edge = Qt::Edge(0); |
172 | QList<int> m_visibleCellsInEdge; |
173 | int m_edgeIndex = 0; |
174 | int m_currentIndex = 0; |
175 | bool m_active = false; |
176 | QQmlIncubator::IncubationMode m_mode = QQmlIncubator::AsynchronousIfNested; |
177 | QPointF m_startPos; |
178 | |
179 | inline QPoint cellAt(int index) { |
180 | return !m_edge || (m_edge & (Qt::LeftEdge | Qt::RightEdge)) |
181 | ? QPoint(m_edgeIndex, m_visibleCellsInEdge[index]) |
182 | : QPoint(m_visibleCellsInEdge[index], m_edgeIndex); |
183 | } |
184 | }; |
185 | |
186 | class EdgeRange { |
187 | public: |
188 | EdgeRange(); |
189 | bool containsIndex(Qt::Edge edge, int index); |
190 | |
191 | int startIndex; |
192 | int endIndex; |
193 | qreal size; |
194 | }; |
195 | |
196 | enum class RebuildState { |
197 | Begin = 0, |
198 | LoadInitalTable, |
199 | VerifyTable, |
200 | LayoutTable, |
201 | LoadAndUnloadAfterLayout, |
202 | PreloadColumns, |
203 | PreloadRows, |
204 | MovePreloadedItemsToPool, |
205 | Done |
206 | }; |
207 | |
208 | enum class RebuildOption { |
209 | None = 0, |
210 | LayoutOnly = 0x1, |
211 | ViewportOnly = 0x2, |
212 | CalculateNewTopLeftRow = 0x4, |
213 | CalculateNewTopLeftColumn = 0x8, |
214 | CalculateNewContentWidth = 0x10, |
215 | CalculateNewContentHeight = 0x20, |
216 | All = 0x40, |
217 | }; |
218 | Q_DECLARE_FLAGS(RebuildOptions, RebuildOption) |
219 | |
220 | public: |
221 | QQuickTableViewPrivate(); |
222 | ~QQuickTableViewPrivate() override; |
223 | |
224 | static inline QQuickTableViewPrivate *get(QQuickTableView *q) { return q->d_func(); } |
225 | |
226 | void updatePolish() override; |
227 | void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override; |
228 | |
229 | public: |
230 | QHash<int, FxTableItem *> loadedItems; |
231 | |
232 | // model, tableModel and modelVariant all point to the same model. modelVariant |
233 | // is the model assigned by the user. And tableModel is the wrapper model we create |
234 | // around it. But if the model is an instance model directly, we cannot wrap it, so |
235 | // we need a pointer for that case as well. |
236 | QQmlInstanceModel* model = nullptr; |
237 | QPointer<QQmlTableInstanceModel> tableModel = nullptr; |
238 | QVariant modelVariant; |
239 | |
240 | // When the applications assignes a new model or delegate to the view, we keep them |
241 | // around until we're ready to take them into use (syncWithPendingChanges). |
242 | QVariant assignedModel = QVariant(int(0)); |
243 | QQmlComponent *assignedDelegate = nullptr; |
244 | |
245 | // loadedRows/Columns describes the rows and columns that are currently loaded (from top left |
246 | // row/column to bottom right row/column). loadedTableOuterRect describes the actual |
247 | // pixels that all the loaded delegate items cover, and is matched agains the viewport to determine when |
248 | // we need to fill up with more rows/columns. loadedTableInnerRect describes the pixels |
249 | // that the loaded table covers if you remove one row/column on each side of the table, and |
250 | // is used to determine rows/columns that are no longer visible and can be unloaded. |
251 | QMap<int, int> loadedColumns; |
252 | QMap<int, int> loadedRows; |
253 | QRectF loadedTableOuterRect; |
254 | QRectF loadedTableInnerRect; |
255 | |
256 | QPointF origin = QPointF(0, 0); |
257 | QSizeF endExtent = QSizeF(0, 0); |
258 | |
259 | QRectF viewportRect = QRectF(0, 0, -1, -1); |
260 | |
261 | QSize tableSize; |
262 | |
263 | RebuildState rebuildState = RebuildState::Done; |
264 | RebuildOptions rebuildOptions = RebuildOption::All; |
265 | RebuildOptions scheduledRebuildOptions = RebuildOption::All; |
266 | |
267 | TableEdgeLoadRequest loadRequest; |
268 | |
269 | QSizeF cellSpacing = QSizeF(0, 0); |
270 | |
271 | QQmlTableInstanceModel::ReusableFlag reusableFlag = QQmlTableInstanceModel::Reusable; |
272 | |
273 | bool blockItemCreatedCallback = false; |
274 | bool layoutWarningIssued = false; |
275 | bool polishing = false; |
276 | bool syncVertically = false; |
277 | bool syncHorizontally = false; |
278 | bool inSetLocalViewportPos = false; |
279 | bool inSyncViewportPosRecursive = false; |
280 | bool inUpdateContentSize = false; |
281 | |
282 | // isTransposed is currently only used by HeaderView. |
283 | // Consider making it public. |
284 | bool isTransposed = false; |
285 | |
286 | QJSValue rowHeightProvider; |
287 | QJSValue columnWidthProvider; |
288 | QQuickTableSectionSizeProvider rowHeights; |
289 | QQuickTableSectionSizeProvider columnWidths; |
290 | |
291 | EdgeRange cachedNextVisibleEdgeIndex[4]; |
292 | EdgeRange cachedColumnWidth; |
293 | EdgeRange cachedRowHeight; |
294 | |
295 | // TableView uses contentWidth/height to report the size of the table (this |
296 | // will e.g make scrollbars written for Flickable work out of the box). This |
297 | // value is continuously calculated, and will change/improve as more columns |
298 | // are loaded into view. At the same time, we want to open up for the |
299 | // possibility that the application can set the content width explicitly, in |
300 | // case it knows what the exact width should be from the start. We therefore |
301 | // override the contentWidth/height properties from QQuickFlickable, to be able |
302 | // to implement this combined behavior. This also lets us lazy build the table |
303 | // if the application needs to know the content size early on. |
304 | QQmlNullableValue<qreal> explicitContentWidth; |
305 | QQmlNullableValue<qreal> explicitContentHeight; |
306 | |
307 | QSizeF averageEdgeSize; |
308 | |
309 | QPointer<QQuickTableView> assignedSyncView; |
310 | QPointer<QQuickTableView> syncView; |
311 | QList<QPointer<QQuickTableView> > syncChildren; |
312 | Qt::Orientations assignedSyncDirection = Qt::Horizontal | Qt::Vertical; |
313 | |
314 | const static QPoint kLeft; |
315 | const static QPoint kRight; |
316 | const static QPoint kUp; |
317 | const static QPoint kDown; |
318 | |
319 | #ifdef QT_DEBUG |
320 | QString forcedIncubationMode = qEnvironmentVariable(varName: "QT_TABLEVIEW_INCUBATION_MODE" ); |
321 | #endif |
322 | |
323 | public: |
324 | QQuickTableViewAttached *getAttachedObject(const QObject *object) const; |
325 | |
326 | int modelIndexAtCell(const QPoint &cell) const; |
327 | QPoint cellAtModelIndex(int modelIndex) const; |
328 | |
329 | qreal sizeHintForColumn(int column); |
330 | qreal sizeHintForRow(int row); |
331 | QSize calculateTableSize(); |
332 | void updateTableSize(); |
333 | |
334 | inline bool isColumnHidden(int column); |
335 | inline bool isRowHidden(int row); |
336 | |
337 | qreal getColumnLayoutWidth(int column); |
338 | qreal getRowLayoutHeight(int row); |
339 | qreal getColumnWidth(int column); |
340 | qreal getRowHeight(int row); |
341 | |
342 | inline int topRow() const { return loadedRows.firstKey(); } |
343 | inline int bottomRow() const { return loadedRows.lastKey(); } |
344 | inline int leftColumn() const { return loadedColumns.firstKey(); } |
345 | inline int rightColumn() const { return loadedColumns.lastKey(); } |
346 | |
347 | QQuickTableView *rootSyncView() const; |
348 | |
349 | bool updateTableRecursive(); |
350 | bool updateTable(); |
351 | void relayoutTableItems(); |
352 | |
353 | void layoutVerticalEdge(Qt::Edge tableEdge); |
354 | void layoutHorizontalEdge(Qt::Edge tableEdge); |
355 | void layoutTopLeftItem(); |
356 | void layoutTableEdgeFromLoadRequest(); |
357 | |
358 | void updateContentWidth(); |
359 | void updateContentHeight(); |
360 | void updateAverageColumnWidth(); |
361 | void updateAverageRowHeight(); |
362 | RebuildOptions checkForVisibilityChanges(); |
363 | void forceLayout(); |
364 | |
365 | void updateExtents(); |
366 | void syncLoadedTableRectFromLoadedTable(); |
367 | void syncLoadedTableFromLoadRequest(); |
368 | |
369 | int nextVisibleEdgeIndex(Qt::Edge edge, int startIndex); |
370 | int nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge); |
371 | bool allColumnsLoaded(); |
372 | bool allRowsLoaded(); |
373 | inline int edgeToArrayIndex(Qt::Edge edge); |
374 | void clearEdgeSizeCache(); |
375 | |
376 | bool canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const; |
377 | bool canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const; |
378 | Qt::Edge nextEdgeToLoad(const QRectF rect); |
379 | Qt::Edge nextEdgeToUnload(const QRectF rect); |
380 | |
381 | qreal cellWidth(const QPoint &cell); |
382 | qreal cellHeight(const QPoint &cell); |
383 | |
384 | FxTableItem *loadedTableItem(const QPoint &cell) const; |
385 | FxTableItem *createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode); |
386 | FxTableItem *loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode); |
387 | |
388 | void releaseItem(FxTableItem *fxTableItem, QQmlTableInstanceModel::ReusableFlag reusableFlag); |
389 | void releaseLoadedItems(QQmlTableInstanceModel::ReusableFlag reusableFlag); |
390 | |
391 | void unloadItem(const QPoint &cell); |
392 | void loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMode incubationMode); |
393 | void unloadEdge(Qt::Edge edge); |
394 | void loadAndUnloadVisibleEdges(); |
395 | void drainReusePoolAfterLoadRequest(); |
396 | void processLoadRequest(); |
397 | |
398 | void processRebuildTable(); |
399 | bool moveToNextRebuildState(); |
400 | void calculateTopLeft(QPoint &topLeft, QPointF &topLeftPos); |
401 | void beginRebuildTable(); |
402 | void layoutAfterLoadingInitialTable(); |
403 | |
404 | void scheduleRebuildTable(QQuickTableViewPrivate::RebuildOptions options); |
405 | |
406 | int resolveImportVersion(); |
407 | void createWrapperModel(); |
408 | |
409 | void initItemCallback(int modelIndex, QObject *item); |
410 | void itemCreatedCallback(int modelIndex, QObject *object); |
411 | void itemPooledCallback(int modelIndex, QObject *object); |
412 | void itemReusedCallback(int modelIndex, QObject *object); |
413 | void modelUpdated(const QQmlChangeSet &changeSet, bool reset); |
414 | |
415 | virtual void syncWithPendingChanges(); |
416 | virtual void syncDelegate(); |
417 | virtual QVariant modelImpl() const; |
418 | virtual void setModelImpl(const QVariant &newModel); |
419 | virtual void syncModel(); |
420 | inline void syncRebuildOptions(); |
421 | virtual void syncSyncView(); |
422 | |
423 | void connectToModel(); |
424 | void disconnectFromModel(); |
425 | |
426 | void rowsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); |
427 | void columnsMovedCallback(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column); |
428 | void rowsInsertedCallback(const QModelIndex &parent, int begin, int end); |
429 | void rowsRemovedCallback(const QModelIndex &parent, int begin, int end); |
430 | void columnsInsertedCallback(const QModelIndex &parent, int begin, int end); |
431 | void columnsRemovedCallback(const QModelIndex &parent, int begin, int end); |
432 | void layoutChangedCallback(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint); |
433 | void modelResetCallback(); |
434 | |
435 | void scheduleRebuildIfFastFlick(); |
436 | void setLocalViewportX(qreal contentX); |
437 | void setLocalViewportY(qreal contentY); |
438 | void syncViewportRect(); |
439 | void syncViewportPosRecursive(); |
440 | |
441 | void fetchMoreData(); |
442 | |
443 | void _q_componentFinalized(); |
444 | void registerCallbackWhenBindingsAreEvaluated(); |
445 | |
446 | inline QString tableLayoutToString() const; |
447 | void dumpTable() const; |
448 | }; |
449 | |
450 | class FxTableItem : public QQuickItemViewFxItem |
451 | { |
452 | public: |
453 | FxTableItem(QQuickItem *item, QQuickTableView *table, bool own) |
454 | : QQuickItemViewFxItem(item, own, QQuickTableViewPrivate::get(q: table)) |
455 | { |
456 | } |
457 | |
458 | qreal position() const override { return 0; } |
459 | qreal endPosition() const override { return 0; } |
460 | qreal size() const override { return 0; } |
461 | qreal sectionSize() const override { return 0; } |
462 | bool contains(qreal, qreal) const override { return false; } |
463 | |
464 | QPoint cell; |
465 | }; |
466 | |
467 | Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickTableViewPrivate::RebuildOptions) |
468 | |
469 | QT_END_NAMESPACE |
470 | |
471 | #endif |
472 | |