Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquicktableview.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtCore/qdir.h>
9#include <QtCore/qmimedata.h>
10#include <QtCore/qtimer.h>
11#include <QtQml/private/qqmlincubator_p.h>
12#include <QtQml/qqmlinfo.h>
13#include <QtQmlModels/private/qqmlchangeset_p.h>
14#include <QtQmlModels/private/qqmldelegatecomponent_p.h>
15#include <QtQmlModels/private/qqmldelegatemodel_p.h>
16#include <QtQmlModels/private/qqmldelegatemodel_p_p.h>
17#include <QtQuick/qquickitemgrabresult.h>
18
19#include <QtQuick/private/qquickflickable_p_p.h>
20#include <QtQuick/private/qquickitemviewfxitem_p_p.h>
21#include <QtQuick/private/qquicktaphandler_p.h>
22
23#include <QtCore/qtyperevision.h>
24
25/*!
26 \qmltype TableView
27 \inqmlmodule QtQuick
28 \since 5.12
29 \ingroup qtquick-views
30 \inherits Flickable
31 \brief Provides a table view of items to display data from a model.
32
33 A TableView has a \l model that defines the data to be displayed, and a
34 \l delegate that defines how the data should be displayed.
35
36 TableView inherits \l Flickable. This means that while the model can have
37 any number of rows and columns, only a subsection of the table is usually
38 visible inside the viewport. As soon as you flick, new rows and columns
39 enter the viewport, while old ones exit and are removed from the viewport.
40 The rows and columns that move out are reused for building the rows and columns
41 that move into the viewport. As such, the TableView support models of any
42 size without affecting performance.
43
44 A TableView displays data from models created from built-in QML types
45 such as ListModel and XmlListModel, which populates the first column only
46 in a TableView. To create models with multiple columns, either use
47 \l TableModel or a C++ model that inherits QAbstractItemModel.
48
49 A TableView does not include headers by default. You can add headers
50 using the \l HorizontalHeaderView and \l VerticalHeaderView from
51 Qt Quick Controls.
52
53 \note TableView will only \l {isRowLoaded()}{load} as many delegate items as
54 needed to fill up the view. There is no guarantee that items outside the view
55 will be loaded, although TableView will sometimes pre-load items for
56 optimization reasons. Hence, a TableView with zero width or height might not
57 load any delegate items at all.
58
59 \section1 Example Usage
60
61 \section2 C++ Models
62
63 The following example shows how to create a model from C++ with multiple
64 columns:
65
66 \snippet qml/tableview/cpp-tablemodel.h 0
67
68 And then the \l TableViewDelegate automatically uses the model to set/get data
69 to/from the model. The \l TableViewDelegate uses the \l {Qt::ItemDataRole}{Qt::DisplayRole}
70 for display text and \l {Qt::ItemDataRole}{Qt::EditRole} for editing data in the model.
71
72 The following snippet shows how to use the model from QML in a custom delegate:
73
74 \snippet qml/tableview/cpp-tablemodel.qml 0
75
76 \section2 QML Models
77
78 For prototyping and displaying very simple data (from a web API, for
79 example), \l TableModel can be used:
80
81 \snippet qml/tableview/qml-tablemodel.qml 0
82
83 As the \l TableViewDelegate uses the \l {Qt::ItemDataRole}{Qt::EditRole} to set
84 the data, it's necessary to specify the edit role in the \l TableModelColumn when
85 the delegate is \l TableViewDelegate:
86
87 \code
88 model: TableModel {
89 TableModelColumn { display: "name", edit: "name" }
90 TableModelColumn { display: "color", edit: "color" }
91
92 rows: [
93 {
94 "name": "cat",
95 "color": "black"
96 },
97 {
98 "name": "dog",
99 "color": "brown"
100 },
101 {
102 "name": "bird",
103 "color": "white"
104 }
105 ]
106 }
107 \endcode
108
109 \section1 Reusing items
110
111 TableView recycles delegate items by default, instead of instantiating from
112 the \l delegate whenever new rows and columns are flicked into view. This
113 approach gives a huge performance boost, depending on the complexity of the
114 delegate.
115
116 When an item is flicked out, it moves to the \e{reuse pool}, which is an
117 internal cache of unused items. When this happens, the \l TableView::pooled
118 signal is emitted to inform the item about it. Likewise, when the item is
119 moved back from the pool, the \l TableView::reused signal is emitted.
120
121 Any item properties that come from the model are updated when the
122 item is reused. This includes \c index, \c row, and \c column, but also
123 any model roles.
124
125 \note Avoid storing any state inside a delegate. If you do, reset it
126 manually on receiving the \l TableView::reused signal.
127
128 If an item has timers or animations, consider pausing them on receiving
129 the \l TableView::pooled signal. That way you avoid using the CPU resources
130 for items that are not visible. Likewise, if an item has resources that
131 cannot be reused, they could be freed up.
132
133 If you don't want to reuse items or if the \l delegate cannot support it,
134 you can set the \l reuseItems property to \c false.
135
136 \note While an item is in the pool, it might still be alive and respond
137 to connected signals and bindings.
138
139 The following example shows a delegate that animates a spinning rectangle. When
140 it is pooled, the animation is temporarily paused:
141
142 \snippet qml/tableview/reusabledelegate.qml 0
143
144 \section1 Row heights and column widths
145
146 When a new column is flicked into view, TableView will determine its width
147 by calling the \l columnWidthProvider. If set, this function will alone decide
148 the width of the column. Otherwise, it will check if an explicit width has
149 been set with \l setColumnWidth(). If not, \l implicitColumnWidth() will be used.
150 The implicit width of a column is the same as the largest
151 \l {Item::implicitWidth}{implicit width} found among the currently loaded
152 delegate items in that column. Trying to set an explicit \c width directly on
153 a delegate has no effect, and will be ignored and overwritten. The same logic also
154 applies to row heights.
155
156 An implementation of a columnWidthProvider that is equivalent to the default
157 logic would be:
158
159 \code
160 columnWidthProvider: function(column) {
161 let w = explicitColumnWidth(column)
162 if (w >= 0)
163 return w;
164 return implicitColumnWidth(column)
165 }
166 \endcode
167
168 Once the column width is resolved, all other items in the same column are resized
169 to this width, including any items that are flicked into the view at a later point.
170
171 \note The resolved width of a column is discarded when the whole column is flicked out
172 of the view, and is recalculated again if it's flicked back in. This means that if the
173 width depends on the \l implicitColumnWidth(), the calculation can be different each time,
174 depending on which row you're at when the column enters (since \l implicitColumnWidth()
175 only considers the delegate items that are currently \l {isColumnLoaded()}{loaded}).
176 To avoid this, you should use a \l columnWidthProvider, or ensure that all the delegate
177 items in the same column have the same \c implicitWidth.
178
179 If you change the values that a \l rowHeightProvider or a
180 \l columnWidthProvider return for rows and columns inside the viewport, you
181 must call \l forceLayout. This informs TableView that it needs to use the
182 provider functions again to recalculate and update the layout.
183
184 Since Qt 5.13, if you want to hide a specific column, you can return \c 0
185 from the \l columnWidthProvider for that column. Likewise, you can return 0
186 from the \l rowHeightProvider to hide a row. If you return a negative
187 number or \c undefined, TableView will fall back to calculate the size based
188 on the delegate items.
189
190 \note The size of a row or column should be a whole number to avoid
191 sub-pixel alignment of items.
192
193 The following example shows how to set a simple \c columnWidthProvider
194 together with a timer that modifies the values the function returns. When
195 the array is modified, \l forceLayout is called to let the changes
196 take effect:
197
198 \snippet qml/tableview/tableviewwithprovider.qml 0
199
200 \section1 Editing cells
201
202 You can let the user edit table cells by providing an edit delegate. The
203 edit delegate will be instantiated according to the \l editTriggers, which
204 by default is when the user double taps on a cell, or presses e.g
205 \l Qt::Key_Enter or \l Qt::Key_Return. The edit delegate is set using
206 \l {TableView::editDelegate}, which is an attached property that you set
207 on the \l delegate. The following snippet shows how to do that:
208
209 \snippet qml/tableview/editdelegate.qml 0
210
211 If the user presses Qt::Key_Enter or Qt::Key_Return while the edit delegate
212 is active, TableView will emit the \l TableView::commit signal to the edit
213 delegate, so that it can write back the changed data to the model.
214
215 \note In order for a cell to be editable, the model needs to override
216 \l QAbstractItemModel::flags(), and return \c Qt::ItemIsEditable.
217 This flag is not enabled in QAbstractItemModel by default.
218 The override could for example look like this:
219
220 \code
221 Qt::ItemFlags QAbstractItemModelSubClass::flags(const QModelIndex &index) const override
222 {
223 Q_UNUSED(index)
224 return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
225 }
226 \endcode
227
228 If the \l {TableView::delegate}{TableView delegate} has a property
229 \c {required property bool editing} defined, it will be set to \c true
230 for the delegate being edited. See the documentation for
231 \l editDelegate for an example on how to use it.
232
233 \sa TableView::editDelegate, TableView::commit, editTriggers, edit(), closeEditor()
234
235 \section1 Overlays and underlays
236
237 All new items that are instantiated from the delegate are parented to the
238 \l{Flickable::}{contentItem} with the \c z value, \c 1. You can add your
239 own items inside the Tableview, as child items of the Flickable. By
240 controlling their \c z value, you can make them be on top of or
241 underneath the table items.
242
243 Here is an example that shows how to add some text on top of the table, that
244 moves together with the table as you flick:
245
246 \snippet qml/tableview/tableviewwithheader.qml 0
247
248 Here is another example that shows how to create an overlay item that
249 stays on top of a particular cell. This requires a bit more code, since
250 the location of a cell will \l {layoutChanged}{change} if the user, for
251 example, is resizing a column in front of it.
252
253 \snippet qml/tableview/overlay.qml 0
254
255 You could also parent the overlay directly to the cell instead of the
256 \l {Flickable::}{contentItem}. But doing so will be fragile since the cell is unloaded
257 or reused whenever it's flicked out of the viewport.
258
259 \sa layoutChanged()
260
261 \section1 Selecting items
262
263 You can add selection support to TableView by assigning an \l ItemSelectionModel to
264 the \l selectionModel property. It will then use this model to control which
265 delegate items should be shown as selected, and which item should be shown as
266 current. You can set \l selectionBehavior to control if the user should
267 be allowed to select individual cells, rows, or columns.
268
269 To find out whether a delegate is selected or current, declare the
270 following properties (unless the delegate is a \l TableViewDelegate,
271 in which case the properties have are already been added):
272
273 \code
274 delegate: Item {
275 required property bool selected
276 required property bool current
277 // ...
278 }
279 \endcode
280
281 \note the \c selected and \c current properties must be defined as \c required.
282 This will inform TableView that it should take responsibility for updating their
283 values. If not, they will simply be ignored. See also \l {Required Properties}.
284
285 The following snippet shows how an application can render the delegate differently
286 depending on the \c selected property:
287
288 \snippet qml/tableview/selectionmodel.qml 0
289
290 The \l currentRow and \l currentColumn properties can also be useful if you need
291 to render a delegate differently depending on if it lies on the same row or column
292 as the current item.
293
294 \note \l{Qt Quick Controls} offers a SelectionRectangle that can be used
295 to let the user select cells.
296
297 \note By default, a cell will become
298 \l {ItemSelectionModel::currentIndex}{current}, and any selections will
299 be removed, when the user taps on it. If such default tap behavior is not wanted
300 (e.g if you use custom pointer handlers inside your delegate), you can set
301 \l pointerNavigationEnabled to \c false.
302
303 \section1 Keyboard navigation
304
305 In order to support keyboard navigation, you need to assign an \l ItemSelectionModel
306 to the \l selectionModel property. TableView will then use this model to manipulate
307 the model's \l {ItemSelectionModel::currentIndex}{currentIndex}.
308
309 It's the responsibility of the delegate to render itself as
310 \l {ItemSelectionModel::currentIndex}{current}. You can do this by adding a
311 property \c {required property bool current} to it, and let the appearance
312 depend on its state. The \c current property's value is set by the TableView.
313 You can also disable keyboard navigation fully (in case you want to implement your
314 own key handlers) by setting \l keyNavigationEnabled to \c false.
315
316 \note By default, the \l TableViewDelegate renders the current and selected cells,
317 so there is no need to add these properties.
318
319 The following example demonstrates how you can use keyboard navigation together
320 with \c current and \c selected properties in a custom delegate:
321
322 \snippet qml/tableview/keyboard-navigation.qml 0
323
324 \section1 Copy and paste
325
326 Implementing copy and paste operations for a TableView usually also includes using
327 a QUndoStack (or some other undo/redo framework). The QUndoStack can be used to
328 store the different operations done on the model, like adding or removing rows, or
329 pasting data from the clipboard, with a way to undo it again later. However, an
330 accompanying QUndoStack that describes the possible operations, and how to undo them,
331 should be designed according to the needs of the model and the application.
332 As such, TableView doesn't offer a built-in API for handling copy and paste.
333
334 The following snippet can be used as a reference for how to add copy and paste support
335 to your model and TableView. It uses the existing mime data API in QAbstractItemModel,
336 together with QClipboard. The snippet will work as it is, but can also be extended to
337 use a QUndoStack.
338
339 \code
340 // Inside your C++ QAbstractTableModel subclass:
341
342 Q_INVOKABLE void copyToClipboard(const QModelIndexList &indexes) const
343 {
344 QGuiApplication::clipboard()->setMimeData(mimeData(indexes));
345 }
346
347 Q_INVOKABLE bool pasteFromClipboard(const QModelIndex &targetIndex)
348 {
349 const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
350 // Consider using a QUndoCommand for the following call. It should store
351 // the (mime) data for the model items that are about to be overwritten, so
352 // that a later call to undo can revert it.
353 return dropMimeData(mimeData, Qt::CopyAction, -1, -1, targetIndex);
354 }
355 \endcode
356
357 The two functions can, for example, be used from QML like this:
358
359 \code
360 TableView {
361 id: tableView
362 model: tableModel
363 selectionModel: ItemSelectionModel {}
364
365 Shortcut {
366 sequence: StandardKey.Copy
367 onActivated: {
368 let indexes = tableView.selectionModel.selectedIndexes
369 tableView.model.copyToClipboard(indexes)
370 }
371 }
372
373 Shortcut {
374 sequence: StandardKey.Paste
375 onActivated: {
376 let targetIndex = tableView.selectionModel.currentIndex
377 tableView.model.pasteFromClipboard(targetIndex)
378 }
379 }
380 }
381 \endcode
382
383 \sa QAbstractItemModel::mimeData(), QAbstractItemModel::dropMimeData(), QUndoStack, QUndoCommand, QClipboard
384*/
385
386/*!
387 \qmlproperty int QtQuick::TableView::rows
388 \readonly
389
390 This property holds the number of rows in the table.
391
392 \note \a rows is usually equal to the number of rows in the model, but can
393 temporarily differ until all pending model changes have been processed.
394
395 This property is read only.
396*/
397
398/*!
399 \qmlproperty int QtQuick::TableView::columns
400 \readonly
401
402 This property holds the number of columns in the table.
403
404 \note \a columns is usually equal to the number of columns in the model, but
405 can temporarily differ until all pending model changes have been processed.
406
407 If the model is a list, columns will be \c 1.
408
409 This property is read only.
410*/
411
412/*!
413 \qmlproperty real QtQuick::TableView::rowSpacing
414
415 This property holds the spacing between the rows.
416
417 The default value is \c 0.
418*/
419
420/*!
421 \qmlproperty real QtQuick::TableView::columnSpacing
422
423 This property holds the spacing between the columns.
424
425 The default value is \c 0.
426*/
427
428/*!
429 \qmlproperty var QtQuick::TableView::rowHeightProvider
430
431 This property can hold a function that returns the row height for each row
432 in the model. It is called whenever TableView needs to know the height of
433 a specific row. The function takes one argument, \c row, for which the
434 TableView needs to know the height.
435
436 Since Qt 5.13, if you want to hide a specific row, you can return \c 0
437 height for that row. If you return a negative number, TableView calculates
438 the height based on the delegate items.
439
440 \note The rowHeightProvider will usually be called two times when
441 a row is about to load (or when doing layout). First, to know if
442 the row is visible and should be loaded. And second, to determine
443 the height of the row after all items have been loaded.
444 If you need to calculate the row height based on the size of the delegate
445 items, you need to wait for the second call, when all the items have been loaded.
446 You can check for this by calling \l {isRowLoaded()}{isRowLoaded(row)},
447 and simply return -1 if that is not yet the case.
448
449 \sa columnWidthProvider, isRowLoaded(), {Row heights and column widths}
450*/
451
452/*!
453 \qmlproperty var QtQuick::TableView::columnWidthProvider
454
455 This property can hold a function that returns the column width for each
456 column in the model. It is called whenever TableView needs to know the
457 width of a specific column. The function takes one argument, \c column,
458 for which the TableView needs to know the width.
459
460 Since Qt 5.13, if you want to hide a specific column, you can return \c 0
461 width for that column. If you return a negative number or \c undefined,
462 TableView calculates the width based on the delegate items.
463
464 \note The columnWidthProvider will usually be called two times when
465 a column is about to load (or when doing layout). First, to know if
466 the column is visible and should be loaded. And second, to determine
467 the width of the column after all items have been loaded.
468 If you need to calculate the column width based on the size of the delegate
469 items, you need to wait for the second call, when all the items have been loaded.
470 You can check for this by calling \l {isColumnLoaded}{isColumnLoaded(column)},
471 and simply return -1 if that is not yet the case.
472
473 \sa rowHeightProvider, isColumnLoaded(), {Row heights and column widths}
474*/
475
476/*!
477 \qmlproperty model QtQuick::TableView::model
478 This property holds the model that provides data for the table.
479
480 The model provides the set of data that is used to create the items
481 in the view. Models can be created directly in QML using \l TableModel,
482 \l ListModel, \l ObjectModel, or provided by a custom
483 C++ model class. The C++ model must be a subclass of \l QAbstractItemModel
484 or a simple list.
485
486 \sa {qml-data-models}{Data Models}
487*/
488
489/*!
490 \qmlproperty Component QtQuick::TableView::delegate
491
492 The delegate provides a template defining each cell item instantiated by the view.
493 It can be any custom component, but it's recommended to use \l {TableViewDelegate},
494 as it styled according to the application style, and offers out-of-the-box functionality.
495
496 To use \l TableViewDelegate, simply set it as the delegate:
497 \code
498 delegate: TableViewDelegate { }
499 \endcode
500
501 The model index is exposed as an accessible \c index property. The same
502 applies to \c row and \c column. Properties of the model are also available
503 depending upon the type of \l {qml-data-models}{Data Model}.
504
505 A delegate should specify its size using \l{Item::}{implicitWidth} and
506 \l {Item::}{implicitHeight}. The TableView lays out the items based on that
507 information. Explicit width or height settings are ignored and overwritten.
508
509 Inside the delegate, you can optionally add one or more of the following
510 properties (unless the delegate is a \l TableViewDelegate, in which case
511 the properties have already been added). TableView modifies the values
512 of these properties to inform the delegate which state it's in. This can be
513 used by the delegate to render itself differently according on its own state.
514
515 \list
516 \li required property bool current - \c true if the delegate is \l {Keyboard navigation}{current.}
517 \li required property bool selected - \c true if the delegate is \l {Selecting items}{selected.}
518 \li required property bool editing - \c true if the delegate is being \l {Editing cells}{edited.}
519 \li required property bool containsDrag - \c true if a column or row is currently being dragged
520 over this delegate. This property is only supported for HorizontalHeaderView and
521 VerticalHeaderView. (since Qt 6.8)
522 \endlist
523
524 The following example shows how to use these properties in a custom delegate:
525 \code
526 delegate: Rectangle {
527 required property bool current
528 required property bool selected
529 border.width: current ? 1 : 0
530 color: selected ? palette.highlight : palette.base
531 }
532 \endcode
533
534 \note Delegates are instantiated as needed and may be destroyed at any time.
535 They are also reused if the \l reuseItems property is set to \c true. You
536 should therefore avoid storing state information in the delegates.
537
538 \sa {Row heights and column widths}, {Reusing items}, {Required Properties},
539 {TableViewDelegate}, {Customizing TableViewDelegate}
540*/
541
542/*!
543 \qmlproperty bool QtQuick::TableView::reuseItems
544
545 This property holds whether or not items instantiated from the \l delegate
546 should be reused. If set to \c false, any currently pooled items
547 are destroyed.
548
549 \sa {Reusing items}, TableView::pooled, TableView::reused
550*/
551
552/*!
553 \qmlproperty real QtQuick::TableView::contentWidth
554
555 This property holds the table width required to accommodate the number of
556 columns in the model. This is usually not the same as the \c width of the
557 \l view, which means that the table's width could be larger or smaller than
558 the viewport width. As a TableView cannot always know the exact width of
559 the table without loading all columns in the model, the \c contentWidth is
560 usually an estimate based on the initially loaded table.
561
562 If you know what the width of the table will be, assign a value to
563 \c contentWidth, to avoid unnecessary calculations and updates to the
564 TableView.
565
566 \sa contentHeight, columnWidthProvider
567*/
568
569/*!
570 \qmlproperty real QtQuick::TableView::contentHeight
571
572 This property holds the table height required to accommodate the number of
573 rows in the data model. This is usually not the same as the \c height of the
574 \c view, which means that the table's height could be larger or smaller than the
575 viewport height. As a TableView cannot always know the exact height of the
576 table without loading all rows in the model, the \c contentHeight is
577 usually an estimate based on the initially loaded table.
578
579 If you know what the height of the table will be, assign a
580 value to \c contentHeight, to avoid unnecessary calculations and updates to
581 the TableView.
582
583 \sa contentWidth, rowHeightProvider
584*/
585
586/*!
587 \qmlmethod void QtQuick::TableView::forceLayout()
588
589 Responding to changes in the model are batched so that they are handled
590 only once per frame. This means the TableView delays showing any changes
591 while a script is being run. The same is also true when changing
592 properties, such as \l rowSpacing or \l{Item::anchors.leftMargin}{leftMargin}.
593
594 This method forces the TableView to immediately update the layout so
595 that any recent changes take effect.
596
597 Calling this function re-evaluates the size and position of each visible
598 row and column. This is needed if the functions assigned to
599 \l rowHeightProvider or \l columnWidthProvider return different values than
600 what is already assigned.
601*/
602
603/*!
604 \qmlproperty bool QtQuick::TableView::alternatingRows
605
606 This property controls whether the background color of the rows should alternate.
607 The default value is style dependent.
608
609 \note This property is only a hint, and might therefore not be
610 respected by custom delegates. It's recommended that a delegate alternates
611 between \c palette.base and \c palette.alternateBase when this hint is
612 \c true, so that the colors can be set from outside of the delegate.
613 For example:
614
615 \code
616 background: Rectangle {
617 color: control.row === control.tableView.currentRow
618 ? control.palette.highlight
619 : (control.tableView.alternatingRows && control.row % 2 !== 0
620 ? control.palette.alternateBase
621 : control.palette.base)
622 }
623 \endcode
624*/
625
626/*!
627 \qmlproperty int QtQuick::TableView::leftColumn
628
629 This property holds the leftmost column that is currently visible inside the view.
630
631 \sa rightColumn, topRow, bottomRow
632*/
633
634/*!
635 \qmlproperty int QtQuick::TableView::rightColumn
636
637 This property holds the rightmost column that is currently visible inside the view.
638
639 \sa leftColumn, topRow, bottomRow
640*/
641
642/*!
643 \qmlproperty int QtQuick::TableView::topRow
644
645 This property holds the topmost row that is currently visible inside the view.
646
647 \sa leftColumn, rightColumn, bottomRow
648*/
649
650/*!
651 \qmlproperty int QtQuick::TableView::bottomRow
652
653 This property holds the bottom-most row that is currently visible inside the view.
654
655 \sa leftColumn, rightColumn, topRow
656*/
657
658/*!
659 \qmlproperty int QtQuick::TableView::currentColumn
660 \readonly
661
662 This read-only property holds the column in the view that contains the
663 item that is \l {Keyboard navigation}{current.} If no item is current, it will be \c -1.
664
665 \note In order for TableView to report what the current column is, you
666 need to assign an \l ItemSelectionModel to \l selectionModel.
667
668 \sa currentRow, selectionModel, {Selecting items}
669*/
670
671/*!
672 \qmlproperty int QtQuick::TableView::currentRow
673 \readonly
674
675 This read-only property holds the row in the view that contains the item
676 that is \l {Keyboard navigation}{current.} If no item is current, it will be \c -1.
677
678 \note In order for TableView to report what the current row is, you
679 need to assign an \l ItemSelectionModel to \l selectionModel.
680
681 \sa currentColumn, selectionModel, {Selecting items}
682*/
683
684/*!
685 \qmlproperty ItemSelectionModel QtQuick::TableView::selectionModel
686 \since 6.2
687
688 This property can be set to control which delegate items should be shown as
689 selected, and which item should be shown as current. If the delegate has a
690 \c {required property bool selected} defined, TableView will keep it in sync
691 with the selection state of the corresponding model item in the selection model.
692 If the delegate has a \c {required property bool current} defined, TableView will
693 keep it in sync with selectionModel.currentIndex.
694
695 \sa {Selecting items}, SelectionRectangle, keyNavigationEnabled, pointerNavigationEnabled
696*/
697
698/*!
699 \qmlproperty bool QtQuick::TableView::animate
700 \since 6.4
701
702 This property can be set to control if TableView should animate the
703 \l {Flickable::}{contentItem} (\l {Flickable::}{contentX} and
704 \l {Flickable::}{contentY}). It is used by
705 \l positionViewAtCell(), and when navigating
706 \l {QItemSelectionModel::currentIndex}{the current index}
707 with the keyboard. The default value is \c true.
708
709 If set to \c false, any ongoing animation will immediately stop.
710
711 \note This property is only a hint. TableView might choose to position
712 the content item without an animation if, for example, the target cell is not
713 \l {isRowLoaded()}{loaded}. However, if set to \c false, animations will
714 always be off.
715
716 \sa positionViewAtCell()
717*/
718
719/*!
720 \qmlproperty bool QtQuick::TableView::keyNavigationEnabled
721 \since 6.4
722
723 This property can be set to control if the user should be able
724 to change \l {QItemSelectionModel::currentIndex()}{the current index}
725 using the keyboard. The default value is \c true.
726
727 \note In order for TableView to support keyboard navigation, you
728 need to assign an \l ItemSelectionModel to \l selectionModel.
729
730 \sa {Keyboard navigation}, selectionModel, selectionBehavior
731 \sa pointerNavigationEnabled, {Flickable::}{interactive}
732*/
733
734/*!
735 \qmlproperty bool QtQuick::TableView::pointerNavigationEnabled
736 \since 6.4
737
738 This property can be set to control if the user should be able
739 to change \l {QItemSelectionModel::currentIndex()}{the current index}
740 using mouse or touch. The default value is \c true.
741
742 \sa selectionModel, keyNavigationEnabled, {Flickable::}{interactive}
743*/
744
745/*!
746 \qmlproperty enumeration QtQuick::TableView::selectionBehavior
747 \since 6.4
748
749 This property holds whether the user can select cells, rows or columns.
750
751 \value TableView.SelectionDisabled
752 The user cannot perform selections
753 \value TableView.SelectCells
754 (Default value) The user can select individual cells
755 \value TableView.SelectRows
756 The user can only select rows
757 \value TableView.SelectColumns
758 The user can only select columns
759
760 \sa {Selecting items}, selectionMode, selectionModel, keyNavigationEnabled
761*/
762
763/*!
764 \qmlproperty enumeration QtQuick::TableView::selectionMode
765 \since 6.6
766
767 If \l selectionBehavior is set to \c {TableView.SelectCells}, this property holds
768 whether the user can select one cell at a time, or multiple cells.
769 If \l selectionBehavior is set to \c {TableView.SelectRows}, this property holds
770 whether the user can select one row at a time, or multiple rows.
771 If \l selectionBehavior is set to \c {TableView.SelectColumns}, this property holds
772 whether the user can select one column at a time, or multiple columns.
773
774 The following modes are available:
775
776 \value TableView.SingleSelection
777 The user can select a single cell, row or column.
778 \value TableView.ContiguousSelection
779 The user can select a single contiguous block of cells.
780 An existing selection can be made bigger or smaller by holding down
781 the \c Shift modifier while selecting.
782 \value TableView.ExtendedSelection
783 (Default value) The user can select multiple individual blocks of
784 cells. An existing selection can be made bigger or smaller by
785 holding down the \c Shift modifier while selecting. A new selection
786 block can be started without clearing the current selection by
787 holding down the \c Control modifier while selecting.
788
789 \sa {Selecting items}, selectionBehavior, selectionModel, keyNavigationEnabled
790*/
791
792/*!
793 \qmlproperty bool QtQuick::TableView::resizableColumns
794 \since 6.5
795
796 This property holds whether the user is allowed to resize columns
797 by dragging between the cells. The default value is \c false.
798*/
799
800/*!
801 \qmlproperty bool QtQuick::TableView::resizableRows
802 \since 6.5
803
804 This property holds whether the user is allowed to resize rows
805 by dragging between the cells. The default value is \c false.
806*/
807
808/*!
809 \qmlproperty enumeration QtQuick::TableView::editTriggers
810 \since 6.5
811
812 This property holds the different ways the user can start to edit a cell.
813 It can be a combination of the following values:
814
815 \default TableView.DoubleTapped | TableView.EditKeyPressed.
816 \value TableView.NoEditTriggers - the user cannot trigger editing of cells.
817 When this value is set, TableView will neither \e {open or close}
818 the edit delegate as a response to any user interaction.
819 But the application can call \l edit() and \l closeEditor() manually.
820 \value TableView.SingleTapped - the user can edit a cell by single tapping it.
821 \value TableView.DoubleTapped - the user can edit a cell by double tapping it.
822 \value TableView.SelectedTapped - the user can edit a
823 \l {QItemSelectionModel::selectedIndexes()}{selected cell} by tapping it.
824 \value TableView.EditKeyPressed - the user can edit the
825 \l {ItemSelectionModel::currentIndex}{current cell} by pressing one
826 of the edit keys. The edit keys are decided by the OS, but are normally
827 \c Qt::Key_Enter and \c Qt::Key_Return.
828 \value TableView.AnyKeyPressed - the user can edit the
829 \l {ItemSelectionModel::currentIndex}{current cell} by pressing any key, other
830 than the cell navigation keys. The pressed key is also sent to the
831 focus object inside the \l {TableView::editDelegate}{edit delegate}.
832
833 For \c TableView.SelectedTapped, \c TableView.EditKeyPressed, and
834 \c TableView.AnyKeyPressed to have any effect, TableView needs to have a
835 \l {selectionModel}{selection model} assigned, since they depend on a
836 \l {ItemSelectionModel::currentIndex}{current index} being set. To be
837 able to receive any key events at all, TableView will also need to have
838 \l QQuickItem::activeFocus.
839
840 When editing a cell, the user can press \c Qt::Key_Tab or \c Qt::Key_Backtab
841 to \l {TableView::commit}{commit} the data, and move editing to the next
842 cell. This behavior can be disabled by setting
843 \l QQuickItem::activeFocusOnTab on TableView to \c false.
844
845 \note In order for a cell to be editable, the \l delegate needs an
846 \l {TableView::editDelegate}{edit delegate} attached, and the model
847 needs to return \c Qt::ItemIsEditable from \l QAbstractItemModel::flags()
848 (exemplified underneath).
849 If you still cannot edit a cell after activating one of the specified
850 triggers, you can, as a help, try to call \l edit() explicitly (e.g
851 from a Button/TapHandler). Doing so will print out a warning explaining
852 why the cell cannot be edited.
853
854 \code
855 Qt::ItemFlags QAbstractItemModelSubClass::flags(const QModelIndex &index) const override
856 {
857 Q_UNUSED(index)
858 return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
859 }
860 \endcode
861
862 \sa TableView::editDelegate, TableView::commit, {Editing cells}
863*/
864
865/*!
866 \qmlmethod void QtQuick::TableView::positionViewAtCell(point cell, PositionMode mode, point offset, rect subRect)
867
868 Positions \l {Flickable::}{contentX} and \l {Flickable::}{contentY} such
869 that \a cell is at the position specified by \a mode. \a mode
870 can be an or-ed combination of the following:
871
872 \value TableView.AlignLeft Position the cell at the left of the view.
873 \value TableView.AlignHCenter Position the cell at the horizontal center of the view.
874 \value TableView.AlignRight Position the cell at the right of the view.
875 \value TableView.AlignTop Position the cell at the top of the view.
876 \value TableView.AlignVCenter Position the cell at the vertical center of the view.
877 \value TableView.AlignBottom Position the cell at the bottom of the view.
878 \value TableView.AlignCenter The same as (TableView.AlignHCenter | TableView.AlignVCenter)
879 \value TableView.Visible If any part of the cell is visible then take no action. Otherwise
880 move the content item so that the entire cell becomes visible.
881 \value TableView.Contain If the entire cell is visible then take no action. Otherwise
882 move the content item so that the entire cell becomes visible. If the cell is
883 bigger than the view, the top-left part of the cell will be preferred.
884
885 If no vertical alignment is specified, vertical positioning will be ignored.
886 The same is true for horizontal alignment.
887
888 Optionally, you can specify \a offset to move \e contentX and \e contentY an extra number of
889 pixels beyond the target alignment. E.g if you want to position the view so
890 that cell [10, 10] ends up at the top-left corner with a 5px margin, you could do:
891
892 \code
893 positionViewAtCell(Qt.point(10, 10), TableView.AlignLeft | TableView.AlignTop, Qt.point(-5, -5))
894 \endcode
895
896 As of Qt 6.4, you can specify a \a subRect to position on a rectangle inside
897 the \a cell, rather than on the bounding rectangle of the whole cell. This can
898 be useful if the cell is e.g larger than the view, and you want to ensure that a
899 specific part of it is visible. The \a subRect needs to be
900 \l {QRectF::isValid()}{valid} to be taken into consideration.
901
902 \note It is not recommended to use \e contentX or \e contentY
903 to position the view at a particular cell. This is unreliable since removing items from
904 the start of the table does not cause all other items to be repositioned.
905 TableView can also sometimes place rows and columns at approximate positions to
906 optimize for speed. The only exception is if the cell is already visible in
907 the view, which can be checked upfront by calling \l itemAtCell().
908
909 Methods should only be called after the Component has completed. To position
910 the view at startup, this method should be called by Component.onCompleted. For
911 example, to position the view at the end:
912
913 \code
914 Component.onCompleted: positionViewAtCell(Qt.point(columns - 1, rows - 1), TableView.AlignRight | TableView.AlignBottom)
915 \endcode
916
917 \note The second argument to this function used to be Qt.Alignment. For backwards
918 compatibility, that enum can still be used. The change to use PositionMode was done
919 in Qt 6.4.
920
921 \sa animate
922*/
923
924/*!
925 \qmlmethod void QtQuick::TableView::positionViewAtIndex(QModelIndex index, PositionMode mode, point offset, rect subRect)
926 \since 6.5
927
928 Positions the view such that \a index is at the position specified
929 by \a mode, \a offset and \a subRect.
930
931 Convenience method for calling
932 \code
933 positionViewAtRow(rowAtIndex(index), mode & Qt.AlignVertical_Mask, offset.y, subRect)
934 positionViewAtColumn(columnAtIndex(index), mode & Qt.AlignVertical_Mask, offset.x, subRect)
935 \endcode
936*/
937
938/*!
939 \qmlmethod bool QtQuick::TableView::isColumnLoaded(int column)
940 \since 6.2
941
942 Returns \c true if the given \a column is loaded.
943
944 A column is loaded when TableView has loaded the delegate items
945 needed to show the column inside the view. This also usually means
946 that the column is visible for the user, but not always.
947
948 This function can be used whenever you need to iterate over the
949 delegate items for a column, e.g from a \l columnWidthProvider, to
950 be sure that the delegate items are available for iteration.
951*/
952
953/*!
954 \qmlmethod bool QtQuick::TableView::isRowLoaded(int row)
955 \since 6.2
956
957 Returns \c true if the given \a row is loaded.
958
959 A row is loaded when TableView has loaded the delegate items
960 needed to show the row inside the view. This also usually means
961 that the row is visible for the user, but not always.
962
963 This function can be used whenever you need to iterate over the
964 delegate items for a row, e.g from a \l rowHeightProvider, to
965 be sure that the delegate items are available for iteration.
966*/
967
968/*!
969 \qmlmethod void QtQuick::TableView::positionViewAtCell(int column, int row, PositionMode mode, point offset, rect subRect)
970 \deprecated
971
972 Use \l {positionViewAtIndex()}{positionViewAtIndex(index(row, column), ...)} instead.
973*/
974
975/*!
976 \qmlmethod void QtQuick::TableView::positionViewAtRow(int row, PositionMode mode, real offset, rect subRect)
977
978 Positions \l {Flickable::}{contentY} such that \a row is at the position specified
979 by \a mode, \a offset and \a subRect.
980
981 Convenience method for calling
982 \code
983 positionViewAtCell(Qt.point(0, row), mode & Qt.AlignVertical_Mask, offset, subRect)
984 \endcode
985*/
986
987/*!
988 \qmlmethod void QtQuick::TableView::positionViewAtColumn(int column, PositionMode mode, real offset, rect subRect)
989
990 Positions \l {Flickable::}{contentX} such that \a column is at the position specified
991 by \a mode, \a offset and \a subRect.
992
993 Convenience method for calling
994 \code
995 positionViewAtCell(Qt.point(column, 0), mode & Qt.AlignHorizontal_Mask, offset, subRect)
996 \endcode
997*/
998
999/*!
1000 \qmlmethod void QtQuick::TableView::moveColumn(int source, int destination)
1001 \since 6.8
1002
1003 Moves a column from the \a source to the \a destination position.
1004
1005 \note If a syncView is set, the sync view will control the internal index mapping for
1006 column reordering. Therefore, in that case, a call to this function will be forwarded to
1007 the sync view instead.
1008*/
1009
1010/*!
1011 \qmlmethod void QtQuick::TableView::clearColumnReordering()
1012 \since 6.8
1013
1014 Resets any previously applied column reordering.
1015
1016 \note If a syncView is set, a call to this function will be forwarded to
1017 corresponding view item and reset the column ordering.
1018*/
1019
1020/*!
1021 \qmlmethod void QtQuick::TableView::moveRow(int source, int destination)
1022 \since 6.8
1023
1024 Moves a row from the \a source to the \a destination position.
1025
1026 \note If a syncView is set, the sync view will control the internal index mapping for
1027 row reordering. Therefore, in that case, a call to this function will be forwarded to
1028 the sync view instead.
1029*/
1030
1031/*!
1032 \qmlmethod void QtQuick::TableView::clearRowReordering()
1033 \since 6.8
1034
1035 Resets any previously applied row reordering.
1036
1037 \note If a syncView is set, a call to this function will be forwarded to
1038 the corresponding view item and reset the row ordering.
1039*/
1040
1041/*!
1042 \qmlmethod Item QtQuick::TableView::itemAtCell(point cell)
1043
1044 Returns the delegate item at \a cell if loaded, otherwise \c null.
1045
1046 \note only the items that are visible in the view are normally loaded.
1047 As soon as a cell is flicked out of the view, the item inside will
1048 either be unloaded or placed in the recycle pool. As such, the return
1049 value should never be stored.
1050*/
1051
1052/*!
1053 \qmlmethod Item QtQuick::TableView::itemAtCell(int column, int row)
1054 \deprecated
1055
1056 Use \l {itemAtIndex()}{itemAtIndex(index(row, column))} instead.
1057*/
1058
1059/*!
1060 \qmlmethod Item QtQuick::TableView::itemAtIndex(QModelIndex index)
1061 \since 6.5
1062
1063 Returns the instantiated delegate item for the cell that represents
1064 \a index. If the item is not \l {isRowLoaded()}{loaded}, the value
1065 will be \c null.
1066
1067 \note only the items that are visible in the view are normally loaded.
1068 As soon as a cell is flicked out of the view, the item inside will
1069 either be unloaded or placed in the recycle pool. As such, the return
1070 value should never be stored.
1071
1072 \note If the \l model is not a QAbstractItemModel, you can also use
1073 \l {itemAtCell()}{itemAtCell(Qt.point(column, row))}. But be aware
1074 that \c {point.x} maps to columns and \c {point.y} maps to rows.
1075*/
1076
1077/*!
1078 \qmlmethod Point QtQuick::TableView::cellAtPos(point position, bool includeSpacing)
1079 \obsolete
1080
1081 Use cellAtPosition(point position) instead.
1082*/
1083
1084/*!
1085 \qmlmethod Point QtQuick::TableView::cellAtPos(real x, real y, bool includeSpacing)
1086 \obsolete
1087
1088 Use cellAtPosition(real x, real y) instead.
1089*/
1090
1091/*!
1092 \qmlmethod Point QtQuick::TableView::cellAtPosition(point position, bool includeSpacing)
1093
1094 Returns the cell at the given \a position in the table. \a position should be relative
1095 to the \l {Flickable::}{contentItem}. If no \l {isRowLoaded()}{loaded} cell intersects
1096 with \a position, the return value will be \c point(-1, -1).
1097
1098 If \a includeSpacing is set to \c true, a cell's bounding box will be considered
1099 to include half the adjacent \l rowSpacing and \l columnSpacing on each side. The
1100 default value is \c false.
1101
1102 \note A \l {Qt Quick Input Handlers}{Input Handler} attached to a TableView installs
1103 itself on the \l {Flickable::}{contentItem} rather than the view. So the position
1104 reported by the handler can be used directly in a call to this function without any
1105 \l {QQuickItem::mapFromItem()}{mapping}.
1106
1107 \sa columnSpacing, rowSpacing
1108*/
1109
1110/*!
1111 \qmlmethod Point QtQuick::TableView::cellAtPosition(real x, real y, bool includeSpacing)
1112
1113 Convenience for calling \c{cellAtPosition(Qt.point(x, y), includeSpacing)}.
1114*/
1115
1116/*!
1117 \qmlmethod real QtQuick::TableView::columnWidth(int column)
1118 \since 6.2
1119
1120 Returns the width of the given \a column. If the column is not
1121 loaded (and therefore not visible), the return value will be \c -1.
1122
1123 \sa columnWidthProvider, implicitColumnWidth(), isColumnLoaded(), {Row heights and column widths}
1124*/
1125
1126/*!
1127 \qmlmethod real QtQuick::TableView::rowHeight(int row)
1128 \since 6.2
1129
1130 Returns the height of the given \a row. If the row is not
1131 loaded (and therefore not visible), the return value will be \c -1.
1132
1133 \sa rowHeightProvider, implicitRowHeight(), isRowLoaded(), {Row heights and column widths}
1134*/
1135
1136/*!
1137 \qmlmethod real QtQuick::TableView::implicitColumnWidth(int column)
1138 \since 6.2
1139
1140 Returns the implicit width of the given \a column. This is the largest
1141 \l {QtQuick::Item::}{implicitWidth} found among the currently
1142 \l{isRowLoaded()}{loaded} delegate items inside that column.
1143
1144 If the \a column is not loaded (and therefore not visible), the return value is \c -1.
1145
1146 \sa columnWidth(), isRowLoaded(), {Row heights and column widths}
1147*/
1148
1149/*!
1150 \qmlmethod real QtQuick::TableView::implicitRowHeight(int row)
1151 \since 6.2
1152
1153 Returns the implicit height of the given \a row. This is the largest
1154 \l {QtQuick::Item::}{implicitHeight} found among the currently
1155 \l{isColumnLoaded()}{loaded} delegate items inside that row.
1156
1157 If the \a row is not loaded (and therefore not visible), the return value is \c -1.
1158
1159 \sa rowHeight(), isColumnLoaded(), {Row heights and column widths}
1160*/
1161
1162/*!
1163 \qmlmethod void QtQuick::TableView::setColumnWidth(int column, real size)
1164
1165 Sets the explicit column width of column \a column to \a size.
1166
1167 If you want to read back the values you set with this function, you
1168 should use \l explicitColumnWidth(). \l columnWidth() will return
1169 the actual size of the column, which can be different if a
1170 \l columnWidthProvider is set.
1171
1172 When TableView needs to resolve the width of \a column, it will first try
1173 to call the \l columnWidthProvider. Only if a provider is not set, will
1174 the widths set with this function be used by default. You can, however, call
1175 \l explicitColumnWidth() from within the provider, and if needed, moderate
1176 the values to e.g always be within a certain interval.
1177 The following snippet shows an example on how to do that:
1178
1179 \code
1180 columnWidthProvider: function(column) {
1181 let w = explicitColumnWidth(column)
1182 if (w >= 0)
1183 return Math.max(100, w);
1184 return implicitColumnWidth(column)
1185 }
1186 \endcode
1187
1188 If \a size is equal to \c 0, the column will be hidden. If \a size is
1189 equal to \c -1, the column will be reset back to use \l implicitColumnWidth().
1190 You are allowed to specify column sizes for columns that are outside the
1191 size of the model.
1192
1193 \note The sizes you set will not be cleared if you change the \l model.
1194 To clear the sizes, you need to call \l clearColumnWidths() explicitly.
1195
1196 \include tableview.qdocinc explicit-column-size-and-syncview
1197
1198 \note For models with \e lots of columns, using \l setColumnWidth() to set the widths for
1199 all the columns at start-up, can be suboptimal. This will consume start-up time and
1200 memory (for storing all the widths). A more scalable approach is to use a
1201 \l columnWidthProvider instead, or rely on the implicit width of the delegate.
1202 A \c columnWidthProvider will only be called on an as-needed basis, and will not
1203 be affected by the size of the model.
1204
1205 \sa explicitColumnWidth(), setRowHeight(), clearColumnWidths(), {Row heights and column widths}
1206*/
1207
1208/*!
1209 \qmlmethod void QtQuick::TableView::clearColumnWidths()
1210
1211 Clears all the column widths set with \l setColumnWidth().
1212
1213 \include tableview.qdocinc explicit-column-size-and-syncview
1214
1215 \sa setColumnWidth(), clearRowHeights(), {Row heights and column widths}
1216*/
1217
1218/*!
1219 \qmlmethod real QtQuick::TableView::explicitColumnWidth(int column)
1220
1221 Returns the width of the \a column set with \l setColumnWidth(). This width might
1222 differ from the actual width of the column, if a \l columnWidthProvider
1223 is in use. To get the actual width of a column, use \l columnWidth().
1224
1225 A return value equal to \c 0 means that the column has been told to hide.
1226 A return value equal to \c -1 means that no explicit width has been set
1227 for the column.
1228
1229 \include tableview.qdocinc explicit-column-size-and-syncview
1230
1231 \sa setColumnWidth(), columnWidth(), {Row heights and column widths}
1232*/
1233
1234/*!
1235 \qmlmethod void QtQuick::TableView::setRowHeight(int row, real size)
1236
1237 Sets the explicit row height of row \a row to \a size.
1238
1239 If you want to read back the values you set with this function, you
1240 should use \l explicitRowHeight(). \l rowHeight() will return
1241 the actual height of the row, which can be different if a
1242 \l rowHeightProvider is set.
1243
1244 When TableView needs to resolve the height of \a row, it will first try
1245 to call the \l rowHeightProvider. Only if a provider is not set, will
1246 the heights set with this function be used by default. You can, however, call
1247 \l explicitRowHeight() from within the provider, and if needed, moderate
1248 the values to e.g always be within a certain interval.
1249 The following snippet shows an example on how to do that:
1250
1251 \code
1252 rowHeightProvider: function(row) {
1253 let h = explicitRowHeight(row)
1254 if (h >= 0)
1255 return Math.max(100, h);
1256 return implicitRowHeight(row)
1257 }
1258 \endcode
1259
1260 If \a size is equal to \c 0, the row will be hidden. If \a size is
1261 equal to \c -1, the row will be reset back to use \l implicitRowHeight().
1262 You are allowed to specify row sizes for rows that are outside the
1263 size of the model.
1264
1265 \note The sizes you set will not be cleared if you change the \l model.
1266 To clear the sizes, you need to call \l clearRowHeights() explicitly.
1267
1268 \include tableview.qdocinc explicit-row-size-and-syncview
1269
1270 \note For models with \e lots of rows, using \l setRowHeight() to set the heights for
1271 all the rows at start-up, can be suboptimal. This will consume start-up time and
1272 memory (for storing all the heights). A more scalable approach is to use a
1273 \l rowHeightProvider instead, or rely on the implicit height of the delegate.
1274 A \c rowHeightProvider will only be called on an as-needed basis, and will not
1275 be affected by the size of the model.
1276
1277 \sa explicitRowHeight(), setColumnWidth(), {Row heights and column widths}
1278*/
1279
1280/*!
1281 \qmlmethod void QtQuick::TableView::clearRowHeights()
1282
1283 Clears all the row heights set with \l setRowHeight().
1284
1285 \include tableview.qdocinc explicit-row-size-and-syncview
1286
1287 \sa setRowHeight(), clearColumnWidths(), {Row heights and column widths}
1288*/
1289
1290/*!
1291 \qmlmethod real QtQuick::TableView::explicitRowHeight(int row)
1292
1293 Returns the height of the \a row set with \l setRowHeight(). This height might
1294 differ from the actual height of the column, if a \l rowHeightProvider
1295 is in use. To get the actual height of a row, use \l rowHeight().
1296
1297 A return value equal to \c 0 means that the row has been told to hide.
1298 A return value equal to \c -1 means that no explicit height has been set
1299 for the row.
1300
1301 \include tableview.qdocinc explicit-row-size-and-syncview
1302
1303 \sa setRowHeight(), rowHeight(), {Row heights and column widths}
1304*/
1305
1306/*!
1307 \qmlmethod QModelIndex QtQuick::TableView::modelIndex(int row, int column)
1308 \since 6.4
1309 \deprecated
1310
1311 Use \l {QtQuick::TableView::}{index(int row, int column)} instead.
1312
1313 \note Because of an API incompatible change between Qt 6.4.0 and Qt 6.4.2, the
1314 order of \c row and \c column was specified in the opposite order. If you
1315 rely on the order to be \c {modelIndex(column, row)}, you can set the
1316 environment variable \c QT_QUICK_TABLEVIEW_COMPAT_VERSION to \c 6.4
1317*/
1318
1319/*!
1320 \qmlmethod QModelIndex QtQuick::TableView::modelIndex(point cell)
1321 \since 6.4
1322
1323 Convenience function for doing:
1324 \code
1325 index(cell.y, cell.x)
1326 \endcode
1327
1328 A \a cell is simply a \l point that combines row and column into
1329 a single type.
1330
1331 \note \c {point.x} will map to the column, and \c {point.y} will map to the row.
1332
1333 \sa index()
1334*/
1335
1336/*!
1337 \qmlmethod QModelIndex QtQuick::TableView::index(int row, int column)
1338 \since 6.4.3
1339
1340 Returns the \l QModelIndex that maps to \a row and \a column in the view.
1341
1342 \a row and \a column should be the row and column in the view (table row and
1343 table column), and not a row and column in the model. For a plain
1344 TableView, this is equivalent of calling \c {model.index(row, column).}
1345 But for a subclass of TableView, like TreeView, where the data model is
1346 wrapped inside an internal proxy model that flattens the tree structure
1347 into a table, you need to use this function to resolve the model index.
1348
1349 \sa rowAtIndex(), columnAtIndex()
1350*/
1351
1352/*!
1353 \qmlmethod int QtQuick::TableView::rowAtIndex(QModelIndex modelIndex)
1354 \since 6.4
1355
1356 Returns the row in the view that maps to \a modelIndex in the model.
1357
1358 \sa columnAtIndex(), index()
1359*/
1360
1361/*!
1362 \qmlmethod int QtQuick::TableView::columnAtIndex(QModelIndex modelIndex)
1363 \since 6.4
1364
1365 Returns the column in the view that maps to \a modelIndex in the model.
1366
1367 \sa rowAtIndex(), index()
1368*/
1369
1370/*!
1371 \qmlmethod point QtQuick::TableView::cellAtIndex(QModelIndex modelIndex)
1372 \since 6.4
1373
1374 Returns the cell in the view that maps to \a modelIndex in the model.
1375 Convenience function for doing:
1376
1377 \code
1378 Qt.point(columnAtIndex(modelIndex), rowAtIndex(modelIndex))
1379 \endcode
1380
1381 A cell is simply a \l point that combines row and column into
1382 a single type.
1383
1384 \note that \c {point.x} will map to the column, and
1385 \c {point.y} will map to the row.
1386*/
1387
1388/*!
1389 \qmlmethod void QtQuick::TableView::edit(QModelIndex modelIndex)
1390 \since 6.5
1391
1392 This function starts an editing session for the cell that represents
1393 \a modelIndex. If the user is already editing another cell, that session ends.
1394
1395 Normally you can specify the different ways of starting an edit session by
1396 using \l editTriggers instead. If that isn't sufficient, you can use this
1397 function. To take full control over cell editing and keep TableView from
1398 interfering, set editTriggers to \c TableView.NoEditTriggers.
1399
1400 \note The \l {ItemSelectionModel::currentIndex}{current index} in the
1401 \l {selectionModel}{selection model} will also change to \a modelIndex.
1402
1403 \sa closeEditor(), editTriggers, TableView::editDelegate, {Editing cells}
1404*/
1405
1406/*!
1407 \qmlmethod void QtQuick::TableView::closeEditor()
1408 \since 6.5
1409
1410 If the user is editing a cell, calling this function will
1411 stop the editing, and destroy the edit delegate instance.
1412
1413 \sa edit(), TableView::editDelegate, {Editing cells}
1414*/
1415
1416/*!
1417 \qmlsignal QtQuick::TableView::layoutChanged()
1418 \since 6.5
1419
1420 This signal is emitted whenever the layout of the
1421 \l {isColumnLoaded()}{loaded} rows and columns has potentially
1422 changed. This will especially be the case when \l forceLayout()
1423 is called, but also when e.g resizing a row or a column, or
1424 when a row or column have entered or left the viewport.
1425
1426 This signal can be used to for example update the geometry
1427 of overlays.
1428
1429 \sa forceLayout(), {Overlays and underlays}
1430*/
1431
1432/*!
1433 \qmlsignal QtQuick::TableView::columnMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
1434 \since 6.8
1435
1436 This signal is emitted when a column is moved. The column's logical index is specified by
1437 \a logicalIndex, the old index by \a oldVisualIndex, and the new index position by
1438 \a newVisualIndex.
1439*/
1440
1441/*!
1442 \qmlsignal QtQuick::TableView::rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
1443 \since 6.8
1444
1445 This signal is emitted when a row is moved. The row's logical index is specified by
1446 \a logicalIndex, the old index by \a oldVisualIndex, and the new index position by
1447 \a newVisualIndex.
1448*/
1449
1450/*!
1451 \qmlattachedproperty TableView QtQuick::TableView::view
1452
1453 This attached property holds the view that manages the delegate instance.
1454 It is attached to each instance of the delegate.
1455*/
1456
1457/*!
1458 \qmlattachedsignal QtQuick::TableView::pooled
1459
1460 This signal is emitted after an item has been added to the reuse
1461 pool. You can use it to pause ongoing timers or animations inside
1462 the item, or free up resources that cannot be reused.
1463
1464 This signal is emitted only if the \l reuseItems property is \c true.
1465
1466 \sa {Reusing items}, reuseItems, reused
1467*/
1468
1469/*!
1470 \qmlattachedsignal QtQuick::TableView::reused
1471
1472 This signal is emitted after an item has been reused. At this point, the
1473 item has been taken out of the pool and placed inside the content view,
1474 and the model properties such as index, row, and column have been updated.
1475
1476 Other properties that are not provided by the model does not change when an
1477 item is reused. You should avoid storing any state inside a delegate, but if
1478 you do, manually reset that state on receiving this signal.
1479
1480 This signal is emitted when the item is reused, and not the first time the
1481 item is created.
1482
1483 This signal is emitted only if the \l reuseItems property is \c true.
1484
1485 \sa {Reusing items}, reuseItems, pooled
1486*/
1487
1488/*!
1489 \qmlattachedsignal QtQuick::TableView::commit
1490 This signal is emitted by the \l {TableView::editDelegate}{edit delegate}
1491
1492 This attached signal is emitted when the \l {TableView::editDelegate}{edit delegate}
1493 is active, and the user presses \l Qt::Key_Enter or \l Qt::Key_Return. It will also
1494 be emitted if TableView has \l QQuickItem::activeFocusOnTab set, and the user
1495 presses Qt::Key_Tab or Qt::Key_Backtab.
1496
1497 This signal will \e not be emitted if editing ends because of reasons other
1498 than the ones mentioned. This includes e.g if the user presses
1499 Qt::Key_Escape, taps outside the delegate, the row or column being
1500 edited is deleted, or if the application calls \l closeEditor().
1501
1502 Upon receiving the signal, the edit delegate should write any modified data
1503 back to the model.
1504
1505 \note This property should be attached to the
1506 \l {TableView::editDelegate}{edit delegate}, and not to the \l delegate.
1507
1508 \sa TableView::editDelegate, editTriggers, {Editing cells}
1509*/
1510
1511/*!
1512 \qmlattachedproperty Component QtQuick::TableView::editDelegate
1513
1514 This attached property holds the edit delegate. It's instantiated
1515 when editing begins, and parented to the delegate it edits. It
1516 supports the same required properties as the
1517 \l {TableView::delegate}{TableView delegate}, including \c index, \c row and \c column.
1518 Properties of the model, like \c display and \c edit, are also available
1519 (depending on the \l {QAbstractItemModel::roleNames()}{role names} exposed
1520 by the model).
1521
1522 Editing starts when the actions specified by \l editTriggers are met, and
1523 the current cell is editable.
1524
1525 \note In order for a cell to be editable, the model needs to override
1526 \l QAbstractItemModel::flags(), and return \c Qt::ItemIsEditable.
1527
1528 You can also open and close the edit delegate manually by calling \l edit()
1529 and \l closeEditor(), respectively.
1530
1531 Editing ends when the user presses \c Qt::Key_Enter or \c Qt::Key_Return
1532 (and also \c Qt::Key_Tab or \c Qt::Key_Backtab, if TableView has
1533 \l QQuickItem::activeFocusOnTab set). In that case, the \l TableView::commit
1534 signal will be emitted, so that the edit delegate can respond by writing any
1535 modified data back to the model. If editing ends because of other reasons
1536 (e.g if the user presses Qt::Key_Escape), the signal will not be emitted.
1537 In any case will \l {Component::destruction}{destruction()} be emitted in the end.
1538
1539 While the edit delegate is showing, the cell underneath will still be visible, and
1540 therefore shine through if the edit delegate is translucent, or otherwise doesn't
1541 cover the whole cell. If this is not wanted, you can either let the root item
1542 of the edit delegate be a solid \l Rectangle, or hide some of the items
1543 inside the \l {TableView::delegate}{TableView delegate.}. The latter can be done
1544 by defining a property \c {required property bool editing} inside it, that you
1545 bind to the \l {QQuickItem::}{visible} property of some of the child items.
1546 The following snippet shows how to do that in a custom delegate:
1547
1548 \snippet qml/tableview/editdelegate.qml 1
1549
1550 When the edit delegate is instantiated, TableView will call \l QQuickItem::forceActiveFocus()
1551 on it. If you want active focus to be set on a child of the edit delegate instead, let
1552 the edit delegate be a \l FocusScope.
1553
1554 By default, \l TableViewDelegate provides an \l {TableView::editDelegate}{edit delegate},
1555 and you can also set your own:
1556
1557 \code
1558 delegate: TableViewDelegate {
1559 TableView.editDelegate: TextField {
1560 width: parent.width
1561 height: parent.height
1562 text: display
1563 TableView.onCommit: display = text
1564 }
1565 }
1566 \endcode
1567
1568 \sa editTriggers, TableView::commit, edit(), closeEditor(), {Editing cells}, TableViewDelegate
1569*/
1570
1571QT_BEGIN_NAMESPACE
1572
1573QQuickSelectable::~QQuickSelectable() { }
1574
1575Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle")
1576
1577#define Q_TABLEVIEW_UNREACHABLE(output) { dumpTable(); qWarning() << "output:" << output; Q_UNREACHABLE(); }
1578#define Q_TABLEVIEW_ASSERT(cond, output) Q_ASSERT((cond) || [&](){ dumpTable(); qWarning() << "output:" << output; return false;}())
1579
1580static const Qt::Edge allTableEdges[] = { Qt::LeftEdge, Qt::RightEdge, Qt::TopEdge, Qt::BottomEdge };
1581
1582static const char* kRequiredProperty_tableView = "tableView";
1583static const char* kRequiredProperties = "_qt_tableview_requiredpropertymask";
1584static const char* kRequiredProperty_selected = "selected";
1585static const char* kRequiredProperty_current = "current";
1586static const char* kRequiredProperty_editing = "editing";
1587static const char* kRequiredProperty_containsDrag = "containsDrag";
1588
1589QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildState state)
1590{
1591#define TV_REBUILDSTATE(STATE)
1592 case QQuickTableViewPrivate::RebuildState::STATE:
1593 dbg << QStringLiteral(#STATE); break;
1594
1595 switch (state) {
1596 TV_REBUILDSTATE(Begin);
1597 TV_REBUILDSTATE(LoadInitalTable);
1598 TV_REBUILDSTATE(VerifyTable);
1599 TV_REBUILDSTATE(LayoutTable);
1600 TV_REBUILDSTATE(CancelOvershoot);
1601 TV_REBUILDSTATE(UpdateContentSize);
1602 TV_REBUILDSTATE(PreloadColumns);
1603 TV_REBUILDSTATE(PreloadRows);
1604 TV_REBUILDSTATE(MovePreloadedItemsToPool);
1605 TV_REBUILDSTATE(Done);
1606 }
1607
1608 return dbg;
1609}
1610
1611QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildOptions options)
1612{
1613#define TV_REBUILDOPTION(OPTION)
1614 if (options & QQuickTableViewPrivate::RebuildOption::OPTION)
1615 dbg << QStringLiteral(#OPTION)
1616
1617 if (options == QQuickTableViewPrivate::RebuildOption::None) {
1618 dbg << QStringLiteral("None");
1619 } else {
1620 TV_REBUILDOPTION(All);
1621 TV_REBUILDOPTION(LayoutOnly);
1622 TV_REBUILDOPTION(ViewportOnly);
1623 TV_REBUILDOPTION(CalculateNewTopLeftRow);
1624 TV_REBUILDOPTION(CalculateNewTopLeftColumn);
1625 TV_REBUILDOPTION(CalculateNewContentWidth);
1626 TV_REBUILDOPTION(CalculateNewContentHeight);
1627 TV_REBUILDOPTION(PositionViewAtRow);
1628 TV_REBUILDOPTION(PositionViewAtColumn);
1629 }
1630
1631 return dbg;
1632}
1633
1634QQuickTableViewPrivate::EdgeRange::EdgeRange()
1635 : startIndex(kEdgeIndexNotSet)
1636 , endIndex(kEdgeIndexNotSet)
1637 , size(0)
1638{}
1639
1640bool QQuickTableViewPrivate::EdgeRange::containsIndex(Qt::Edge edge, int index)
1641{
1642 if (startIndex == kEdgeIndexNotSet)
1643 return false;
1644
1645 if (endIndex == kEdgeIndexAtEnd) {
1646 switch (edge) {
1647 case Qt::LeftEdge:
1648 case Qt::TopEdge:
1649 return index <= startIndex;
1650 case Qt::RightEdge:
1651 case Qt::BottomEdge:
1652 return index >= startIndex;
1653 }
1654 }
1655
1656 const int s = std::min(startIndex, endIndex);
1657 const int e = std::max(startIndex, endIndex);
1658 return index >= s && index <= e;
1659}
1660
1661QQuickTableViewPrivate::QQuickTableViewPrivate()
1662 : QQuickFlickablePrivate()
1663{
1664}
1665
1666QQuickTableViewPrivate::~QQuickTableViewPrivate()
1667{
1668 if (editItem) {
1669 QQuickItem *cellItem = editItem->parentItem();
1670 Q_ASSERT(cellItem);
1671 editModel->dispose(editItem);
1672 tableModel->release(cellItem, QQmlInstanceModel::NotReusable);
1673 }
1674
1675 if (editModel)
1676 delete editModel;
1677
1678 for (auto *fxTableItem : loadedItems) {
1679 if (auto item = fxTableItem->item) {
1680 if (fxTableItem->ownItem)
1681 delete item;
1682 else if (tableModel)
1683 tableModel->dispose(item);
1684 }
1685 delete fxTableItem;
1686 }
1687
1688 if (tableModel)
1689 delete tableModel;
1690}
1691
1692QString QQuickTableViewPrivate::tableLayoutToString() const
1693{
1694 if (loadedItems.isEmpty())
1695 return QLatin1String("table is empty!");
1696 return QString(QLatin1String("table cells: (%1,%2) -> (%3,%4), item count: %5, table rect: %6,%7 x %8,%9"))
1697 .arg(leftColumn()).arg(topRow())
1698 .arg(rightColumn()).arg(bottomRow())
1699 .arg(loadedItems.size())
1700 .arg(loadedTableOuterRect.x())
1701 .arg(loadedTableOuterRect.y())
1702 .arg(loadedTableOuterRect.width())
1703 .arg(loadedTableOuterRect.height());
1704}
1705
1706void QQuickTableViewPrivate::dumpTable() const
1707{
1708 auto listCopy = loadedItems.values();
1709 std::stable_sort(listCopy.begin(), listCopy.end(),
1710 [](const FxTableItem *lhs, const FxTableItem *rhs)
1711 { return lhs->index < rhs->index; });
1712
1713 qWarning() << QStringLiteral("******* TABLE DUMP *******");
1714 for (int i = 0; i < listCopy.size(); ++i)
1715 qWarning() << static_cast<FxTableItem *>(listCopy.at(i))->cell;
1716 qWarning() << tableLayoutToString();
1717
1718 const QString filename = QStringLiteral("QQuickTableView_dumptable_capture.png");
1719 const QString path = QDir::current().absoluteFilePath(filename);
1720 if (q_func()->window() && q_func()->window()->grabWindow().save(path))
1721 qWarning() << "Window capture saved to:" << path;
1722}
1723
1724void QQuickTableViewPrivate::setRequiredProperty(const char *property,
1725 const QVariant &value, int serializedModelIndex, QObject *object, bool init)
1726{
1727 Q_Q(QQuickTableView);
1728
1729 QQmlTableInstanceModel *tableInstanceModel = qobject_cast<QQmlTableInstanceModel *>(model);
1730 if (!tableInstanceModel) {
1731 // TableView only supports using required properties when backed by
1732 // a QQmlTableInstanceModel. This is almost always the case, except
1733 // if you assign it an ObjectModel or a DelegateModel (which are really
1734 // not supported by TableView, it expects a QAIM).
1735 return;
1736 }
1737
1738 // Attaching a property list to the delegate item is just a
1739 // work-around until QMetaProperty::isRequired() works (QTBUG-98846).
1740 const QString propertyName = QString::fromUtf8(property);
1741
1742 if (init) {
1743 bool wasRequired = false;
1744 if (object == editItem) {
1745 // Special case: the item that we should write to belongs to the edit
1746 // model rather than 'model' (which is used for normal delegate items).
1747 wasRequired = editModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1748 } else {
1749 wasRequired = tableInstanceModel->setRequiredProperty(serializedModelIndex, propertyName, value);
1750 }
1751 if (wasRequired) {
1752 QStringList propertyList = object->property(kRequiredProperties).toStringList();
1753 object->setProperty(kRequiredProperties, propertyList << propertyName);
1754 }
1755 } else {
1756 {
1757 const QStringList propertyList = object->property(kRequiredProperties).toStringList();
1758 if (propertyList.contains(propertyName)) {
1759 const auto metaObject = object->metaObject();
1760 const int propertyIndex = metaObject->indexOfProperty(property);
1761 const auto metaProperty = metaObject->property(propertyIndex);
1762 metaProperty.write(object, value);
1763 }
1764 }
1765
1766 if (editItem) {
1767 // Whenever we're told to update a required property for a table item that has the
1768 // same model index as the edit item, we also mirror that update to the edit item.
1769 // As such, this function is never called for the edit item directly (except the
1770 // first time when it needs to be initialized).
1771 Q_TABLEVIEW_ASSERT(object != editItem, "");
1772 const QModelIndex modelIndex = q->modelIndex(cellAtModelIndex(serializedModelIndex));
1773 if (modelIndex == editIndex) {
1774 const QStringList propertyList = editItem->property(kRequiredProperties).toStringList();
1775 if (propertyList.contains(propertyName)) {
1776 const auto metaObject = editItem->metaObject();
1777 const int propertyIndex = metaObject->indexOfProperty(property);
1778 const auto metaProperty = metaObject->property(propertyIndex);
1779 metaProperty.write(editItem, value);
1780 }
1781 }
1782 }
1783
1784 }
1785}
1786
1787QQuickItem *QQuickTableViewPrivate::selectionPointerHandlerTarget() const
1788{
1789 return const_cast<QQuickTableView *>(q_func())->contentItem();
1790}
1791
1792bool QQuickTableViewPrivate::hasSelection() const
1793{
1794 return selectionModel && selectionModel->hasSelection();
1795}
1796
1797bool QQuickTableViewPrivate::startSelection(const QPointF &pos, Qt::KeyboardModifiers modifiers)
1798{
1799 Q_Q(QQuickTableView);
1800 if (!selectionModel) {
1801 if (warnNoSelectionModel)
1802 qmlWarning(q_func()) << "Cannot start selection: no SelectionModel assigned!";
1803 warnNoSelectionModel = false;
1804 return false;
1805 }
1806
1807 if (selectionBehavior == QQuickTableView::SelectionDisabled) {
1808 qmlWarning(q) << "Cannot start selection: TableView.selectionBehavior == TableView.SelectionDisabled";
1809 return false;
1810 }
1811
1812 // Only allow a selection if it doesn't conflict with resizing
1813 if (resizeHandler->state() != QQuickTableViewResizeHandler::Listening)
1814 return false;
1815
1816 // For SingleSelection and ContiguousSelection, we should only allow one
1817 // selection at a time. We also clear the current selection if the mode
1818 // is ExtendedSelection, but no modifier is being held.
1819 if (selectionMode == QQuickTableView::SingleSelection
1820 || selectionMode == QQuickTableView::ContiguousSelection
1821 || modifiers == Qt::NoModifier)
1822 clearSelection();
1823 else if (selectionModel)
1824 existingSelection = selectionModel->selection();
1825
1826 // If pos is on top of an unselected cell, we start a session where the user selects which
1827 // cells to become selected. Otherwise, if pos is on top of an already selected cell and
1828 // ctrl is being held, we start a session where the user selects which selected cells to
1829 // become unselected.
1830 selectionFlag = QItemSelectionModel::Select;
1831 if (modifiers & Qt::ControlModifier) {
1832 QPoint startCell = clampedCellAtPos(pos);
1833 if (!cellIsValid(startCell))
1834 return false;
1835 const QModelIndex startIndex = q->index(startCell.y(), startCell.x());
1836 if (selectionModel->isSelected(startIndex))
1837 selectionFlag = QItemSelectionModel::Deselect;
1838 }
1839
1840 selectionStartCell = QPoint(-1, -1);
1841 selectionEndCell = QPoint(-1, -1);
1842 closeEditorAndCommit();
1843 return true;
1844}
1845
1846void QQuickTableViewPrivate::setSelectionStartPos(const QPointF &pos)
1847{
1848 Q_Q(QQuickTableView);
1849 Q_ASSERT(selectionFlag != QItemSelectionModel::NoUpdate);
1850 if (loadedItems.isEmpty())
1851 return;
1852 if (!selectionModel) {
1853 if (warnNoSelectionModel)
1854 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1855 warnNoSelectionModel = false;
1856 return;
1857 }
1858 const QAbstractItemModel *qaim = selectionModel->model();
1859 if (!qaim)
1860 return;
1861
1862 if (selectionMode == QQuickTableView::SingleSelection
1863 && cellIsValid(selectionStartCell)) {
1864 return;
1865 }
1866
1867 const QRect prevSelection = selection();
1868
1869 QPoint clampedCell;
1870 if (pos.x() == -1) {
1871 // Special case: use current cell as start cell
1872 clampedCell = q->cellAtIndex(selectionModel->currentIndex());
1873 } else {
1874 clampedCell = clampedCellAtPos(pos);
1875 if (cellIsValid(clampedCell))
1876 setCurrentIndex(clampedCell);
1877 }
1878
1879 if (!cellIsValid(clampedCell))
1880 return;
1881
1882 switch (selectionBehavior) {
1883 case QQuickTableView::SelectCells:
1884 selectionStartCell = clampedCell;
1885 break;
1886 case QQuickTableView::SelectRows:
1887 selectionStartCell = QPoint(0, clampedCell.y());
1888 break;
1889 case QQuickTableView::SelectColumns:
1890 selectionStartCell = QPoint(clampedCell.x(), 0);
1891 break;
1892 case QQuickTableView::SelectionDisabled:
1893 return;
1894 }
1895
1896 if (!cellIsValid(selectionEndCell))
1897 return;
1898
1899 // Update selection model
1900 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1901 updateSelection(prevSelection, selection());
1902}
1903
1904void QQuickTableViewPrivate::setSelectionEndPos(const QPointF &pos)
1905{
1906 Q_ASSERT(selectionFlag != QItemSelectionModel::NoUpdate);
1907 if (loadedItems.isEmpty())
1908 return;
1909 if (!selectionModel) {
1910 if (warnNoSelectionModel)
1911 qmlWarning(q_func()) << "Cannot set selection: no SelectionModel assigned!";
1912 warnNoSelectionModel = false;
1913 return;
1914 }
1915 const QAbstractItemModel *qaim = selectionModel->model();
1916 if (!qaim)
1917 return;
1918
1919 const QRect prevSelection = selection();
1920
1921 QPoint clampedCell;
1922 if (selectionMode == QQuickTableView::SingleSelection) {
1923 clampedCell = selectionStartCell;
1924 } else {
1925 clampedCell = clampedCellAtPos(pos);
1926 if (!cellIsValid(clampedCell))
1927 return;
1928 }
1929
1930 setCurrentIndex(clampedCell);
1931
1932 switch (selectionBehavior) {
1933 case QQuickTableView::SelectCells:
1934 selectionEndCell = clampedCell;
1935 break;
1936 case QQuickTableView::SelectRows:
1937 selectionEndCell = QPoint(tableSize.width() - 1, clampedCell.y());
1938 break;
1939 case QQuickTableView::SelectColumns:
1940 selectionEndCell = QPoint(clampedCell.x(), tableSize.height() - 1);
1941 break;
1942 case QQuickTableView::SelectionDisabled:
1943 return;
1944 }
1945
1946 if (!cellIsValid(selectionStartCell))
1947 return;
1948
1949 // Update selection model
1950 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
1951 updateSelection(prevSelection, selection());
1952}
1953
1954QPoint QQuickTableViewPrivate::clampedCellAtPos(const QPointF &pos) const
1955{
1956 Q_Q(const QQuickTableView);
1957
1958 // Note: pos should be relative to selectionPointerHandlerTarget()
1959 QPoint cell = q->cellAtPosition(pos, true);
1960 if (cellIsValid(cell))
1961 return cell;
1962
1963 if (loadedTableOuterRect.width() == 0 || loadedTableOuterRect.height() == 0)
1964 return QPoint(-1, -1);
1965
1966 // Clamp the cell to the loaded table and the viewport, whichever is the smallest
1967 QPointF clampedPos(
1968 qBound(loadedTableOuterRect.x(), pos.x(), loadedTableOuterRect.right() - 1),
1969 qBound(loadedTableOuterRect.y(), pos.y(), loadedTableOuterRect.bottom() - 1));
1970 QPointF clampedPosInView = q->mapFromItem(selectionPointerHandlerTarget(), clampedPos);
1971 clampedPosInView.rx() = qBound(0., clampedPosInView.x(), viewportRect.width());
1972 clampedPosInView.ry() = qBound(0., clampedPosInView.y(), viewportRect.height());
1973 clampedPos = q->mapToItem(selectionPointerHandlerTarget(), clampedPosInView);
1974
1975 return q->cellAtPosition(clampedPos, true);
1976}
1977
1978void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection)
1979{
1980 if (oldSelection == newSelection)
1981 return;
1982
1983 const QAbstractItemModel *qaim = selectionModel->model();
1984 const QRect oldRect = oldSelection.normalized();
1985 const QRect newRect = newSelection.normalized();
1986
1987 const auto &columnMapping = syncView ? syncView->d_func()->horizontalLogicalIndices
1988 : horizontalLogicalIndices;
1989 const auto &rowMapping = syncView ? syncView->d_func()->verticalLogicalIndices
1990 : verticalLogicalIndices;
1991 const bool hasMapping = !columnMapping.empty() || !rowMapping.empty();
1992
1993 QItemSelection select;
1994 QItemSelection deselect;
1995
1996 const auto mergeInto =
1997 [this, qaim, hasMapping](QItemSelection &selection,
1998 const QModelIndex &startIndex, const QModelIndex &endIndex)
1999 {
2000 if (hasMapping) {
2001 for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) {
2002 const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()),
2003 logicalColumnIndex(modelIndex.column()));
2004 selection.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select);
2005 }
2006 } else {
2007 selection.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
2008 }
2009 };
2010
2011 // Select cells inside the new selection rect
2012 {
2013 const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x());
2014 const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width());
2015 mergeInto(select, startIndex, endIndex);
2016 }
2017
2018 // Unselect cells in the new minus old rects
2019 if (oldRect.x() < newRect.x()) {
2020 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
2021 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1);
2022 mergeInto(deselect, startIndex, endIndex);
2023 } else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) {
2024 const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1);
2025 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
2026 mergeInto(deselect, startIndex, endIndex);
2027 }
2028
2029 if (oldRect.y() < newRect.y()) {
2030 const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x());
2031 const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width());
2032 mergeInto(deselect, startIndex, endIndex);
2033 } else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) {
2034 const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x());
2035 const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width());
2036 mergeInto(deselect, startIndex, endIndex);
2037 }
2038
2039 if (selectionFlag == QItemSelectionModel::Select) {
2040 // Don't clear the selection that existed before the user started a new selection block
2041 deselect.merge(existingSelection, QItemSelectionModel::Deselect);
2042 selectionModel->select(deselect, QItemSelectionModel::Deselect);
2043 selectionModel->select(select, QItemSelectionModel::Select);
2044 } else if (selectionFlag == QItemSelectionModel::Deselect){
2045 QItemSelection oldSelection = existingSelection;
2046 oldSelection.merge(select, QItemSelectionModel::Deselect);
2047 selectionModel->select(oldSelection, QItemSelectionModel::Select);
2048 selectionModel->select(select, QItemSelectionModel::Deselect);
2049 } else {
2050 Q_UNREACHABLE();
2051 }
2052}
2053
2054void QQuickTableViewPrivate::cancelSelectionTracking()
2055{
2056 // Cancel any ongoing key/mouse aided selection tracking
2057 selectionStartCell = QPoint(-1, -1);
2058 selectionEndCell = QPoint(-1, -1);
2059 existingSelection.clear();
2060 selectionFlag = QItemSelectionModel::NoUpdate;
2061 if (selectableCallbackFunction)
2062 selectableCallbackFunction(QQuickSelectable::CallBackFlag::CancelSelection);
2063}
2064
2065void QQuickTableViewPrivate::clearSelection()
2066{
2067 if (!selectionModel)
2068 return;
2069 QScopedValueRollback callbackGuard(inSelectionModelUpdate, true);
2070 selectionModel->clearSelection();
2071}
2072
2073void QQuickTableViewPrivate::normalizeSelection()
2074{
2075 // Normalize the selection if necessary, so that the start cell is to the left
2076 // and above the end cell. This is typically done after a selection drag has
2077 // finished so that the start and end positions up in sync with the handles.
2078 // This will not cause any changes to the selection itself.
2079 if (selectionEndCell.x() < selectionStartCell.x())
2080 std::swap(selectionStartCell.rx(), selectionEndCell.rx());
2081 if (selectionEndCell.y() < selectionStartCell.y())
2082 std::swap(selectionStartCell.ry(), selectionEndCell.ry());
2083}
2084
2085QRectF QQuickTableViewPrivate::selectionRectangle() const
2086{
2087 Q_Q(const QQuickTableView);
2088
2089 if (loadedColumns.isEmpty() || loadedRows.isEmpty())
2090 return QRectF();
2091
2092 QPoint topLeftCell = selectionStartCell;
2093 QPoint bottomRightCell = selectionEndCell;
2094 if (bottomRightCell.x() < topLeftCell.x())
2095 std::swap(topLeftCell.rx(), bottomRightCell.rx());
2096 if (selectionEndCell.y() < topLeftCell.y())
2097 std::swap(topLeftCell.ry(), bottomRightCell.ry());
2098
2099 const QPoint leftCell(topLeftCell.x(), topRow());
2100 const QPoint topCell(leftColumn(), topLeftCell.y());
2101 const QPoint rightCell(bottomRightCell.x(), topRow());
2102 const QPoint bottomCell(leftColumn(), bottomRightCell.y());
2103
2104 // If the corner cells of the selection are loaded, we can position the
2105 // selection rectangle at its exact location. Otherwise we extend it out
2106 // to the edges of the content item. This is not ideal, but the best we
2107 // can do while the location of the corner cells are unknown.
2108 // This will at least move the selection handles (and other overlay) out
2109 // of the viewport until the affected cells are eventually loaded.
2110 int left = 0;
2111 int top = 0;
2112 int right = 0;
2113 int bottom = 0;
2114
2115 if (loadedItems.contains(modelIndexAtCell(leftCell)))
2116 left = loadedTableItem(leftCell)->geometry().left();
2117 else if (leftCell.x() > rightColumn())
2118 left = q->contentWidth();
2119
2120 if (loadedItems.contains(modelIndexAtCell(topCell)))
2121 top = loadedTableItem(topCell)->geometry().top();
2122 else if (topCell.y() > bottomRow())
2123 top = q->contentHeight();
2124
2125 if (loadedItems.contains(modelIndexAtCell(rightCell)))
2126 right = loadedTableItem(rightCell)->geometry().right();
2127 else if (rightCell.x() > rightColumn())
2128 right = q->contentWidth();
2129
2130 if (loadedItems.contains(modelIndexAtCell(bottomCell)))
2131 bottom = loadedTableItem(bottomCell)->geometry().bottom();
2132 else if (bottomCell.y() > bottomRow())
2133 bottom = q->contentHeight();
2134
2135 return QRectF(left, top, right - left, bottom - top);
2136}
2137
2138QRect QQuickTableViewPrivate::selection() const
2139{
2140 const qreal w = selectionEndCell.x() - selectionStartCell.x();
2141 const qreal h = selectionEndCell.y() - selectionStartCell.y();
2142 return QRect(selectionStartCell.x(), selectionStartCell.y(), w, h);
2143}
2144
2145QSizeF QQuickTableViewPrivate::scrollTowardsPoint(const QPointF &pos, const QSizeF &step)
2146{
2147 Q_Q(QQuickTableView);
2148
2149 if (loadedItems.isEmpty())
2150 return QSizeF();
2151
2152 // Scroll the content item towards pos.
2153 // Return the distance in pixels from the edge of the viewport to pos.
2154 // The caller will typically use this information to throttle the scrolling speed.
2155 // If pos is already inside the viewport, or the viewport is scrolled all the way
2156 // to the end, we return 0.
2157 QSizeF dist(0, 0);
2158
2159 const bool outsideLeft = pos.x() < viewportRect.x();
2160 const bool outsideRight = pos.x() >= viewportRect.right() - 1;
2161 const bool outsideTop = pos.y() < viewportRect.y();
2162 const bool outsideBottom = pos.y() >= viewportRect.bottom() - 1;
2163
2164 if (outsideLeft) {
2165 const bool firstColumnLoaded = atTableEnd(Qt::LeftEdge);
2166 const qreal remainingDist = viewportRect.left() - loadedTableOuterRect.left();
2167 if (remainingDist > 0 || !firstColumnLoaded) {
2168 qreal stepX = step.width();
2169 if (firstColumnLoaded)
2170 stepX = qMin(stepX, remainingDist);
2171 q->setContentX(q->contentX() - stepX);
2172 dist.setWidth(pos.x() - viewportRect.left() - 1);
2173 }
2174 } else if (outsideRight) {
2175 const bool lastColumnLoaded = atTableEnd(Qt::RightEdge);
2176 const qreal remainingDist = loadedTableOuterRect.right() - viewportRect.right();
2177 if (remainingDist > 0 || !lastColumnLoaded) {
2178 qreal stepX = step.width();
2179 if (lastColumnLoaded)
2180 stepX = qMin(stepX, remainingDist);
2181 q->setContentX(q->contentX() + stepX);
2182 dist.setWidth(pos.x() - viewportRect.right() - 1);
2183 }
2184 }
2185
2186 if (outsideTop) {
2187 const bool firstRowLoaded = atTableEnd(Qt::TopEdge);
2188 const qreal remainingDist = viewportRect.top() - loadedTableOuterRect.top();
2189 if (remainingDist > 0 || !firstRowLoaded) {
2190 qreal stepY = step.height();
2191 if (firstRowLoaded)
2192 stepY = qMin(stepY, remainingDist);
2193 q->setContentY(q->contentY() - stepY);
2194 dist.setHeight(pos.y() - viewportRect.top() - 1);
2195 }
2196 } else if (outsideBottom) {
2197 const bool lastRowLoaded = atTableEnd(Qt::BottomEdge);
2198 const qreal remainingDist = loadedTableOuterRect.bottom() - viewportRect.bottom();
2199 if (remainingDist > 0 || !lastRowLoaded) {
2200 qreal stepY = step.height();
2201 if (lastRowLoaded)
2202 stepY = qMin(stepY, remainingDist);
2203 q->setContentY(q->contentY() + stepY);
2204 dist.setHeight(pos.y() - viewportRect.bottom() - 1);
2205 }
2206 }
2207
2208 return dist;
2209}
2210
2211void QQuickTableViewPrivate::setCallback(std::function<void (CallBackFlag)> func)
2212{
2213 selectableCallbackFunction = func;
2214}
2215
2216QQuickTableViewAttached *QQuickTableViewPrivate::getAttachedObject(const QObject *object) const
2217{
2218 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickTableView>(object, false);
2219 return static_cast<QQuickTableViewAttached *>(attachedObject);
2220}
2221
2222QQuickTableViewAttached::QQuickTableViewAttached(QObject *parent)
2223 : QObject(parent)
2224{
2225 QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent);
2226 if (!parentItem)
2227 return;
2228
2229 // For a normal delegate, the 3rd parent should be the view (1:delegate, 2:contentItem,
2230 // 3:TableView). For an edit delegate, the 4th. We don't search further than that, as
2231 // you're not supposed to use attached objects on any other descendant.
2232 for (int i = 0; i < 3; ++i) {
2233 parentItem = parentItem->parentItem();
2234 if (!parentItem)
2235 return;
2236 if (auto tableView = qobject_cast<QQuickTableView *>(parentItem)) {
2237 setView(tableView);
2238 return;
2239 }
2240 }
2241}
2242
2243int QQuickTableViewPrivate::modelIndexAtCell(const QPoint &cell) const
2244{
2245 // QQmlTableInstanceModel expects index to be in column-major
2246 // order. This means that if the view is transposed (with a flipped
2247 // width and height), we need to calculate it in row-major instead.
2248 if (isTransposed) {
2249 int availableColumns = tableSize.width();
2250 return (cell.y() * availableColumns) + cell.x();
2251 } else {
2252 int availableRows = tableSize.height();
2253 return (cell.x() * availableRows) + cell.y();
2254 }
2255}
2256
2257QPoint QQuickTableViewPrivate::cellAtModelIndex(int modelIndex) const
2258{
2259 // QQmlTableInstanceModel expects index to be in column-major
2260 // order. This means that if the view is transposed (with a flipped
2261 // width and height), we need to calculate it in row-major instead.
2262 if (isTransposed) {
2263 int availableColumns = tableSize.width();
2264 int row = int(modelIndex / availableColumns);
2265 int column = modelIndex % availableColumns;
2266 return QPoint(column, row);
2267 } else {
2268 int availableRows = tableSize.height();
2269 int column = int(modelIndex / availableRows);
2270 int row = modelIndex % availableRows;
2271 return QPoint(column, row);
2272 }
2273}
2274
2275int QQuickTableViewPrivate::modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex) const
2276{
2277 // Convert QModelIndex to cell index. A cell index is just an
2278 // integer representation of a cell instead of using a QPoint.
2279 const QPoint cell = q_func()->cellAtIndex(modelIndex);
2280 if (!cellIsValid(cell))
2281 return -1;
2282 return modelIndexAtCell(visualIndex ? cell : QPoint(modelIndex.column(), modelIndex.row()));
2283}
2284
2285int QQuickTableViewPrivate::edgeToArrayIndex(Qt::Edge edge) const
2286{
2287 return int(log2(float(edge)));
2288}
2289
2290void QQuickTableViewPrivate::clearEdgeSizeCache()
2291{
2292 cachedColumnWidth.startIndex = kEdgeIndexNotSet;
2293 cachedRowHeight.startIndex = kEdgeIndexNotSet;
2294
2295 for (Qt::Edge edge : allTableEdges)
2296 cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)].startIndex = kEdgeIndexNotSet;
2297}
2298
2299int QQuickTableViewPrivate::nextVisibleEdgeIndexAroundLoadedTable(Qt::Edge edge) const
2300{
2301 // Find the next column (or row) around the loaded table that is
2302 // visible, and should be loaded next if the content item moves.
2303 int startIndex = -1;
2304 switch (edge) {
2305 case Qt::LeftEdge: startIndex = leftColumn() - 1; break;
2306 case Qt::RightEdge: startIndex = rightColumn() + 1; break;
2307 case Qt::TopEdge: startIndex = topRow() - 1; break;
2308 case Qt::BottomEdge: startIndex = bottomRow() + 1; break;
2309 }
2310
2311 return nextVisibleEdgeIndex(edge, startIndex);
2312}
2313
2314int QQuickTableViewPrivate::nextVisibleEdgeIndex(Qt::Edge edge, int startIndex) const
2315{
2316 // First check if we have already searched for the first visible index
2317 // after the given startIndex recently, and if so, return the cached result.
2318 // The cached result is valid if startIndex is inside the range between the
2319 // startIndex and the first visible index found after it.
2320 auto &cachedResult = cachedNextVisibleEdgeIndex[edgeToArrayIndex(edge)];
2321 if (cachedResult.containsIndex(edge, startIndex))
2322 return cachedResult.endIndex;
2323
2324 // Search for the first column (or row) in the direction of edge that is
2325 // visible, starting from the given column (startIndex).
2326 int foundIndex = kEdgeIndexNotSet;
2327 int testIndex = startIndex;
2328
2329 switch (edge) {
2330 case Qt::LeftEdge: {
2331 forever {
2332 if (testIndex < 0) {
2333 foundIndex = kEdgeIndexAtEnd;
2334 break;
2335 }
2336
2337 if (!isColumnHidden(testIndex)) {
2338 foundIndex = testIndex;
2339 break;
2340 }
2341
2342 --testIndex;
2343 }
2344 break; }
2345 case Qt::RightEdge: {
2346 forever {
2347 if (testIndex > tableSize.width() - 1) {
2348 foundIndex = kEdgeIndexAtEnd;
2349 break;
2350 }
2351
2352 if (!isColumnHidden(testIndex)) {
2353 foundIndex = testIndex;
2354 break;
2355 }
2356
2357 ++testIndex;
2358 }
2359 break; }
2360 case Qt::TopEdge: {
2361 forever {
2362 if (testIndex < 0) {
2363 foundIndex = kEdgeIndexAtEnd;
2364 break;
2365 }
2366
2367 if (!isRowHidden(testIndex)) {
2368 foundIndex = testIndex;
2369 break;
2370 }
2371
2372 --testIndex;
2373 }
2374 break; }
2375 case Qt::BottomEdge: {
2376 forever {
2377 if (testIndex > tableSize.height() - 1) {
2378 foundIndex = kEdgeIndexAtEnd;
2379 break;
2380 }
2381
2382 if (!isRowHidden(testIndex)) {
2383 foundIndex = testIndex;
2384 break;
2385 }
2386
2387 ++testIndex;
2388 }
2389 break; }
2390 }
2391
2392 cachedResult.startIndex = startIndex;
2393 cachedResult.endIndex = foundIndex;
2394 return foundIndex;
2395}
2396
2397void QQuickTableViewPrivate::updateContentWidth()
2398{
2399 // Note that we actually never really know what the content size / size of the full table will
2400 // be. Even if e.g spacing changes, and we normally would assume that the size of the table
2401 // would increase accordingly, the model might also at some point have removed/hidden/resized
2402 // rows/columns outside the viewport. This would also affect the size, but since we don't load
2403 // rows or columns outside the viewport, this information is ignored. And even if we did, we
2404 // might also have been fast-flicked to a new location at some point, and started a new rebuild
2405 // there based on a new guesstimated top-left cell. So the calculated content size should always
2406 // be understood as a guesstimate, which sometimes can be really off (as a tradeoff for performance).
2407 // When this is not acceptable, the user can always set a custom content size explicitly.
2408 Q_Q(QQuickTableView);
2409
2410 if (syncHorizontally) {
2411 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
2412 q->QQuickFlickable::setContentWidth(syncView->contentWidth());
2413 return;
2414 }
2415
2416 if (explicitContentWidth.isValid()) {
2417 // Don't calculate contentWidth when it
2418 // was set explicitly by the application.
2419 return;
2420 }
2421
2422 if (loadedItems.isEmpty()) {
2423 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
2424 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2425 q->QQuickFlickable::setContentWidth(kDefaultColumnWidth);
2426 else
2427 q->QQuickFlickable::setContentWidth(0);
2428 return;
2429 }
2430
2431 const int nextColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge);
2432 const int columnsRemaining = nextColumn == kEdgeIndexAtEnd ? 0 : tableSize.width() - nextColumn;
2433 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2434 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2435 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2436 const qreal estimatedWidth = loadedTableOuterRect.right() + estimatedRemainingWidth;
2437
2438 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
2439 q->QQuickFlickable::setContentWidth(estimatedWidth);
2440}
2441
2442void QQuickTableViewPrivate::updateContentHeight()
2443{
2444 Q_Q(QQuickTableView);
2445
2446 if (syncVertically) {
2447 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
2448 q->QQuickFlickable::setContentHeight(syncView->contentHeight());
2449 return;
2450 }
2451
2452 if (explicitContentHeight.isValid()) {
2453 // Don't calculate contentHeight when it
2454 // was set explicitly by the application.
2455 return;
2456 }
2457
2458 if (loadedItems.isEmpty()) {
2459 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
2460 if (model && model->count() > 0 && tableModel && tableModel->delegate())
2461 q->QQuickFlickable::setContentHeight(kDefaultRowHeight);
2462 else
2463 q->QQuickFlickable::setContentHeight(0);
2464 return;
2465 }
2466
2467 const int nextRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge);
2468 const int rowsRemaining = nextRow == kEdgeIndexAtEnd ? 0 : tableSize.height() - nextRow;
2469 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2470 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2471 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2472 const qreal estimatedHeight = loadedTableOuterRect.bottom() + estimatedRemainingHeight;
2473
2474 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
2475 q->QQuickFlickable::setContentHeight(estimatedHeight);
2476}
2477
2478void QQuickTableViewPrivate::updateExtents()
2479{
2480 // When rows or columns outside the viewport are removed or added, or a rebuild
2481 // forces us to guesstimate a new top-left, the edges of the table might end up
2482 // out of sync with the edges of the content view. We detect this situation here, and
2483 // move the origin to ensure that there will never be gaps at the end of the table.
2484 // Normally we detect that the size of the whole table is not going to be equal to the
2485 // size of the content view already when we load the last row/column, and especially
2486 // before it's flicked completely inside the viewport. For those cases we simply adjust
2487 // the origin/endExtent, to give a smooth flicking experience.
2488 // But if flicking fast (e.g with a scrollbar), it can happen that the viewport ends up
2489 // outside the end of the table in just one viewport update. To avoid a "blink" in the
2490 // viewport when that happens, we "move" the loaded table into the viewport to cover it.
2491 Q_Q(QQuickTableView);
2492
2493 bool tableMovedHorizontally = false;
2494 bool tableMovedVertically = false;
2495
2496 const int nextLeftColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::LeftEdge);
2497 const int nextRightColumn = nextVisibleEdgeIndexAroundLoadedTable(Qt::RightEdge);
2498 const int nextTopRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::TopEdge);
2499 const int nextBottomRow = nextVisibleEdgeIndexAroundLoadedTable(Qt::BottomEdge);
2500
2501 QPointF prevOrigin = origin;
2502 QSizeF prevEndExtent = endExtent;
2503
2504 if (syncHorizontally) {
2505 const auto syncView_d = syncView->d_func();
2506 origin.rx() = syncView_d->origin.x();
2507 endExtent.rwidth() = syncView_d->endExtent.width();
2508 } else if (nextLeftColumn == kEdgeIndexAtEnd) {
2509 // There are no more columns to load on the left side of the table.
2510 // In that case, we ensure that the origin match the beginning of the table.
2511 if (loadedTableOuterRect.left() > viewportRect.left()) {
2512 // We have a blank area at the left end of the viewport. In that case we don't have time to
2513 // wait for the viewport to move (after changing origin), since that will take an extra
2514 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2515 // us overshooting, we brute force the loaded table inside the already existing viewport.
2516 if (loadedTableOuterRect.left() > origin.x()) {
2517 const qreal diff = loadedTableOuterRect.left() - origin.x();
2518 loadedTableOuterRect.moveLeft(loadedTableOuterRect.left() - diff);
2519 loadedTableInnerRect.moveLeft(loadedTableInnerRect.left() - diff);
2520 tableMovedHorizontally = true;
2521 }
2522 }
2523 origin.rx() = loadedTableOuterRect.left();
2524 } else if (loadedTableOuterRect.left() <= origin.x() + cellSpacing.width()) {
2525 // The table rect is at the origin, or outside, but we still have more
2526 // visible columns to the left. So we try to guesstimate how much space
2527 // the rest of the columns will occupy, and move the origin accordingly.
2528 const int columnsRemaining = nextLeftColumn + 1;
2529 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2530 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2531 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2532 origin.rx() = loadedTableOuterRect.left() - estimatedRemainingWidth;
2533 } else if (nextRightColumn == kEdgeIndexAtEnd) {
2534 // There are no more columns to load on the right side of the table.
2535 // In that case, we ensure that the end of the content view match the end of the table.
2536 if (loadedTableOuterRect.right() < viewportRect.right()) {
2537 // We have a blank area at the right end of the viewport. In that case we don't have time to
2538 // wait for the viewport to move (after changing endExtent), since that will take an extra
2539 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2540 // us overshooting, we brute force the loaded table inside the already existing viewport.
2541 const qreal w = qMin(viewportRect.right(), q->contentWidth() + endExtent.width());
2542 if (loadedTableOuterRect.right() < w) {
2543 const qreal diff = loadedTableOuterRect.right() - w;
2544 loadedTableOuterRect.moveRight(loadedTableOuterRect.right() - diff);
2545 loadedTableInnerRect.moveRight(loadedTableInnerRect.right() - diff);
2546 tableMovedHorizontally = true;
2547 }
2548 }
2549 endExtent.rwidth() = loadedTableOuterRect.right() - q->contentWidth();
2550 } else if (loadedTableOuterRect.right() >= q->contentWidth() + endExtent.width() - cellSpacing.width()) {
2551 // The right-most column is outside the end of the content view, and we
2552 // still have more visible columns in the model. This can happen if the application
2553 // has set a fixed content width.
2554 const int columnsRemaining = tableSize.width() - nextRightColumn;
2555 const qreal remainingColumnWidths = columnsRemaining * averageEdgeSize.width();
2556 const qreal remainingSpacing = columnsRemaining * cellSpacing.width();
2557 const qreal estimatedRemainingWidth = remainingColumnWidths + remainingSpacing;
2558 const qreal pixelsOutsideContentWidth = loadedTableOuterRect.right() - q->contentWidth();
2559 endExtent.rwidth() = pixelsOutsideContentWidth + estimatedRemainingWidth;
2560 }
2561
2562 if (syncVertically) {
2563 const auto syncView_d = syncView->d_func();
2564 origin.ry() = syncView_d->origin.y();
2565 endExtent.rheight() = syncView_d->endExtent.height();
2566 } else if (nextTopRow == kEdgeIndexAtEnd) {
2567 // There are no more rows to load on the top side of the table.
2568 // In that case, we ensure that the origin match the beginning of the table.
2569 if (loadedTableOuterRect.top() > viewportRect.top()) {
2570 // We have a blank area at the top of the viewport. In that case we don't have time to
2571 // wait for the viewport to move (after changing origin), since that will take an extra
2572 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2573 // us overshooting, we brute force the loaded table inside the already existing viewport.
2574 if (loadedTableOuterRect.top() > origin.y()) {
2575 const qreal diff = loadedTableOuterRect.top() - origin.y();
2576 loadedTableOuterRect.moveTop(loadedTableOuterRect.top() - diff);
2577 loadedTableInnerRect.moveTop(loadedTableInnerRect.top() - diff);
2578 tableMovedVertically = true;
2579 }
2580 }
2581 origin.ry() = loadedTableOuterRect.top();
2582 } else if (loadedTableOuterRect.top() <= origin.y() + cellSpacing.height()) {
2583 // The table rect is at the origin, or outside, but we still have more
2584 // visible rows at the top. So we try to guesstimate how much space
2585 // the rest of the rows will occupy, and move the origin accordingly.
2586 const int rowsRemaining = nextTopRow + 1;
2587 const qreal remainingRowHeights = rowsRemaining * averageEdgeSize.height();
2588 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2589 const qreal estimatedRemainingHeight = remainingRowHeights + remainingSpacing;
2590 origin.ry() = loadedTableOuterRect.top() - estimatedRemainingHeight;
2591 } else if (nextBottomRow == kEdgeIndexAtEnd) {
2592 // There are no more rows to load on the bottom side of the table.
2593 // In that case, we ensure that the end of the content view match the end of the table.
2594 if (loadedTableOuterRect.bottom() < viewportRect.bottom()) {
2595 // We have a blank area at the bottom of the viewport. In that case we don't have time to
2596 // wait for the viewport to move (after changing endExtent), since that will take an extra
2597 // update cycle, which will be visible as a blink. Instead, unless the blank spot is just
2598 // us overshooting, we brute force the loaded table inside the already existing viewport.
2599 const qreal h = qMin(viewportRect.bottom(), q->contentHeight() + endExtent.height());
2600 if (loadedTableOuterRect.bottom() < h) {
2601 const qreal diff = loadedTableOuterRect.bottom() - h;
2602 loadedTableOuterRect.moveBottom(loadedTableOuterRect.bottom() - diff);
2603 loadedTableInnerRect.moveBottom(loadedTableInnerRect.bottom() - diff);
2604 tableMovedVertically = true;
2605 }
2606 }
2607 endExtent.rheight() = loadedTableOuterRect.bottom() - q->contentHeight();
2608 } else if (loadedTableOuterRect.bottom() >= q->contentHeight() + endExtent.height() - cellSpacing.height()) {
2609 // The bottom-most row is outside the end of the content view, and we
2610 // still have more visible rows in the model. This can happen if the application
2611 // has set a fixed content height.
2612 const int rowsRemaining = tableSize.height() - nextBottomRow;
2613 const qreal remainingRowHeigts = rowsRemaining * averageEdgeSize.height();
2614 const qreal remainingSpacing = rowsRemaining * cellSpacing.height();
2615 const qreal estimatedRemainingHeight = remainingRowHeigts + remainingSpacing;
2616 const qreal pixelsOutsideContentHeight = loadedTableOuterRect.bottom() - q->contentHeight();
2617 endExtent.rheight() = pixelsOutsideContentHeight + estimatedRemainingHeight;
2618 }
2619
2620 if (tableMovedHorizontally || tableMovedVertically) {
2621 qCDebug(lcTableViewDelegateLifecycle) << "move table to" << loadedTableOuterRect;
2622
2623 // relayoutTableItems() will take care of moving the existing
2624 // delegate items into the new loadedTableOuterRect.
2625 relayoutTableItems();
2626
2627 // Inform the sync children that they need to rebuild to stay in sync
2628 for (auto syncChild : std::as_const(syncChildren)) {
2629 auto syncChild_d = syncChild->d_func();
2630 syncChild_d->scheduledRebuildOptions |= RebuildOption::ViewportOnly;
2631 if (tableMovedHorizontally)
2632 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn;
2633 if (tableMovedVertically)
2634 syncChild_d->scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow;
2635 }
2636 }
2637
2638 if (prevOrigin != origin || prevEndExtent != endExtent) {
2639 if (prevOrigin != origin)
2640 qCDebug(lcTableViewDelegateLifecycle) << "move origin to:" << origin;
2641 if (prevEndExtent != endExtent)
2642 qCDebug(lcTableViewDelegateLifecycle) << "move endExtent to:" << endExtent;
2643 // updateBeginningEnd() will let the new extents take effect. This will also change the
2644 // visualArea of the flickable, which again will cause any attached scrollbars to adjust
2645 // the position of the handle. Note the latter will cause the viewport to move once more.
2646 hData.markExtentsDirty();
2647 vData.markExtentsDirty();
2648 updateBeginningEnd();
2649 if (!q->isMoving()) {
2650 // When we adjust the extents, the viewport can sometimes be left suspended in an
2651 // overshooted state. It will bounce back again once the user clicks inside the
2652 // viewport. But this comes across as a bug, so returnToBounds explicitly.
2653 q->returnToBounds();
2654 }
2655 }
2656}
2657
2658void QQuickTableViewPrivate::updateAverageColumnWidth()
2659{
2660 if (explicitContentWidth.isValid()) {
2661 const qreal accColumnSpacing = (tableSize.width() - 1) * cellSpacing.width();
2662 averageEdgeSize.setWidth((explicitContentWidth - accColumnSpacing) / tableSize.width());
2663 } else {
2664 const qreal accColumnSpacing = (loadedColumns.count() - 1) * cellSpacing.width();
2665 averageEdgeSize.setWidth((loadedTableOuterRect.width() - accColumnSpacing) / loadedColumns.count());
2666 }
2667}
2668
2669void QQuickTableViewPrivate::updateAverageRowHeight()
2670{
2671 if (explicitContentHeight.isValid()) {
2672 const qreal accRowSpacing = (tableSize.height() - 1) * cellSpacing.height();
2673 averageEdgeSize.setHeight((explicitContentHeight - accRowSpacing) / tableSize.height());
2674 } else {
2675 const qreal accRowSpacing = (loadedRows.count() - 1) * cellSpacing.height();
2676 averageEdgeSize.setHeight((loadedTableOuterRect.height() - accRowSpacing) / loadedRows.count());
2677 }
2678}
2679
2680void QQuickTableViewPrivate::syncLoadedTableRectFromLoadedTable()
2681{
2682 const QPoint topLeft = QPoint(leftColumn(), topRow());
2683 const QPoint bottomRight = QPoint(rightColumn(), bottomRow());
2684 QRectF topLeftRect = loadedTableItem(topLeft)->geometry();
2685 QRectF bottomRightRect = loadedTableItem(bottomRight)->geometry();
2686 loadedTableOuterRect = QRectF(topLeftRect.topLeft(), bottomRightRect.bottomRight());
2687 loadedTableInnerRect = QRectF(topLeftRect.bottomRight(), bottomRightRect.topLeft());
2688}
2689
2690QQuickTableViewPrivate::RebuildOptions QQuickTableViewPrivate::checkForVisibilityChanges()
2691{
2692 // This function will check if there are any visibility changes among
2693 // the _already loaded_ rows and columns. Note that there can be rows
2694 // and columns to the bottom or right that was not loaded, but should
2695 // now become visible (in case there is free space around the table).
2696 if (loadedItems.isEmpty()) {
2697 // Report no changes
2698 return RebuildOption::None;
2699 }
2700
2701 RebuildOptions rebuildOptions = RebuildOption::None;
2702
2703 if (loadedTableOuterRect.x() == origin.x() && leftColumn() != 0) {
2704 // Since the left column is at the origin of the viewport, but still not the first
2705 // column in the model, we need to calculate a new left column since there might be
2706 // columns in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2707 rebuildOptions.setFlag(RebuildOption::ViewportOnly);
2708 rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftColumn);
2709 } else {
2710 // Go through all loaded columns from first to last, find the columns that used
2711 // to be hidden and not loaded, and check if they should become visible
2712 // (and vice versa). If there is a change, we need to rebuild.
2713 for (int column = leftColumn(); column <= rightColumn(); ++column) {
2714 const bool wasVisibleFromBefore = loadedColumns.contains(column);
2715 const bool isVisibleNow = !qFuzzyIsNull(getColumnWidth(column));
2716 if (wasVisibleFromBefore == isVisibleNow)
2717 continue;
2718
2719 // A column changed visibility. This means that it should
2720 // either be loaded or unloaded. So we need a rebuild.
2721 qCDebug(lcTableViewDelegateLifecycle) << "Column" << column << "changed visibility to" << isVisibleNow;
2722 rebuildOptions.setFlag(RebuildOption::ViewportOnly);
2723 if (column == leftColumn()) {
2724 // The first loaded column should now be hidden. This means that we
2725 // need to calculate which column should now be first instead.
2726 rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftColumn);
2727 }
2728 break;
2729 }
2730 }
2731
2732 if (loadedTableOuterRect.y() == origin.y() && topRow() != 0) {
2733 // Since the top row is at the origin of the viewport, but still not the first
2734 // row in the model, we need to calculate a new top row since there might be
2735 // rows in front of it that used to be hidden, but should now be visible (QTBUG-93264).
2736 rebuildOptions.setFlag(RebuildOption::ViewportOnly);
2737 rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftRow);
2738 } else {
2739 // Go through all loaded rows from first to last, find the rows that used
2740 // to be hidden and not loaded, and check if they should become visible
2741 // (and vice versa). If there is a change, we need to rebuild.
2742 for (int row = topRow(); row <= bottomRow(); ++row) {
2743 const bool wasVisibleFromBefore = loadedRows.contains(row);
2744 const bool isVisibleNow = !qFuzzyIsNull(getRowHeight(row));
2745 if (wasVisibleFromBefore == isVisibleNow)
2746 continue;
2747
2748 // A row changed visibility. This means that it should
2749 // either be loaded or unloaded. So we need a rebuild.
2750 qCDebug(lcTableViewDelegateLifecycle) << "Row" << row << "changed visibility to" << isVisibleNow;
2751 rebuildOptions.setFlag(RebuildOption::ViewportOnly);
2752 if (row == topRow())
2753 rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftRow);
2754 break;
2755 }
2756 }
2757
2758 return rebuildOptions;
2759}
2760
2761void QQuickTableViewPrivate::forceLayout(bool immediate)
2762{
2763 clearEdgeSizeCache();
2764 RebuildOptions rebuildOptions = RebuildOption::None;
2765
2766 const QSize actualTableSize = calculateTableSize();
2767 if (tableSize != actualTableSize) {
2768 // The table size will have changed if forceLayout is called after
2769 // the row count in the model has changed, but before we received
2770 // a rowsInsertedCallback about it (and vice versa for columns).
2771 rebuildOptions |= RebuildOption::ViewportOnly;
2772 }
2773
2774 // Resizing a column (or row) can result in the table going from being
2775 // e.g completely inside the viewport to go outside. And in the latter
2776 // case, the user needs to be able to scroll the viewport, also if
2777 // flags such as Flickable.StopAtBounds is in use. So we need to
2778 // update contentWidth/Height to support that case.
2779 rebuildOptions |= RebuildOption::LayoutOnly
2780 | RebuildOption::CalculateNewContentWidth
2781 | RebuildOption::CalculateNewContentHeight
2782 | checkForVisibilityChanges();
2783
2784 scheduleRebuildTable(rebuildOptions);
2785
2786 if (immediate) {
2787 auto rootView = rootSyncView();
2788 const bool updated = rootView->d_func()->updateTableRecursive();
2789 if (!updated) {
2790 qWarning() << "TableView::forceLayout(): Cannot do an immediate re-layout during an ongoing layout!";
2791 rootView->polish();
2792 }
2793 }
2794}
2795
2796void QQuickTableViewPrivate::syncLoadedTableFromLoadRequest()
2797{
2798 if (loadRequest.edge() == Qt::Edge(0)) {
2799 // No edge means we're loading the top-left item
2800 loadedColumns.insert(loadRequest.column());
2801 loadedRows.insert(loadRequest.row());
2802 return;
2803 }
2804
2805 switch (loadRequest.edge()) {
2806 case Qt::LeftEdge:
2807 case Qt::RightEdge:
2808 loadedColumns.insert(loadRequest.column());
2809 break;
2810 case Qt::TopEdge:
2811 case Qt::BottomEdge:
2812 loadedRows.insert(loadRequest.row());
2813 break;
2814 }
2815}
2816
2817FxTableItem *QQuickTableViewPrivate::loadedTableItem(const QPoint &cell) const
2818{
2819 const int modelIndex = modelIndexAtCell(cell);
2820 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2821 return loadedItems.value(modelIndex);
2822}
2823
2824FxTableItem *QQuickTableViewPrivate::createFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
2825{
2826 Q_Q(QQuickTableView);
2827
2828 bool ownItem = false;
2829
2830 int modelIndex = modelIndexAtCell(isTransposed ? QPoint(logicalRowIndex(cell.x()), logicalColumnIndex(cell.y())) :
2831 QPoint(logicalColumnIndex(cell.x()), logicalRowIndex(cell.y())));
2832
2833 QObject* object = model->object(modelIndex, incubationMode);
2834 if (!object) {
2835 if (model->incubationStatus(modelIndex) == QQmlIncubator::Loading) {
2836 // Item is incubating. Return nullptr for now, and let the table call this
2837 // function again once we get a callback to itemCreatedCallback().
2838 return nullptr;
2839 }
2840
2841 qWarning() << "TableView: failed loading index:" << modelIndex;
2842 object = new QQuickItem();
2843 ownItem = true;
2844 }
2845
2846 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
2847 if (!item) {
2848 // The model could not provide an QQuickItem for the
2849 // given index, so we create a placeholder instead.
2850 qWarning() << "TableView: delegate is not an item:" << modelIndex;
2851 model->release(object);
2852 item = new QQuickItem();
2853 ownItem = true;
2854 } else {
2855 QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors;
2856 if (anchors && anchors->activeDirections())
2857 qmlWarning(item) << "TableView: detected anchors on delegate with index: " << modelIndex
2858 << ". Use implicitWidth and implicitHeight instead.";
2859 }
2860
2861 if (ownItem) {
2862 // Parent item is normally set early on from initItemCallback (to
2863 // allow bindings to the parent property). But if we created the item
2864 // within this function, we need to set it explicit.
2865 item->setImplicitWidth(kDefaultColumnWidth);
2866 item->setImplicitHeight(kDefaultRowHeight);
2867 item->setParentItem(q->contentItem());
2868 }
2869 Q_TABLEVIEW_ASSERT(item->parentItem() == q->contentItem(), item->parentItem());
2870
2871 FxTableItem *fxTableItem = new FxTableItem(item, q, ownItem);
2872 fxTableItem->setVisible(false);
2873 fxTableItem->cell = cell;
2874 fxTableItem->index = modelIndex;
2875 return fxTableItem;
2876}
2877
2878FxTableItem *QQuickTableViewPrivate::loadFxTableItem(const QPoint &cell, QQmlIncubator::IncubationMode incubationMode)
2879{
2880#ifdef QT_DEBUG
2881 // Since TableView needs to work flawlessly when e.g incubating inside an async
2882 // loader, being able to override all loading to async while debugging can be helpful.
2883 static const bool forcedAsync = forcedIncubationMode == QLatin1String("async");
2884 if (forcedAsync)
2885 incubationMode = QQmlIncubator::Asynchronous;
2886#endif
2887
2888 // Note that even if incubation mode is asynchronous, the item might
2889 // be ready immediately since the model has a cache of items.
2890 QScopedValueRollback guard(blockItemCreatedCallback, true);
2891 auto item = createFxTableItem(cell, incubationMode);
2892 qCDebug(lcTableViewDelegateLifecycle) << cell << "ready?" << bool(item);
2893 return item;
2894}
2895
2896void QQuickTableViewPrivate::releaseLoadedItems(QQmlTableInstanceModel::ReusableFlag reusableFlag) {
2897 // Make a copy and clear the list of items first to avoid destroyed
2898 // items being accessed during the loop (QTBUG-61294)
2899 auto const tmpList = loadedItems;
2900 loadedItems.clear();
2901 for (FxTableItem *item : tmpList)
2902 releaseItem(item, reusableFlag);
2903}
2904
2905void QQuickTableViewPrivate::releaseItem(FxTableItem *fxTableItem, QQmlTableInstanceModel::ReusableFlag reusableFlag)
2906{
2907 Q_Q(QQuickTableView);
2908 // Note that fxTableItem->item might already have been destroyed, in case
2909 // the item is owned by the QML context rather than the model (e.g ObjectModel etc).
2910 auto item = fxTableItem->item;
2911
2912 if (fxTableItem->ownItem) {
2913 Q_TABLEVIEW_ASSERT(item, fxTableItem->index);
2914 delete item;
2915 } else if (item) {
2916 auto releaseFlag = model->release(item, reusableFlag);
2917 if (releaseFlag == QQmlInstanceModel::Pooled) {
2918 fxTableItem->setVisible(false);
2919
2920 // If the item (or a descendant) has focus, remove it, so
2921 // that the item doesn't enter with focus when it's reused.
2922 if (QQuickWindow *window = item->window()) {
2923 const auto focusItem = qobject_cast<QQuickItem *>(window->focusObject());
2924 if (focusItem) {
2925 const bool hasFocus = item == focusItem || item->isAncestorOf(focusItem);
2926 if (hasFocus) {
2927 const auto focusChild = QQuickItemPrivate::get(q)->subFocusItem;
2928 deliveryAgentPrivate()->clearFocusInScope(q, focusChild, Qt::OtherFocusReason);
2929 }
2930 }
2931 }
2932 }
2933 }
2934
2935 delete fxTableItem;
2936}
2937
2938void QQuickTableViewPrivate::unloadItem(const QPoint &cell)
2939{
2940 const int modelIndex = modelIndexAtCell(cell);
2941 Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell);
2942 releaseItem(loadedItems.take(modelIndex), reusableFlag);
2943}
2944
2945bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
2946{
2947 switch (tableEdge) {
2948 case Qt::LeftEdge:
2949 return loadedTableOuterRect.left() > fillRect.left() + cellSpacing.width();
2950 case Qt::RightEdge:
2951 return loadedTableOuterRect.right() < fillRect.right() - cellSpacing.width();
2952 case Qt::TopEdge:
2953 return loadedTableOuterRect.top() > fillRect.top() + cellSpacing.height();
2954 case Qt::BottomEdge:
2955 return loadedTableOuterRect.bottom() < fillRect.bottom() - cellSpacing.height();
2956 }
2957
2958 return false;
2959}
2960
2961bool QQuickTableViewPrivate::canUnloadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const
2962{
2963 // Note: if there is only one row or column left, we cannot unload, since
2964 // they are needed as anchor point for further layouting. We also skip
2965 // unloading in the direction we're currently scrolling.
2966
2967 switch (tableEdge) {
2968 case Qt::LeftEdge:
2969 if (loadedColumns.count() <= 1)
2970 return false;
2971 if (positionXAnimation.isRunning()) {
2972 const qreal to = positionXAnimation.to().toFloat();
2973 if (to < viewportRect.x())
2974 return false;
2975 }
2976 return loadedTableInnerRect.left() <= fillRect.left();
2977 case Qt::RightEdge:
2978 if (loadedColumns.count() <= 1)
2979 return false;
2980 if (positionXAnimation.isRunning()) {
2981 const qreal to = positionXAnimation.to().toFloat();
2982 if (to > viewportRect.x())
2983 return false;
2984 }
2985 return loadedTableInnerRect.right() >= fillRect.right();
2986 case Qt::TopEdge:
2987 if (loadedRows.count() <= 1)
2988 return false;
2989 if (positionYAnimation.isRunning()) {
2990 const qreal to = positionYAnimation.to().toFloat();
2991 if (to < viewportRect.y())
2992 return false;
2993 }
2994 return loadedTableInnerRect.top() <= fillRect.top();
2995 case Qt::BottomEdge:
2996 if (loadedRows.count() <= 1)
2997 return false;
2998 if (positionYAnimation.isRunning()) {
2999 const qreal to = positionYAnimation.to().toFloat();
3000 if (to > viewportRect.y())
3001 return false;
3002 }
3003 return loadedTableInnerRect.bottom() >= fillRect.bottom();
3004 }
3005 Q_TABLEVIEW_UNREACHABLE(tableEdge);
3006 return false;
3007}
3008
3009Qt::Edge QQuickTableViewPrivate::nextEdgeToLoad(const QRectF rect)
3010{
3011 for (Qt::Edge edge : allTableEdges) {
3012 if (!canLoadTableEdge(edge, rect))
3013 continue;
3014 const int nextIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
3015 if (nextIndex == kEdgeIndexAtEnd)
3016 continue;
3017 return edge;
3018 }
3019
3020 return Qt::Edge(0);
3021}
3022
3023Qt::Edge QQuickTableViewPrivate::nextEdgeToUnload(const QRectF rect)
3024{
3025 for (Qt::Edge edge : allTableEdges) {
3026 if (canUnloadTableEdge(edge, rect))
3027 return edge;
3028 }
3029 return Qt::Edge(0);
3030}
3031
3032qreal QQuickTableViewPrivate::cellWidth(const QPoint& cell) const
3033{
3034 // Using an items width directly is not an option, since we change
3035 // it during layout (which would also cause problems when recycling items).
3036 auto const cellItem = loadedTableItem(cell)->item;
3037 return cellItem->implicitWidth();
3038}
3039
3040qreal QQuickTableViewPrivate::cellHeight(const QPoint& cell) const
3041{
3042 // Using an items height directly is not an option, since we change
3043 // it during layout (which would also cause problems when recycling items).
3044 auto const cellItem = loadedTableItem(cell)->item;
3045 return cellItem->implicitHeight();
3046}
3047
3048qreal QQuickTableViewPrivate::sizeHintForColumn(int column) const
3049{
3050 // Find the widest cell in the column, and return its width
3051 qreal columnWidth = 0;
3052 for (const int row : loadedRows)
3053 columnWidth = qMax(columnWidth, cellWidth(QPoint(column, row)));
3054
3055 return columnWidth;
3056}
3057
3058qreal QQuickTableViewPrivate::sizeHintForRow(int row) const
3059{
3060 // Find the highest cell in the row, and return its height
3061 qreal rowHeight = 0;
3062 for (const int column : loadedColumns)
3063 rowHeight = qMax(rowHeight, cellHeight(QPoint(column, row)));
3064 return rowHeight;
3065}
3066
3067QSize QQuickTableViewPrivate::calculateTableSize()
3068{
3069 QSize size(0, 0);
3070 if (tableModel)
3071 size = QSize(tableModel->columns(), tableModel->rows());
3072 else if (model)
3073 size = QSize(1, model->count());
3074
3075 return isTransposed ? size.transposed() : size;
3076}
3077
3078qreal QQuickTableViewPrivate::getColumnLayoutWidth(int column)
3079{
3080 // Return the column width specified by the application, or go
3081 // through the loaded items and calculate it as a fallback. For
3082 // layouting, the width can never be zero (or negative), as this
3083 // can lead us to be stuck in an infinite loop trying to load and
3084 // fill out the empty viewport space with empty columns.
3085 const qreal explicitColumnWidth = getColumnWidth(column);
3086 if (explicitColumnWidth >= 0)
3087 return explicitColumnWidth;
3088
3089 if (syncHorizontally) {
3090 if (syncView->d_func()->loadedColumns.contains(column))
3091 return syncView->d_func()->getColumnLayoutWidth(column);
3092 }
3093
3094 // Iterate over the currently visible items in the column. The downside
3095 // of doing that, is that the column width will then only be based on the implicit
3096 // width of the currently loaded items (which can be different depending on which
3097 // row you're at when the column is flicked in). The upshot is that you don't have to
3098 // bother setting columnWidthProvider for small tables, or if the implicit width doesn't vary.
3099 qreal columnWidth = sizeHintForColumn(column);
3100
3101 if (qIsNaN(columnWidth) || columnWidth <= 0) {
3102 if (!layoutWarningIssued) {
3103 layoutWarningIssued = true;
3104 qmlWarning(q_func()) << "the delegate's implicitWidth needs to be greater than zero";
3105 }
3106 columnWidth = kDefaultColumnWidth;
3107 }
3108
3109 return columnWidth;
3110}
3111
3112qreal QQuickTableViewPrivate::getEffectiveRowY(int row) const
3113{
3114 // Return y pos of row after layout
3115 Q_TABLEVIEW_ASSERT(loadedRows.contains(row), row);
3116 return loadedTableItem(QPoint(leftColumn(), row))->geometry().y();
3117}
3118
3119qreal QQuickTableViewPrivate::getEffectiveRowHeight(int row) const
3120{
3121 // Return row height after layout
3122 Q_TABLEVIEW_ASSERT(loadedRows.contains(row), row);
3123 return loadedTableItem(QPoint(leftColumn(), row))->geometry().height();
3124}
3125
3126qreal QQuickTableViewPrivate::getEffectiveColumnX(int column) const
3127{
3128 // Return x pos of column after layout
3129 Q_TABLEVIEW_ASSERT(loadedColumns.contains(column), column);
3130 return loadedTableItem(QPoint(column, topRow()))->geometry().x();
3131}
3132
3133qreal QQuickTableViewPrivate::getEffectiveColumnWidth(int column) const
3134{
3135 // Return column width after layout
3136 Q_TABLEVIEW_ASSERT(loadedColumns.contains(column), column);
3137 return loadedTableItem(QPoint(column, topRow()))->geometry().width();
3138}
3139
3140qreal QQuickTableViewPrivate::getRowLayoutHeight(int row)
3141{
3142 // Return the row height specified by the application, or go
3143 // through the loaded items and calculate it as a fallback. For
3144 // layouting, the height can never be zero (or negative), as this
3145 // can lead us to be stuck in an infinite loop trying to load and
3146 // fill out the empty viewport space with empty rows.
3147 const qreal explicitRowHeight = getRowHeight(row);
3148 if (explicitRowHeight >= 0)
3149 return explicitRowHeight;
3150
3151 if (syncVertically) {
3152 if (syncView->d_func()->loadedRows.contains(row))
3153 return syncView->d_func()->getRowLayoutHeight(row);
3154 }
3155
3156 // Iterate over the currently visible items in the row. The downside
3157 // of doing that, is that the row height will then only be based on the implicit
3158 // height of the currently loaded items (which can be different depending on which
3159 // column you're at when the row is flicked in). The upshot is that you don't have to
3160 // bother setting rowHeightProvider for small tables, or if the implicit height doesn't vary.
3161 qreal rowHeight = sizeHintForRow(row);
3162
3163 if (qIsNaN(rowHeight) || rowHeight <= 0) {
3164 if (!layoutWarningIssued) {
3165 layoutWarningIssued = true;
3166 qmlWarning(q_func()) << "the delegate's implicitHeight needs to be greater than zero";
3167 }
3168 rowHeight = kDefaultRowHeight;
3169 }
3170
3171 return rowHeight;
3172}
3173
3174qreal QQuickTableViewPrivate::getColumnWidth(int column) const
3175{
3176 // Return the width of the given column, if explicitly set. Return 0 if the column
3177 // is hidden, and -1 if the width is not set (which means that the width should
3178 // instead be calculated from the implicit size of the delegate items. This function
3179 // can be overridden by e.g HeaderView to provide the column widths by other means.
3180 Q_Q(const QQuickTableView);
3181
3182 const int noExplicitColumnWidth = -1;
3183
3184 if (cachedColumnWidth.startIndex == logicalColumnIndex(column))
3185 return cachedColumnWidth.size;
3186
3187 if (syncHorizontally)
3188 return syncView->d_func()->getColumnWidth(column);
3189
3190 if (columnWidthProvider.isUndefined()) {
3191 // We only respect explicit column widths when no columnWidthProvider
3192 // is set. Otherwise it's the responsibility of the provider to e.g
3193 // call explicitColumnWidth() (and implicitColumnWidth()), if needed.
3194 qreal explicitColumnWidth = q->explicitColumnWidth(column);
3195 if (explicitColumnWidth >= 0)
3196 return explicitColumnWidth;
3197 return noExplicitColumnWidth;
3198 }
3199
3200 qreal columnWidth = noExplicitColumnWidth;
3201
3202 if (columnWidthProvider.isCallable()) {
3203 auto const columnAsArgument = QJSValueList() << QJSValue(column);
3204 columnWidth = columnWidthProvider.call(columnAsArgument).toNumber();
3205 if (qIsNaN(columnWidth) || columnWidth < 0)
3206 columnWidth = noExplicitColumnWidth;
3207 } else {
3208 if (!layoutWarningIssued) {
3209 layoutWarningIssued = true;
3210 qmlWarning(q_func()) << "columnWidthProvider doesn't contain a function";
3211 }
3212 columnWidth = noExplicitColumnWidth;
3213 }
3214
3215 cachedColumnWidth.startIndex = logicalColumnIndex(column);
3216 cachedColumnWidth.size = columnWidth;
3217 return columnWidth;
3218}
3219
3220qreal QQuickTableViewPrivate::getRowHeight(int row) const
3221{
3222 // Return the height of the given row, if explicitly set. Return 0 if the row
3223 // is hidden, and -1 if the height is not set (which means that the height should
3224 // instead be calculated from the implicit size of the delegate items. This function
3225 // can be overridden by e.g HeaderView to provide the row heights by other means.
3226 Q_Q(const QQuickTableView);
3227
3228 const int noExplicitRowHeight = -1;
3229
3230 if (cachedRowHeight.startIndex == logicalRowIndex(row))
3231 return cachedRowHeight.size;
3232
3233 if (syncVertically)
3234 return syncView->d_func()->getRowHeight(row);
3235
3236 if (rowHeightProvider.isUndefined()) {
3237 // We only resepect explicit row heights when no rowHeightProvider
3238 // is set. Otherwise it's the responsibility of the provider to e.g
3239 // call explicitRowHeight() (and implicitRowHeight()), if needed.
3240 qreal explicitRowHeight = q->explicitRowHeight(row);
3241 if (explicitRowHeight >= 0)
3242 return explicitRowHeight;
3243 return noExplicitRowHeight;
3244 }
3245
3246 qreal rowHeight = noExplicitRowHeight;
3247
3248 if (rowHeightProvider.isCallable()) {
3249 auto const rowAsArgument = QJSValueList() << QJSValue(row);
3250 rowHeight = rowHeightProvider.call(rowAsArgument).toNumber();
3251 if (qIsNaN(rowHeight) || rowHeight < 0)
3252 rowHeight = noExplicitRowHeight;
3253 } else {
3254 if (!layoutWarningIssued) {
3255 layoutWarningIssued = true;
3256 qmlWarning(q_func()) << "rowHeightProvider doesn't contain a function";
3257 }
3258 rowHeight = noExplicitRowHeight;
3259 }
3260
3261 cachedRowHeight.startIndex = logicalRowIndex(row);
3262 cachedRowHeight.size = rowHeight;
3263 return rowHeight;
3264}
3265
3266qreal QQuickTableViewPrivate::getAlignmentContentX(int column, Qt::Alignment alignment, const qreal offset, const QRectF &subRect)
3267{
3268 Q_Q(QQuickTableView);
3269
3270 qreal contentX = 0;
3271 const int columnX = getEffectiveColumnX(column);
3272
3273 if (subRect.isValid()) {
3274 if (alignment == (Qt::AlignLeft | Qt::AlignRight)) {
3275 // Special case: Align to the right as long as the left
3276 // edge of the cell remains visible. Otherwise align to the left.
3277 alignment = subRect.width() > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3278 }
3279
3280 if (alignment & Qt::AlignLeft) {
3281 contentX = columnX + subRect.x() + offset;
3282 } else if (alignment & Qt::AlignRight) {
3283 contentX = columnX + subRect.right() - viewportRect.width() + offset;
3284 } else if (alignment & Qt::AlignHCenter) {
3285 const qreal centerDistance = (viewportRect.width() - subRect.width()) / 2;
3286 contentX = columnX + subRect.x() - centerDistance + offset;
3287 }
3288 } else {
3289 const int columnWidth = getEffectiveColumnWidth(column);
3290 if (alignment == (Qt::AlignLeft | Qt::AlignRight))
3291 alignment = columnWidth > q->width() ? Qt::AlignLeft : Qt::AlignRight;
3292
3293 if (alignment & Qt::AlignLeft) {
3294 contentX = columnX + offset;
3295 } else if (alignment & Qt::AlignRight) {
3296 contentX = columnX + columnWidth - viewportRect.width() + offset;
3297 } else if (alignment & Qt::AlignHCenter) {
3298 const qreal centerDistance = (viewportRect.width() - columnWidth) / 2;
3299 contentX = columnX - centerDistance + offset;
3300 }
3301 }
3302
3303 // Don't overshoot
3304 contentX = qBound(-q->minXExtent(), contentX, -q->maxXExtent());
3305
3306 return contentX;
3307}
3308
3309qreal QQuickTableViewPrivate::getAlignmentContentY(int row, Qt::Alignment alignment, const qreal offset, const QRectF &subRect)
3310{
3311 Q_Q(QQuickTableView);
3312
3313 qreal contentY = 0;
3314 const int rowY = getEffectiveRowY(row);
3315
3316 if (subRect.isValid()) {
3317 if (alignment == (Qt::AlignTop | Qt::AlignBottom)) {
3318 // Special case: Align to the bottom as long as the top
3319 // edge of the cell remains visible. Otherwise align to the top.
3320 alignment = subRect.height() > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3321 }
3322
3323 if (alignment & Qt::AlignTop) {
3324 contentY = rowY + subRect.y() + offset;
3325 } else if (alignment & Qt::AlignBottom) {
3326 contentY = rowY + subRect.bottom() - viewportRect.height() + offset;
3327 } else if (alignment & Qt::AlignVCenter) {
3328 const qreal centerDistance = (viewportRect.height() - subRect.height()) / 2;
3329 contentY = rowY + subRect.y() - centerDistance + offset;
3330 }
3331 } else {
3332 const int rowHeight = getEffectiveRowHeight(row);
3333 if (alignment == (Qt::AlignTop | Qt::AlignBottom))
3334 alignment = rowHeight > q->height() ? Qt::AlignTop : Qt::AlignBottom;
3335
3336 if (alignment & Qt::AlignTop) {
3337 contentY = rowY + offset;
3338 } else if (alignment & Qt::AlignBottom) {
3339 contentY = rowY + rowHeight - viewportRect.height() + offset;
3340 } else if (alignment & Qt::AlignVCenter) {
3341 const qreal centerDistance = (viewportRect.height() - rowHeight) / 2;
3342 contentY = rowY - centerDistance + offset;
3343 }
3344 }
3345
3346 // Don't overshoot
3347 contentY = qBound(-q->minYExtent(), contentY, -q->maxYExtent());
3348
3349 return contentY;
3350}
3351
3352bool QQuickTableViewPrivate::isColumnHidden(int column) const
3353{
3354 // A column is hidden if the width is explicit set to zero (either by
3355 // using a columnWidthProvider, or by overriding getColumnWidth()).
3356 return qFuzzyIsNull(getColumnWidth(column));
3357}
3358
3359bool QQuickTableViewPrivate::isRowHidden(int row) const
3360{
3361 // A row is hidden if the height is explicit set to zero (either by
3362 // using a rowHeightProvider, or by overriding getRowHeight()).
3363 return qFuzzyIsNull(getRowHeight(row));
3364}
3365
3366void QQuickTableViewPrivate::relayoutTableItems()
3367{
3368 qCDebug(lcTableViewDelegateLifecycle);
3369
3370 if (viewportRect.isEmpty()) {
3371 // This can happen if TableView was resized down to have a zero size
3372 qCDebug(lcTableViewDelegateLifecycle()) << "Skipping relayout, viewport has zero size";
3373 return;
3374 }
3375
3376 qreal nextColumnX = loadedTableOuterRect.x();
3377 qreal nextRowY = loadedTableOuterRect.y();
3378
3379 for (const int column : loadedColumns) {
3380 // Adjust the geometry of all cells in the current column
3381 const qreal width = getColumnLayoutWidth(column);
3382
3383 for (const int row : loadedRows) {
3384 auto item = loadedTableItem(QPoint(column, row));
3385 QRectF geometry = item->geometry();
3386 geometry.moveLeft(nextColumnX);
3387 geometry.setWidth(width);
3388 item->setGeometry(geometry);
3389 }
3390
3391 if (width > 0)
3392 nextColumnX += width + cellSpacing.width();
3393 }
3394
3395 for (const int row : loadedRows) {
3396 // Adjust the geometry of all cells in the current row
3397 const qreal height = getRowLayoutHeight(row);
3398
3399 for (const int column : loadedColumns) {
3400 auto item = loadedTableItem(QPoint(column, row));
3401 QRectF geometry = item->geometry();
3402 geometry.moveTop(nextRowY);
3403 geometry.setHeight(height);
3404 item->setGeometry(geometry);
3405 }
3406
3407 if (height > 0)
3408 nextRowY += height + cellSpacing.height();
3409 }
3410
3411 if (Q_UNLIKELY(lcTableViewDelegateLifecycle().isDebugEnabled())) {
3412 for (const int column : loadedColumns) {
3413 for (const int row : loadedRows) {
3414 QPoint cell = QPoint(column, row);
3415 qCDebug(lcTableViewDelegateLifecycle()) << "relayout item:" << cell << loadedTableItem(cell)->geometry();
3416 }
3417 }
3418 }
3419}
3420
3421void QQuickTableViewPrivate::layoutVerticalEdge(Qt::Edge tableEdge)
3422{
3423 int columnThatNeedsLayout;
3424 int neighbourColumn;
3425 qreal columnX;
3426 qreal columnWidth;
3427
3428 if (tableEdge == Qt::LeftEdge) {
3429 columnThatNeedsLayout = leftColumn();
3430 neighbourColumn = loadedColumns.values().at(1);
3431 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3432 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3433 columnX = neighbourItem->geometry().left() - cellSpacing.width() - columnWidth;
3434 } else {
3435 columnThatNeedsLayout = rightColumn();
3436 neighbourColumn = loadedColumns.values().at(loadedColumns.count() - 2);
3437 columnWidth = getColumnLayoutWidth(columnThatNeedsLayout);
3438 const auto neighbourItem = loadedTableItem(QPoint(neighbourColumn, topRow()));
3439 columnX = neighbourItem->geometry().right() + cellSpacing.width();
3440 }
3441
3442 for (const int row : loadedRows) {
3443 auto fxTableItem = loadedTableItem(QPoint(columnThatNeedsLayout, row));
3444 auto const neighbourItem = loadedTableItem(QPoint(neighbourColumn, row));
3445 const qreal rowY = neighbourItem->geometry().y();
3446 const qreal rowHeight = neighbourItem->geometry().height();
3447
3448 fxTableItem->setGeometry(QRectF(columnX, rowY, columnWidth, rowHeight));
3449 fxTableItem->setVisible(true);
3450
3451 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(columnThatNeedsLayout, row) << fxTableItem->geometry();
3452 }
3453}
3454
3455void QQuickTableViewPrivate::layoutHorizontalEdge(Qt::Edge tableEdge)
3456{
3457 int rowThatNeedsLayout;
3458 int neighbourRow;
3459
3460 if (tableEdge == Qt::TopEdge) {
3461 rowThatNeedsLayout = topRow();
3462 neighbourRow = loadedRows.values().at(1);
3463 } else {
3464 rowThatNeedsLayout = bottomRow();
3465 neighbourRow = loadedRows.values().at(loadedRows.count() - 2);
3466 }
3467
3468 // Set the width first, since text items in QtQuick will calculate
3469 // implicitHeight based on the text items width.
3470 for (const int column : loadedColumns) {
3471 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3472 auto const neighbourItem = loadedTableItem(QPoint(column, neighbourRow));
3473 const qreal columnX = neighbourItem->geometry().x();
3474 const qreal columnWidth = neighbourItem->geometry().width();
3475 fxTableItem->item->setX(columnX);
3476 fxTableItem->item->setWidth(columnWidth);
3477 }
3478
3479 qreal rowY;
3480 qreal rowHeight;
3481 if (tableEdge == Qt::TopEdge) {
3482 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3483 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3484 rowY = neighbourItem->geometry().top() - cellSpacing.height() - rowHeight;
3485 } else {
3486 rowHeight = getRowLayoutHeight(rowThatNeedsLayout);
3487 const auto neighbourItem = loadedTableItem(QPoint(leftColumn(), neighbourRow));
3488 rowY = neighbourItem->geometry().bottom() + cellSpacing.height();
3489 }
3490
3491 for (const int column : loadedColumns) {
3492 auto fxTableItem = loadedTableItem(QPoint(column, rowThatNeedsLayout));
3493 fxTableItem->item->setY(rowY);
3494 fxTableItem->item->setHeight(rowHeight);
3495 fxTableItem->setVisible(true);
3496
3497 qCDebug(lcTableViewDelegateLifecycle()) << "layout item:" << QPoint(column, rowThatNeedsLayout) << fxTableItem->geometry();
3498 }
3499}
3500
3501void QQuickTableViewPrivate::layoutTopLeftItem()
3502{
3503 const QPoint cell(loadRequest.column(), loadRequest.row());
3504 auto topLeftItem = loadedTableItem(cell);
3505 auto item = topLeftItem->item;
3506
3507 item->setPosition(loadRequest.startPosition());
3508 item->setSize(QSizeF(getColumnLayoutWidth(cell.x()), getRowLayoutHeight(cell.y())));
3509 topLeftItem->setVisible(true);
3510 qCDebug(lcTableViewDelegateLifecycle) << "geometry:" << topLeftItem->geometry();
3511}
3512
3513void QQuickTableViewPrivate::layoutTableEdgeFromLoadRequest()
3514{
3515 if (loadRequest.edge() == Qt::Edge(0)) {
3516 // No edge means we're loading the top-left item
3517 layoutTopLeftItem();
3518 return;
3519 }
3520
3521 switch (loadRequest.edge()) {
3522 case Qt::LeftEdge:
3523 case Qt::RightEdge:
3524 layoutVerticalEdge(loadRequest.edge());
3525 break;
3526 case Qt::TopEdge:
3527 case Qt::BottomEdge:
3528 layoutHorizontalEdge(loadRequest.edge());
3529 break;
3530 }
3531}
3532
3533void QQuickTableViewPrivate::processLoadRequest()
3534{
3535 Q_Q(QQuickTableView);
3536 Q_TABLEVIEW_ASSERT(loadRequest.isActive(), "");
3537
3538 while (loadRequest.hasCurrentCell()) {
3539 QPoint cell = loadRequest.currentCell();
3540 FxTableItem *fxTableItem = loadFxTableItem(cell, loadRequest.incubationMode());
3541
3542 if (!fxTableItem) {
3543 // Requested item is not yet ready. Just leave, and wait for this
3544 // function to be called again when the item is ready.
3545 return;
3546 }
3547
3548 loadedItems.insert(modelIndexAtCell(cell), fxTableItem);
3549 loadRequest.moveToNextCell();
3550 }
3551
3552 qCDebug(lcTableViewDelegateLifecycle()) << "all items loaded!";
3553
3554 syncLoadedTableFromLoadRequest();
3555 layoutTableEdgeFromLoadRequest();
3556 syncLoadedTableRectFromLoadedTable();
3557
3558 if (rebuildState == RebuildState::Done) {
3559 // Loading of this edge was not done as a part of a rebuild, but
3560 // instead as an incremental build after e.g a flick.
3561 updateExtents();
3562 drainReusePoolAfterLoadRequest();
3563
3564 switch (loadRequest.edge()) {
3565 case Qt::LeftEdge:
3566 emit q->leftColumnChanged();
3567 break;
3568 case Qt::RightEdge:
3569 emit q->rightColumnChanged();
3570 break;
3571 case Qt::TopEdge:
3572 emit q->topRowChanged();
3573 break;
3574 case Qt::BottomEdge:
3575 emit q->bottomRowChanged();
3576 break;
3577 }
3578
3579 if (editIndex.isValid())
3580 updateEditItem();
3581
3582 emit q->layoutChanged();
3583 }
3584
3585 loadRequest.markAsDone();
3586
3587 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3588 qCDebug(lcTableViewDelegateLifecycle()) << "Load request completed!";
3589 qCDebug(lcTableViewDelegateLifecycle()) << "****************************************";
3590}
3591
3592void QQuickTableViewPrivate::processRebuildTable()
3593{
3594 Q_Q(QQuickTableView);
3595
3596 if (rebuildState == RebuildState::Begin) {
3597 qCDebug(lcTableViewDelegateLifecycle()) << "begin rebuild:" << q << "options:" << rebuildOptions;
3598 tableSizeBeforeRebuild = tableSize;
3599 edgesBeforeRebuild = loadedItems.isEmpty() ? QMargins(-1,-1,-1,-1)
3600 : QMargins(q->leftColumn(), q->topRow(), q->rightColumn(), q->bottomRow());
3601 }
3602
3603 moveToNextRebuildState();
3604
3605 if (rebuildState == RebuildState::LoadInitalTable) {
3606 loadInitialTable();
3607 if (!moveToNextRebuildState())
3608 return;
3609 }
3610
3611 if (rebuildState == RebuildState::VerifyTable) {
3612 if (loadedItems.isEmpty()) {
3613 qCDebug(lcTableViewDelegateLifecycle()) << "no items loaded!";
3614 updateContentWidth();
3615 updateContentHeight();
3616 rebuildState = RebuildState::Done;
3617 } else if (!moveToNextRebuildState()) {
3618 return;
3619 }
3620 }
3621
3622 if (rebuildState == RebuildState::LayoutTable) {
3623 layoutAfterLoadingInitialTable();
3624 loadAndUnloadVisibleEdges();
3625 if (!moveToNextRebuildState())
3626 return;
3627 }
3628
3629 if (rebuildState == RebuildState::CancelOvershoot) {
3630 cancelOvershootAfterLayout();
3631 loadAndUnloadVisibleEdges();
3632 if (!moveToNextRebuildState())
3633 return;
3634 }
3635
3636 if (rebuildState == RebuildState::UpdateContentSize) {
3637 updateContentSize();
3638 if (!moveToNextRebuildState())
3639 return;
3640 }
3641
3642 const bool preload = (rebuildOptions & RebuildOption::All
3643 && reusableFlag == QQmlTableInstanceModel::Reusable);
3644
3645 if (rebuildState == RebuildState::PreloadColumns) {
3646 if (preload && !atTableEnd(Qt::RightEdge))
3647 loadEdge(Qt::RightEdge, QQmlIncubator::AsynchronousIfNested);
3648 if (!moveToNextRebuildState())
3649 return;
3650 }
3651
3652 if (rebuildState == RebuildState::PreloadRows) {
3653 if (preload && !atTableEnd(Qt::BottomEdge))
3654 loadEdge(Qt::BottomEdge, QQmlIncubator::AsynchronousIfNested);
3655 if (!moveToNextRebuildState())
3656 return;
3657 }
3658
3659 if (rebuildState == RebuildState::MovePreloadedItemsToPool) {
3660 while (Qt::Edge edge = nextEdgeToUnload(viewportRect))
3661 unloadEdge(edge);
3662 if (!moveToNextRebuildState())
3663 return;
3664 }
3665
3666 if (rebuildState == RebuildState::Done) {
3667 if (tableSizeBeforeRebuild.width() != tableSize.width())
3668 emit q->columnsChanged();
3669 if (tableSizeBeforeRebuild.height() != tableSize.height())
3670 emit q->rowsChanged();
3671 if (edgesBeforeRebuild.left() != q->leftColumn())
3672 emit q->leftColumnChanged();
3673 if (edgesBeforeRebuild.right() != q->rightColumn())
3674 emit q->rightColumnChanged();
3675 if (edgesBeforeRebuild.top() != q->topRow())
3676 emit q->topRowChanged();
3677 if (edgesBeforeRebuild.bottom() != q->bottomRow())
3678 emit q->bottomRowChanged();
3679
3680 if (editIndex.isValid())
3681 updateEditItem();
3682 updateCurrentRowAndColumn();
3683
3684 emit q->layoutChanged();
3685
3686 qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString();
3687 qCDebug(lcTableViewDelegateLifecycle()) << "rebuild completed!";
3688 qCDebug(lcTableViewDelegateLifecycle()) << "################################################";
3689 qCDebug(lcTableViewDelegateLifecycle());
3690 }
3691
3692 Q_TABLEVIEW_ASSERT(rebuildState == RebuildState::Done, int(rebuildState));
3693}
3694
3695bool QQuickTableViewPrivate::moveToNextRebuildState()
3696{
3697 if (loadRequest.isActive()) {
3698 // Items are still loading async, which means
3699 // that the current state is not yet done.
3700 return false;
3701 }
3702
3703 if (rebuildState == RebuildState::Begin
3704 && rebuildOptions.testFlag(RebuildOption::LayoutOnly))
3705 rebuildState = RebuildState::LayoutTable;
3706 else
3707 rebuildState = RebuildState(int(rebuildState) + 1);
3708
3709 qCDebug(lcTableViewDelegateLifecycle()) << rebuildState;
3710 return true;
3711}
3712
3713void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topLeftPos)
3714{
3715 if (tableSize.isEmpty()) {
3716 // There is no cell that can be top left
3717 topLeftCell.rx() = kEdgeIndexAtEnd;
3718 topLeftCell.ry() = kEdgeIndexAtEnd;
3719 return;
3720 }
3721
3722 if (syncHorizontally || syncVertically) {
3723 const auto syncView_d = syncView->d_func();
3724
3725 if (syncView_d->loadedItems.isEmpty()) {
3726 topLeftCell.rx() = 0;
3727 topLeftCell.ry() = 0;
3728 return;
3729 }
3730
3731 // Get sync view top left, and use that as our own top left (if possible)
3732 const QPoint syncViewTopLeftCell(syncView_d->leftColumn(), syncView_d->topRow());
3733 const auto syncViewTopLeftFxItem = syncView_d->loadedTableItem(syncViewTopLeftCell);
3734 const QPointF syncViewTopLeftPos = syncViewTopLeftFxItem->geometry().topLeft();
3735
3736 if (syncHorizontally) {
3737 topLeftCell.rx() = syncViewTopLeftCell.x();
3738 topLeftPos.rx() = syncViewTopLeftPos.x();
3739
3740 if (topLeftCell.x() >= tableSize.width()) {
3741 // Top left is outside our own model.
3742 topLeftCell.rx() = kEdgeIndexAtEnd;
3743 topLeftPos.rx() = kEdgeIndexAtEnd;
3744 }
3745 }
3746
3747 if (syncVertically) {
3748 topLeftCell.ry() = syncViewTopLeftCell.y();
3749 topLeftPos.ry() = syncViewTopLeftPos.y();
3750
3751 if (topLeftCell.y() >= tableSize.height()) {
3752 // Top left is outside our own model.
3753 topLeftCell.ry() = kEdgeIndexAtEnd;
3754 topLeftPos.ry() = kEdgeIndexAtEnd;
3755 }
3756 }
3757
3758 if (syncHorizontally && syncVertically) {
3759 // We have a valid top left, so we're done
3760 return;
3761 }
3762 }
3763
3764 // Since we're not sync-ing both horizontal and vertical, calculate the missing
3765 // dimention(s) ourself. If we rebuild all, we find the first visible top-left
3766 // item starting from cell(0, 0). Otherwise, guesstimate which row or column that
3767 // should be the new top-left given the geometry of the viewport.
3768
3769 if (!syncHorizontally) {
3770 if (rebuildOptions & RebuildOption::All) {
3771 // Find the first visible column from the beginning
3772 topLeftCell.rx() = nextVisibleEdgeIndex(Qt::RightEdge, 0);
3773 if (topLeftCell.x() == kEdgeIndexAtEnd) {
3774 // No visible column found
3775 return;
3776 }
3777 } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftColumn) {
3778 // Guesstimate new top left
3779 const int newColumn = int(viewportRect.x() / (averageEdgeSize.width() + cellSpacing.width()));
3780 topLeftCell.rx() = qBound(0, newColumn, tableSize.width() - 1);
3781 topLeftPos.rx() = topLeftCell.x() * (averageEdgeSize.width() + cellSpacing.width());
3782 } else if (rebuildOptions & RebuildOption::PositionViewAtColumn) {
3783 topLeftCell.rx() = qBound(0, positionViewAtColumnAfterRebuild, tableSize.width() - 1);
3784 topLeftPos.rx() = qFloor(topLeftCell.x()) * (averageEdgeSize.width() + cellSpacing.width());
3785 } else {
3786 // Keep the current top left, unless it's outside model
3787 topLeftCell.rx() = qBound(0, leftColumn(), tableSize.width() - 1);
3788 // We begin by loading the columns where the viewport is at
3789 // now. But will move the whole table and the viewport
3790 // later, when we do a layoutAfterLoadingInitialTable().
3791 topLeftPos.rx() = loadedTableOuterRect.x();
3792 }
3793 }
3794
3795 if (!syncVertically) {
3796 if (rebuildOptions & RebuildOption::All) {
3797 // Find the first visible row from the beginning
3798 topLeftCell.ry() = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
3799 if (topLeftCell.y() == kEdgeIndexAtEnd) {
3800 // No visible row found
3801 return;
3802 }
3803 } else if (rebuildOptions & RebuildOption::CalculateNewTopLeftRow) {
3804 // Guesstimate new top left
3805 const int newRow = int(viewportRect.y() / (averageEdgeSize.height() + cellSpacing.height()));
3806 topLeftCell.ry() = qBound(0, newRow, tableSize.height() - 1);
3807 topLeftPos.ry() = topLeftCell.y() * (averageEdgeSize.height() + cellSpacing.height());
3808 } else if (rebuildOptions & RebuildOption::PositionViewAtRow) {
3809 topLeftCell.ry() = qBound(0, positionViewAtRowAfterRebuild, tableSize.height() - 1);
3810 topLeftPos.ry() = qFloor(topLeftCell.y()) * (averageEdgeSize.height() + cellSpacing.height());
3811 } else {
3812 topLeftCell.ry() = qBound(0, topRow(), tableSize.height() - 1);
3813 topLeftPos.ry() = loadedTableOuterRect.y();
3814 }
3815 }
3816}
3817
3818void QQuickTableViewPrivate::loadInitialTable()
3819{
3820 tableSize = calculateTableSize();
3821
3822 if (positionXAnimation.isRunning()) {
3823 positionXAnimation.stop();
3824 setLocalViewportX(positionXAnimation.to().toReal());
3825 syncViewportRect();
3826 }
3827
3828 if (positionYAnimation.isRunning()) {
3829 positionYAnimation.stop();
3830 setLocalViewportY(positionYAnimation.to().toReal());
3831 syncViewportRect();
3832 }
3833
3834 QPoint topLeft;
3835 QPointF topLeftPos;
3836 calculateTopLeft(topLeft, topLeftPos);
3837 qCDebug(lcTableViewDelegateLifecycle()) << "initial viewport rect:" << viewportRect;
3838 qCDebug(lcTableViewDelegateLifecycle()) << "initial top left cell:" << topLeft << ", pos:" << topLeftPos;
3839
3840 if (!loadedItems.isEmpty()) {
3841 if (rebuildOptions & RebuildOption::All)
3842 releaseLoadedItems(QQmlTableInstanceModel::NotReusable);
3843 else if (rebuildOptions & RebuildOption::ViewportOnly)
3844 releaseLoadedItems(reusableFlag);
3845 }
3846
3847 if (rebuildOptions & RebuildOption::All) {
3848 origin = QPointF(0, 0);
3849 endExtent = QSizeF(0, 0);
3850 hData.markExtentsDirty();
3851 vData.markExtentsDirty();
3852 updateBeginningEnd();
3853 }
3854
3855 loadedColumns.clear();
3856 loadedRows.clear();
3857 loadedTableOuterRect = QRect();
3858 loadedTableInnerRect = QRect();
3859 clearEdgeSizeCache();
3860
3861 if (syncHorizontally)
3862 setLocalViewportX(syncView->contentX());
3863
3864 if (syncVertically)
3865 setLocalViewportY(syncView->contentY());
3866
3867 if (!syncHorizontally && rebuildOptions & RebuildOption::PositionViewAtColumn)
3868 setLocalViewportX(topLeftPos.x());
3869
3870 if (!syncVertically && rebuildOptions & RebuildOption::PositionViewAtRow)
3871 setLocalViewportY(topLeftPos.y());
3872
3873 syncViewportRect();
3874
3875 if (!model) {
3876 qCDebug(lcTableViewDelegateLifecycle()) << "no model found, leaving table empty";
3877 return;
3878 }
3879
3880 if (model->count() == 0) {
3881 qCDebug(lcTableViewDelegateLifecycle()) << "empty model found, leaving table empty";
3882 return;
3883 }
3884
3885 if (tableModel && !tableModel->delegate()) {
3886 qCDebug(lcTableViewDelegateLifecycle()) << "no delegate found, leaving table empty";
3887 return;
3888 }
3889
3890 if (topLeft.x() == kEdgeIndexAtEnd || topLeft.y() == kEdgeIndexAtEnd) {
3891 qCDebug(lcTableViewDelegateLifecycle()) << "no visible row or column found, leaving table empty";
3892 return;
3893 }
3894
3895 if (topLeft.x() == kEdgeIndexNotSet || topLeft.y() == kEdgeIndexNotSet) {
3896 qCDebug(lcTableViewDelegateLifecycle()) << "could not resolve top-left item, leaving table empty";
3897 return;
3898 }
3899
3900 if (viewportRect.isEmpty()) {
3901 qCDebug(lcTableViewDelegateLifecycle()) << "viewport has zero size, leaving table empty";
3902 return;
3903 }
3904
3905 // Load top-left item. After loaded, loadItemsInsideRect() will take
3906 // care of filling out the rest of the table.
3907 loadRequest.begin(topLeft, topLeftPos, QQmlIncubator::AsynchronousIfNested);
3908 processLoadRequest();
3909 loadAndUnloadVisibleEdges();
3910}
3911
3912void QQuickTableViewPrivate::updateContentSize()
3913{
3914 const bool allColumnsLoaded = atTableEnd(Qt::LeftEdge) && atTableEnd(Qt::RightEdge);
3915 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentWidth) || allColumnsLoaded) {
3916 updateAverageColumnWidth();
3917 updateContentWidth();
3918 }
3919
3920 const bool allRowsLoaded = atTableEnd(Qt::TopEdge) && atTableEnd(Qt::BottomEdge);
3921 if (rebuildOptions.testFlag(RebuildOption::CalculateNewContentHeight) || allRowsLoaded) {
3922 updateAverageRowHeight();
3923 updateContentHeight();
3924 }
3925
3926 updateExtents();
3927}
3928
3929void QQuickTableViewPrivate::layoutAfterLoadingInitialTable()
3930{
3931 clearEdgeSizeCache();
3932 relayoutTableItems();
3933 syncLoadedTableRectFromLoadedTable();
3934
3935 updateContentSize();
3936
3937 adjustViewportXAccordingToAlignment();
3938 adjustViewportYAccordingToAlignment();
3939}
3940
3941void QQuickTableViewPrivate::adjustViewportXAccordingToAlignment()
3942{
3943 // Check if we are supposed to position the viewport at a certain column
3944 if (!rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn))
3945 return;
3946 // The requested column might have been hidden or is outside model bounds
3947 if (positionViewAtColumnAfterRebuild != leftColumn())
3948 return;
3949
3950 const qreal newContentX = getAlignmentContentX(
3951 positionViewAtColumnAfterRebuild,
3952 positionViewAtColumnAlignment,
3953 positionViewAtColumnOffset,
3954 positionViewAtColumnSubRect);
3955
3956 setLocalViewportX(newContentX);
3957 syncViewportRect();
3958}
3959
3960void QQuickTableViewPrivate::adjustViewportYAccordingToAlignment()
3961{
3962 // Check if we are supposed to position the viewport at a certain row
3963 if (!rebuildOptions.testFlag(RebuildOption::PositionViewAtRow))
3964 return;
3965 // The requested row might have been hidden or is outside model bounds
3966 if (positionViewAtRowAfterRebuild != topRow())
3967 return;
3968
3969 const qreal newContentY = getAlignmentContentY(
3970 positionViewAtRowAfterRebuild,
3971 positionViewAtRowAlignment,
3972 positionViewAtRowOffset,
3973 positionViewAtRowSubRect);
3974
3975 setLocalViewportY(newContentY);
3976 syncViewportRect();
3977}
3978
3979void QQuickTableViewPrivate::cancelOvershootAfterLayout()
3980{
3981 Q_Q(QQuickTableView);
3982
3983 // Note: we only want to cancel overshoot from a rebuild if we're supposed to position
3984 // the view on a specific cell. The app is allowed to overshoot by setting contentX and
3985 // contentY manually. Also, if this view is a sync child, we should always stay in sync
3986 // with the syncView, so then we don't do anything.
3987 const bool positionVertically = rebuildOptions.testFlag(RebuildOption::PositionViewAtRow);
3988 const bool positionHorizontally = rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn);
3989 const bool cancelVertically = positionVertically && !syncVertically;
3990 const bool cancelHorizontally = positionHorizontally && !syncHorizontally;
3991
3992 if (cancelHorizontally && !qFuzzyIsNull(q->horizontalOvershoot())) {
3993 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot horizontally:" << q->horizontalOvershoot();
3994 setLocalViewportX(q->horizontalOvershoot() < 0 ? -q->minXExtent() : -q->maxXExtent());
3995 syncViewportRect();
3996 }
3997
3998 if (cancelVertically && !qFuzzyIsNull(q->verticalOvershoot())) {
3999 qCDebug(lcTableViewDelegateLifecycle()) << "cancelling overshoot vertically:" << q->verticalOvershoot();
4000 setLocalViewportY(q->verticalOvershoot() < 0 ? -q->minYExtent() : -q->maxYExtent());
4001 syncViewportRect();
4002 }
4003}
4004
4005void QQuickTableViewPrivate::unloadEdge(Qt::Edge edge)
4006{
4007 Q_Q(QQuickTableView);
4008 qCDebug(lcTableViewDelegateLifecycle) << edge;
4009
4010 switch (edge) {
4011 case Qt::LeftEdge: {
4012 const int column = leftColumn();
4013 for (int row : loadedRows)
4014 unloadItem(QPoint(column, row));
4015 loadedColumns.remove(column);
4016 syncLoadedTableRectFromLoadedTable();
4017 if (rebuildState == RebuildState::Done)
4018 emit q->leftColumnChanged();
4019 break; }
4020 case Qt::RightEdge: {
4021 const int column = rightColumn();
4022 for (int row : loadedRows)
4023 unloadItem(QPoint(column, row));
4024 loadedColumns.remove(column);
4025 syncLoadedTableRectFromLoadedTable();
4026 if (rebuildState == RebuildState::Done)
4027 emit q->rightColumnChanged();
4028 break; }
4029 case Qt::TopEdge: {
4030 const int row = topRow();
4031 for (int col : loadedColumns)
4032 unloadItem(QPoint(col, row));
4033 loadedRows.remove(row);
4034 syncLoadedTableRectFromLoadedTable();
4035 if (rebuildState == RebuildState::Done)
4036 emit q->topRowChanged();
4037 break; }
4038 case Qt::BottomEdge: {
4039 const int row = bottomRow();
4040 for (int col : loadedColumns)
4041 unloadItem(QPoint(col, row));
4042 loadedRows.remove(row);
4043 syncLoadedTableRectFromLoadedTable();
4044 if (rebuildState == RebuildState::Done)
4045 emit q->bottomRowChanged();
4046 break; }
4047 }
4048
4049 if (rebuildState == RebuildState::Done)
4050 emit q->layoutChanged();
4051
4052 qCDebug(lcTableViewDelegateLifecycle) << tableLayoutToString();
4053}
4054
4055void QQuickTableViewPrivate::loadEdge(Qt::Edge edge, QQmlIncubator::IncubationMode incubationMode)
4056{
4057 const int edgeIndex = nextVisibleEdgeIndexAroundLoadedTable(edge);
4058 qCDebug(lcTableViewDelegateLifecycle) << edge << edgeIndex << q_func();
4059
4060 const auto &visibleCells = edge & (Qt::LeftEdge | Qt::RightEdge)
4061 ? loadedRows.values() : loadedColumns.values();
4062 loadRequest.begin(edge, edgeIndex, visibleCells, incubationMode);
4063 processLoadRequest();
4064}
4065
4066void QQuickTableViewPrivate::loadAndUnloadVisibleEdges(QQmlIncubator::IncubationMode incubationMode)
4067{
4068 // Unload table edges that have been moved outside the visible part of the
4069 // table (including buffer area), and load new edges that has been moved inside.
4070 // Note: an important point is that we always keep the table rectangular
4071 // and without holes to reduce complexity (we never leave the table in
4072 // a half-loaded state, or keep track of multiple patches).
4073 // We load only one edge (row or column) at a time. This is especially
4074 // important when loading into the buffer, since we need to be able to
4075 // cancel the buffering quickly if the user starts to flick, and then
4076 // focus all further loading on the edges that are flicked into view.
4077
4078 if (loadRequest.isActive()) {
4079 // Don't start loading more edges while we're
4080 // already waiting for another one to load.
4081 return;
4082 }
4083
4084 if (loadedItems.isEmpty()) {
4085 // We need at least the top-left item to be loaded before we can
4086 // start loading edges around it. Not having a top-left item at
4087 // this point means that the model is empty (or no delegate).
4088 return;
4089 }
4090
4091 bool tableModified;
4092
4093 do {
4094 tableModified = false;
4095
4096 if (Qt::Edge edge = nextEdgeToUnload(viewportRect)) {
4097 tableModified = true;
4098 unloadEdge(edge);
4099 }
4100
4101 if (Qt::Edge edge = nextEdgeToLoad(viewportRect)) {
4102 tableModified = true;
4103 loadEdge(edge, incubationMode);
4104 if (loadRequest.isActive())
4105 return;
4106 }
4107 } while (tableModified);
4108
4109}
4110
4111void QQuickTableViewPrivate::drainReusePoolAfterLoadRequest()
4112{
4113 Q_Q(QQuickTableView);
4114
4115 if (reusableFlag == QQmlTableInstanceModel::NotReusable || !tableModel)
4116 return;
4117
4118 if (!qFuzzyIsNull(q->verticalOvershoot()) || !qFuzzyIsNull(q->horizontalOvershoot())) {
4119 // Don't drain while we're overshooting, since this will fill up the
4120 // pool, but we expect to reuse them all once the content item moves back.
4121 return;
4122 }
4123
4124 // When loading edges, we don't want to drain the reuse pool too aggressively. Normally,
4125 // all the items in the pool are reused rapidly as the content view is flicked around
4126 // anyway. Even if the table is temporarily flicked to a section that contains fewer
4127 // cells than what used to be (e.g if the flicked-in rows are taller than average), it
4128 // still makes sense to keep all the items in circulation; Chances are, that soon enough,
4129 // thinner rows are flicked back in again (meaning that we can fit more items into the
4130 // view). But at the same time, if a delegate chooser is in use, the pool might contain
4131 // items created from different delegates. And some of those delegates might be used only
4132 // occasionally. So to avoid situations where an item ends up in the pool for too long, we
4133 // call drain after each load request, but with a sufficiently large pool time. (If an item
4134 // in the pool has a large pool time, it means that it hasn't been reused for an equal
4135 // amount of load cycles, and should be released).
4136 //
4137 // We calculate an appropriate pool time by figuring out what the minimum time must be to
4138 // not disturb frequently reused items. Since the number of items in a row might be higher
4139 // than in a column (or vice versa), the minimum pool time should take into account that
4140 // you might be flicking out a single row (filling up the pool), before you continue
4141 // flicking in several new columns (taking them out again, but now in smaller chunks). This
4142 // will increase the number of load cycles items are kept in the pool (poolTime), but still,
4143 // we shouldn't release them, as they are still being reused frequently.
4144 // To get a flexible maxValue (that e.g tolerates rows and columns being flicked
4145 // in with varying sizes, causing some items not to be resued immediately), we multiply the
4146 // value by 2. Note that we also add an extra +1 to the column count, because the number of
4147 // visible columns will fluctuate between +1/-1 while flicking.
4148 const int w = loadedColumns.count();
4149 const int h = loadedRows.count();
4150 const int minTime = int(std::ceil(w > h ? qreal(w + 1) / h : qreal(h + 1) / w));
4151 const int maxTime = minTime * 2;
4152 tableModel->drainReusableItemsPool(maxTime);
4153}
4154
4155void QQuickTableViewPrivate::scheduleRebuildTable(RebuildOptions options) {
4156 if (!q_func()->isComponentComplete()) {
4157 // We'll rebuild the table once complete anyway
4158 return;
4159 }
4160
4161 scheduledRebuildOptions |= options;
4162 q_func()->polish();
4163}
4164
4165QQuickTableView *QQuickTableViewPrivate::rootSyncView() const
4166{
4167 QQuickTableView *root = const_cast<QQuickTableView *>(q_func());
4168 while (QQuickTableView *view = root->d_func()->syncView)
4169 root = view;
4170 return root;
4171}
4172
4173void QQuickTableViewPrivate::updatePolish()
4174{
4175 // We always start updating from the top of the syncView tree, since
4176 // the layout of a syncView child will depend on the layout of the syncView.
4177 // E.g when a new column is flicked in, the syncView should load and layout
4178 // the column first, before any syncChildren gets a chance to do the same.
4179 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
4180 rootSyncView()->d_func()->updateTableRecursive();
4181}
4182
4183bool QQuickTableViewPrivate::updateTableRecursive()
4184{
4185 if (polishing) {
4186 // We're already updating the Table in this view, so
4187 // we cannot continue. Signal this back by returning false.
4188 // The caller can then choose to call "polish()" instead, to
4189 // do the update later.
4190 return false;
4191 }
4192
4193 const bool updateComplete = updateTable();
4194 if (!updateComplete)
4195 return false;
4196
4197 const auto children = syncChildren;
4198 for (auto syncChild : children) {
4199 auto syncChild_d = syncChild->d_func();
4200 const int mask =
4201 RebuildOption::PositionViewAtRow |
4202 RebuildOption::PositionViewAtColumn |
4203 RebuildOption::CalculateNewTopLeftRow |
4204 RebuildOption::CalculateNewTopLeftColumn;
4205 syncChild_d->scheduledRebuildOptions |= rebuildOptions & ~mask;
4206
4207 const bool descendantUpdateComplete = syncChild_d->updateTableRecursive();
4208 if (!descendantUpdateComplete)
4209 return false;
4210 }
4211
4212 rebuildOptions = RebuildOption::None;
4213
4214 return true;
4215}
4216
4217bool QQuickTableViewPrivate::updateTable()
4218{
4219 // Whenever something changes, e.g viewport moves, spacing is set to a
4220 // new value, model changes etc, this function will end up being called. Here
4221 // we check what needs to be done, and load/unload cells accordingly.
4222 // If we cannot complete the update (because we need to wait for an item
4223 // to load async), we return false.
4224
4225 Q_TABLEVIEW_ASSERT(!polishing, "recursive updatePolish() calls are not allowed!");
4226 QScopedValueRollback polishGuard(polishing, true);
4227
4228 if (loadRequest.isActive()) {
4229 // We're currently loading items async to build a new edge in the table. We see the loading
4230 // as an atomic operation, which means that we don't continue doing anything else until all
4231 // items have been received and laid out. Note that updatePolish is then called once more
4232 // after the loadRequest has completed to handle anything that might have occurred in-between.
4233 return false;
4234 }
4235
4236 if (rebuildState != RebuildState::Done) {
4237 processRebuildTable();
4238 return rebuildState == RebuildState::Done;
4239 }
4240
4241 syncWithPendingChanges();
4242
4243 if (rebuildState == RebuildState::Begin) {
4244 processRebuildTable();
4245 return rebuildState == RebuildState::Done;
4246 }
4247
4248 if (loadedItems.isEmpty())
4249 return !loadRequest.isActive();
4250
4251 loadAndUnloadVisibleEdges();
4252 updateEditItem();
4253
4254 return !loadRequest.isActive();
4255}
4256
4257void QQuickTableViewPrivate::fixup(QQuickFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent)
4258{
4259 if (inUpdateContentSize) {
4260 // We update the content size dynamically as we load and unload edges.
4261 // Unfortunately, this also triggers a call to this function. The base
4262 // implementation will do things like start a momentum animation or move
4263 // the content view somewhere else, which causes glitches. This can
4264 // especially happen if flicking on one of the syncView children, which triggers
4265 // an update to our content size. In that case, the base implementation don't know
4266 // that the view is being indirectly dragged, and will therefore do strange things as
4267 // it tries to 'fixup' the geometry. So we use a guard to prevent this from happening.
4268 return;
4269 }
4270
4271 QQuickFlickablePrivate::fixup(data, minExtent, maxExtent);
4272}
4273
4274QTypeRevision QQuickTableViewPrivate::resolveImportVersion()
4275{
4276 const auto data = QQmlData::get(q_func());
4277 if (!data || !data->propertyCache)
4278 return QTypeRevision::zero();
4279
4280 const auto cppMetaObject = data->propertyCache->firstCppMetaObject();
4281 const auto qmlTypeView = QQmlMetaType::qmlType(cppMetaObject);
4282
4283 // TODO: did we rather want qmlTypeView.revision() here?
4284 return qmlTypeView.metaObjectRevision();
4285}
4286
4287void QQuickTableViewPrivate::createWrapperModel()
4288{
4289 Q_Q(QQuickTableView);
4290 // When the assigned model is not an instance model, we create a wrapper
4291 // model (QQmlTableInstanceModel) that keeps a pointer to both the
4292 // assigned model and the assigned delegate. This model will give us a
4293 // common interface to any kind of model (js arrays, QAIM, number etc), and
4294 // help us create delegate instances.
4295 tableModel = new QQmlTableInstanceModel(qmlContext(q));
4296 tableModel->useImportVersion(resolveImportVersion());
4297 model = tableModel;
4298}
4299
4300bool QQuickTableViewPrivate::selectedInSelectionModel(const QPoint &cell) const
4301{
4302 if (!selectionModel)
4303 return false;
4304
4305 QAbstractItemModel *model = selectionModel->model();
4306 if (!model)
4307 return false;
4308
4309 return selectionModel->isSelected(q_func()->modelIndex(cell));
4310}
4311
4312bool QQuickTableViewPrivate::currentInSelectionModel(const QPoint &cell) const
4313{
4314 if (!selectionModel)
4315 return false;
4316
4317 QAbstractItemModel *model = selectionModel->model();
4318 if (!model)
4319 return false;
4320
4321 return selectionModel->currentIndex() == q_func()->modelIndex(cell);
4322}
4323
4324void QQuickTableViewPrivate::selectionChangedInSelectionModel(const QItemSelection &selected, const QItemSelection &deselected)
4325{
4326 if (!inSelectionModelUpdate) {
4327 // The selection model was manipulated outside of TableView
4328 // and SelectionRectangle. In that case we cancel any ongoing
4329 // selection tracking.
4330 cancelSelectionTracking();
4331 }
4332
4333 const auto &selectedIndexes = selected.indexes();
4334 const auto &deselectedIndexes = deselected.indexes();
4335 for (int i = 0; i < selectedIndexes.size(); ++i)
4336 setSelectedOnDelegateItem(selectedIndexes.at(i), true);
4337 for (int i = 0; i < deselectedIndexes.size(); ++i)
4338 setSelectedOnDelegateItem(deselectedIndexes.at(i), false);
4339}
4340
4341void QQuickTableViewPrivate::setSelectedOnDelegateItem(const QModelIndex &modelIndex, bool select)
4342{
4343 if (modelIndex.isValid() && modelIndex.model() != selectionSourceModel()) {
4344 qmlWarning(q_func())
4345 << "Cannot select cells: TableView.selectionModel.model is not "
4346 << "compatible with the model displayed in the view";
4347 return;
4348 }
4349
4350 const int cellIndex = modelIndexToCellIndex(modelIndex);
4351 if (!loadedItems.contains(cellIndex))
4352 return;
4353 const QPoint cell = cellAtModelIndex(cellIndex);
4354 QQuickItem *item = loadedTableItem(cell)->item;
4355 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(select), cellIndex, item, false);
4356}
4357
4358QAbstractItemModel *QQuickTableViewPrivate::selectionSourceModel()
4359{
4360 // TableView.selectionModel.model should always be the same as TableView.model.
4361 // After all, when the user selects an index in the view, the same index should
4362 // be selected in the selection model. We therefore set the model in
4363 // selectionModel.model automatically.
4364 // But it's not always the case that the model shown in the view is the same
4365 // as TableView.model. Subclasses with a proxy model will instead show the
4366 // proxy model (e.g TreeView and HeaderView). And then it's no longer clear if
4367 // we should use the proxy model or the TableView.model as source model in
4368 // TableView.selectionModel. It's up to the subclass. But in short, if the proxy
4369 // model shares the same model items as TableView.model (just with e.g a filter
4370 // applied, or sorted etc), then TableView.model should be used. If the proxy
4371 // model is a completely different model that shares no model items with
4372 // TableView.model, then the proxy model should be used (e.g HeaderView).
4373 return qaim(modelImpl());
4374}
4375
4376QAbstractItemModel *QQuickTableViewPrivate::qaim(QVariant modelAsVariant) const
4377{
4378 // If modelAsVariant wraps a qaim, return it
4379 if (modelAsVariant.userType() == qMetaTypeId<QJSValue>())
4380 modelAsVariant = modelAsVariant.value<QJSValue>().toVariant();
4381 return qvariant_cast<QAbstractItemModel *>(modelAsVariant);
4382}
4383
4384void QQuickTableViewPrivate::updateSelectedOnAllDelegateItems()
4385{
4386 updateCurrentRowAndColumn();
4387
4388 for (auto it = loadedItems.keyBegin(), end = loadedItems.keyEnd(); it != end; ++it) {
4389 const int cellIndex = *it;
4390 const QPoint cell = cellAtModelIndex(cellIndex);
4391 const bool selected = selectedInSelectionModel(cell);
4392 const bool current = currentInSelectionModel(cell);
4393 QQuickItem *item = loadedTableItem(cell)->item;
4394 const bool editing = editIndex == q_func()->modelIndex(cell);
4395 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), cellIndex, item, false);
4396 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), cellIndex, item, false);
4397 setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(editing), cellIndex, item, false);
4398 }
4399}
4400
4401void QQuickTableViewPrivate::currentChangedInSelectionModel(const QModelIndex &current, const QModelIndex &previous)
4402{
4403 if (current.isValid() && current.model() != selectionSourceModel()) {
4404 qmlWarning(q_func())
4405 << "Cannot change current index: TableView.selectionModel.model is not "
4406 << "compatible with the model displayed in the view";
4407 return;
4408 }
4409
4410 updateCurrentRowAndColumn();
4411 setCurrentOnDelegateItem(previous, false);
4412 setCurrentOnDelegateItem(current, true);
4413}
4414
4415void QQuickTableViewPrivate::updateCurrentRowAndColumn()
4416{
4417 Q_Q(QQuickTableView);
4418
4419 const QModelIndex currentIndex = selectionModel ? selectionModel->currentIndex() : QModelIndex();
4420 const QPoint currentCell = q->cellAtIndex(currentIndex);
4421 if (currentCell.x() != currentColumn) {
4422 currentColumn = currentCell.x();
4423 emit q->currentColumnChanged();
4424 }
4425
4426 if (currentCell.y() != currentRow) {
4427 currentRow = currentCell.y();
4428 emit q->currentRowChanged();
4429 }
4430}
4431
4432void QQuickTableViewPrivate::setCurrentOnDelegateItem(const QModelIndex &index, bool isCurrent)
4433{
4434 const int cellIndex = modelIndexToCellIndex(index);
4435 if (!loadedItems.contains(cellIndex))
4436 return;
4437
4438 const QPoint cell = cellAtModelIndex(cellIndex);
4439 QQuickItem *item = loadedTableItem(cell)->item;
4440 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(isCurrent), cellIndex, item, false);
4441}
4442
4443void QQuickTableViewPrivate::itemCreatedCallback(int modelIndex, QObject*)
4444{
4445 if (blockItemCreatedCallback)
4446 return;
4447
4448 qCDebug(lcTableViewDelegateLifecycle) << "item done loading:"
4449 << cellAtModelIndex(modelIndex);
4450
4451 // Since the item we waited for has finished incubating, we can
4452 // continue with the load request. processLoadRequest will
4453 // ask the model for the requested item once more, which will be
4454 // quick since the model has cached it.
4455 processLoadRequest();
4456 loadAndUnloadVisibleEdges();
4457 updatePolish();
4458}
4459
4460void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object)
4461{
4462 Q_Q(QQuickTableView);
4463
4464 auto item = qobject_cast<QQuickItem*>(object);
4465 if (!item)
4466 return;
4467
4468 item->setParentItem(q->contentItem());
4469 item->setZ(1);
4470
4471 if (auto attached = getAttachedObject(item))
4472 attached->setView(q);
4473
4474 const QPoint cell = cellAtModelIndex(modelIndex);
4475 const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y()));
4476 const bool current = currentInSelectionModel(visualCell);
4477 const bool selected = selectedInSelectionModel(visualCell);
4478
4479 setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), modelIndex, item, true);
4480 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true);
4481 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true);
4482 setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), modelIndex, item, true);
4483 setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, item, true);
4484}
4485
4486void QQuickTableViewPrivate::itemPooledCallback(int modelIndex, QObject *object)
4487{
4488 Q_UNUSED(modelIndex);
4489
4490 if (auto attached = getAttachedObject(object))
4491 emit attached->pooled();
4492}
4493
4494void QQuickTableViewPrivate::itemReusedCallback(int modelIndex, QObject *object)
4495{
4496 Q_Q(QQuickTableView);
4497
4498 const QPoint cell = cellAtModelIndex(modelIndex);
4499 const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y()));
4500 const bool current = currentInSelectionModel(visualCell);
4501 const bool selected = selectedInSelectionModel(visualCell);
4502
4503 setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), modelIndex, object, false);
4504 setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false);
4505 setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false);
4506 // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing
4507 setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, object, false);
4508
4509 if (auto item = qobject_cast<QQuickItem*>(object))
4510 QQuickItemPrivate::get(item)->setCulled(false);
4511
4512 if (auto attached = getAttachedObject(object))
4513 emit attached->reused();
4514}
4515
4516void QQuickTableViewPrivate::syncWithPendingChanges()
4517{
4518 // The application can change properties like the model or the delegate while
4519 // we're e.g in the middle of e.g loading a new row. Since this will lead to
4520 // unpredicted behavior, and possibly a crash, we need to postpone taking
4521 // such assignments into effect until we're in a state that allows it.
4522
4523 syncViewportRect();
4524 syncModel();
4525 syncDelegate();
4526 syncDelegateModelAccess();
4527 syncSyncView();
4528 syncPositionView();
4529
4530 syncRebuildOptions();
4531}
4532
4533void QQuickTableViewPrivate::syncRebuildOptions()
4534{
4535 if (!scheduledRebuildOptions)
4536 return;
4537
4538 rebuildState = RebuildState::Begin;
4539 rebuildOptions = scheduledRebuildOptions;
4540 scheduledRebuildOptions = RebuildOption::None;
4541
4542 if (loadedItems.isEmpty())
4543 rebuildOptions.setFlag(RebuildOption::All);
4544
4545 // Some options are exclusive:
4546 if (rebuildOptions.testFlag(RebuildOption::All)) {
4547 rebuildOptions.setFlag(RebuildOption::ViewportOnly, false);
4548 rebuildOptions.setFlag(RebuildOption::LayoutOnly, false);
4549 rebuildOptions.setFlag(RebuildOption::CalculateNewContentWidth);
4550 rebuildOptions.setFlag(RebuildOption::CalculateNewContentHeight);
4551 } else if (rebuildOptions.testFlag(RebuildOption::ViewportOnly)) {
4552 rebuildOptions.setFlag(RebuildOption::LayoutOnly, false);
4553 }
4554
4555 if (rebuildOptions.testFlag(RebuildOption::PositionViewAtRow))
4556 rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftRow, false);
4557
4558 if (rebuildOptions.testFlag(RebuildOption::PositionViewAtColumn))
4559 rebuildOptions.setFlag(RebuildOption::CalculateNewTopLeftColumn, false);
4560}
4561
4562void QQuickTableViewPrivate::syncDelegate()
4563{
4564 if (!tableModel) {
4565 // Only the tableModel uses the delegate assigned to a
4566 // TableView. DelegateModel has it's own delegate, and
4567 // ObjectModel etc. doesn't use one.
4568 return;
4569 }
4570
4571 if (assignedDelegate != tableModel->delegate())
4572 tableModel->setDelegate(assignedDelegate);
4573}
4574
4575void QQuickTableViewPrivate::syncDelegateModelAccess()
4576{
4577 if (!tableModel) {
4578 // Only the tableModel uses the delegateModelAccess assigned to a
4579 // TableView. DelegateModel has its own delegateModelAccess, and
4580 // ObjectModel doesn't use one.
4581 return;
4582 }
4583
4584 tableModel->setDelegateModelAccess(assignedDelegateModelAccess);
4585}
4586
4587QVariant QQuickTableViewPrivate::modelImpl() const
4588{
4589 if (needsModelSynchronization)
4590 return assignedModel;
4591 if (tableModel)
4592 return tableModel->model();
4593 return QVariant::fromValue(model);
4594}
4595
4596void QQuickTableViewPrivate::setModelImpl(const QVariant &newModel)
4597{
4598 assignedModel = newModel;
4599 needsModelSynchronization = true;
4600 scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
4601 emit q_func()->modelChanged();
4602}
4603
4604void QQuickTableViewPrivate::syncModel()
4605{
4606 if (tableModel) {
4607 if (tableModel->model() == assignedModel)
4608 return;
4609 } else if (QVariant::fromValue(model) == assignedModel) {
4610 return;
4611 }
4612
4613 if (model) {
4614 disconnectFromModel();
4615 releaseLoadedItems(QQmlTableInstanceModel::NotReusable);
4616 }
4617
4618 const auto instanceModel = qobject_cast<QQmlInstanceModel *>(
4619 qvariant_cast<QObject *>(assignedModel));
4620
4621 if (instanceModel) {
4622 if (tableModel) {
4623 delete tableModel;
4624 tableModel = nullptr;
4625 }
4626 model = instanceModel;
4627 } else {
4628 if (!tableModel)
4629 createWrapperModel();
4630 tableModel->setModel(assignedModel);
4631 }
4632
4633 needsModelSynchronization = false;
4634 connectToModel();
4635}
4636
4637void QQuickTableViewPrivate::syncSyncView()
4638{
4639 Q_Q(QQuickTableView);
4640
4641 if (assignedSyncView != syncView) {
4642 if (syncView)
4643 syncView->d_func()->syncChildren.removeOne(q);
4644
4645 if (assignedSyncView) {
4646 QQuickTableView *view = assignedSyncView;
4647
4648 while (view) {
4649 if (view == q) {
4650 if (!layoutWarningIssued) {
4651 layoutWarningIssued = true;
4652 qmlWarning(q) << "TableView: recursive syncView connection detected!";
4653 }
4654 syncView = nullptr;
4655 return;
4656 }
4657 view = view->d_func()->syncView;
4658 }
4659
4660 assignedSyncView->d_func()->syncChildren.append(q);
4661 scheduledRebuildOptions |= RebuildOption::ViewportOnly;
4662 }
4663
4664 syncView = assignedSyncView;
4665 }
4666
4667 syncHorizontally = syncView && assignedSyncDirection & Qt::Horizontal;
4668 syncVertically = syncView && assignedSyncDirection & Qt::Vertical;
4669
4670 if (syncHorizontally) {
4671 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
4672 q->setColumnSpacing(syncView->columnSpacing());
4673 q->setLeftMargin(syncView->leftMargin());
4674 q->setRightMargin(syncView->rightMargin());
4675 updateContentWidth();
4676
4677 if (scheduledRebuildOptions & RebuildOption::LayoutOnly) {
4678 if (syncView->leftColumn() != q->leftColumn()
4679 || syncView->d_func()->loadedTableOuterRect.left() != loadedTableOuterRect.left()) {
4680 // The left column is no longer the same, or at the same pos, as the left column in
4681 // syncView. This can happen if syncView did a relayout that caused its left column
4682 // to be resized so small that it ended up outside the viewport. It can also happen
4683 // if the syncView loaded and unloaded columns after the relayout. We therefore need
4684 // to sync our own left column and pos to be the same, which we do by rebuilding the
4685 // whole viewport instead of just doing a plain LayoutOnly.
4686 scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn;
4687 scheduledRebuildOptions.setFlag(RebuildOption::ViewportOnly);
4688 }
4689 }
4690 }
4691
4692 if (syncVertically) {
4693 QScopedValueRollback fixupGuard(inUpdateContentSize, true);
4694 q->setRowSpacing(syncView->rowSpacing());
4695 q->setTopMargin(syncView->topMargin());
4696 q->setBottomMargin(syncView->bottomMargin());
4697 updateContentHeight();
4698
4699 if (scheduledRebuildOptions & RebuildOption::LayoutOnly) {
4700 if (syncView->topRow() != q->topRow()
4701 || syncView->d_func()->loadedTableOuterRect.top() != loadedTableOuterRect.top()) {
4702 // The top row is no longer the same, or at the same pos, as the top row in
4703 // syncView. This can happen if syncView did a relayout that caused its top row
4704 // to be resized so small that it ended up outside the viewport. It can also happen
4705 // if the syncView loaded and unloaded rows after the relayout. We therefore need
4706 // to sync our own top row and pos to be the same, which we do by rebuilding the
4707 // whole viewport instead of just doing a plain LayoutOnly.
4708 scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow;
4709 scheduledRebuildOptions.setFlag(RebuildOption::ViewportOnly);
4710 }
4711 }
4712 }
4713
4714 if (syncView && loadedItems.isEmpty() && !tableSize.isEmpty()) {
4715 // When we have a syncView, we can sometimes temporarily end up with no loaded items.
4716 // This can happen if the syncView has a model with more rows or columns than us, in
4717 // which case the viewport can end up in a place where we have no rows or columns to
4718 // show. In that case, check now if the viewport has been flicked back again, and
4719 // that we can rebuild the table with a visible top-left cell.
4720 const auto syncView_d = syncView->d_func();
4721 if (!syncView_d->loadedItems.isEmpty()) {
4722 if (syncHorizontally && syncView_d->leftColumn() <= tableSize.width() - 1)
4723 scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::ViewportOnly;
4724 else if (syncVertically && syncView_d->topRow() <= tableSize.height() - 1)
4725 scheduledRebuildOptions |= QQuickTableViewPrivate::RebuildOption::ViewportOnly;
4726 }
4727 }
4728}
4729
4730void QQuickTableViewPrivate::syncPositionView()
4731{
4732 // Only positionViewAtRowAfterRebuild/positionViewAtColumnAfterRebuild are critical
4733 // to sync before a rebuild to avoid them being overwritten
4734 // by the setters while building. The other position properties
4735 // can change without it causing trouble.
4736 positionViewAtRowAfterRebuild = assignedPositionViewAtRowAfterRebuild;
4737 positionViewAtColumnAfterRebuild = assignedPositionViewAtColumnAfterRebuild;
4738}
4739
4740void QQuickTableViewPrivate::connectToModel()
4741{
4742 Q_Q(QQuickTableView);
4743 Q_TABLEVIEW_ASSERT(model, "");
4744
4745 QObjectPrivate::connect(model, &QQmlInstanceModel::createdItem, this, &QQuickTableViewPrivate::itemCreatedCallback);
4746 QObjectPrivate::connect(model, &QQmlInstanceModel::initItem, this, &QQuickTableViewPrivate::initItemCallback);
4747 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4748 QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4749
4750 // Connect atYEndChanged to a function that fetches data if more is available
4751 QObjectPrivate::connect(q, &QQuickTableView::atYEndChanged, this, &QQuickTableViewPrivate::fetchMoreData);
4752
4753 if (auto const aim = model->abstractItemModel()) {
4754 // When the model exposes a QAIM, we connect to it directly. This means that if the current model is
4755 // a QQmlDelegateModel, we just ignore all the change sets it emits. In most cases, the model will instead
4756 // be our own QQmlTableInstanceModel, which doesn't bother creating change sets at all. For models that are
4757 // not based on QAIM (like QQmlObjectModel, QQmlListModel, javascript arrays etc), there is currently no way
4758 // to modify the model at runtime without also re-setting the model on the view.
4759 connect(aim, &QAbstractItemModel::rowsMoved, this, &QQuickTableViewPrivate::rowsMovedCallback);
4760 connect(aim, &QAbstractItemModel::columnsMoved, this, &QQuickTableViewPrivate::columnsMovedCallback);
4761 connect(aim, &QAbstractItemModel::rowsInserted, this, &QQuickTableViewPrivate::rowsInsertedCallback);
4762 connect(aim, &QAbstractItemModel::rowsRemoved, this, &QQuickTableViewPrivate::rowsRemovedCallback);
4763 connect(aim, &QAbstractItemModel::columnsInserted, this, &QQuickTableViewPrivate::columnsInsertedCallback);
4764 connect(aim, &QAbstractItemModel::columnsRemoved, this, &QQuickTableViewPrivate::columnsRemovedCallback);
4765 connect(aim, &QAbstractItemModel::modelReset, this, &QQuickTableViewPrivate::modelResetCallback);
4766 connect(aim, &QAbstractItemModel::layoutChanged, this, &QQuickTableViewPrivate::layoutChangedCallback);
4767 connect(aim, &QAbstractItemModel::dataChanged, this, &QQuickTableViewPrivate::dataChangedCallback);
4768 } else {
4769 QObjectPrivate::connect(model, &QQmlInstanceModel::modelUpdated, this, &QQuickTableViewPrivate::modelUpdated);
4770 }
4771
4772 if (tableModel) {
4773 QObject::connect(tableModel, &QQmlTableInstanceModel::modelChanged,
4774 q, &QQuickTableView::modelChanged);
4775 }
4776}
4777
4778void QQuickTableViewPrivate::disconnectFromModel()
4779{
4780 Q_Q(QQuickTableView);
4781 Q_TABLEVIEW_ASSERT(model, "");
4782
4783 QObjectPrivate::disconnect(model, &QQmlInstanceModel::createdItem, this, &QQuickTableViewPrivate::itemCreatedCallback);
4784 QObjectPrivate::disconnect(model, &QQmlInstanceModel::initItem, this, &QQuickTableViewPrivate::initItemCallback);
4785 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback);
4786 QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback);
4787
4788 QObjectPrivate::disconnect(q, &QQuickTableView::atYEndChanged, this, &QQuickTableViewPrivate::fetchMoreData);
4789
4790 if (auto const aim = model->abstractItemModel()) {
4791 disconnect(aim, &QAbstractItemModel::rowsMoved, this, &QQuickTableViewPrivate::rowsMovedCallback);
4792 disconnect(aim, &QAbstractItemModel::columnsMoved, this, &QQuickTableViewPrivate::columnsMovedCallback);
4793 disconnect(aim, &QAbstractItemModel::rowsInserted, this, &QQuickTableViewPrivate::rowsInsertedCallback);
4794 disconnect(aim, &QAbstractItemModel::rowsRemoved, this, &QQuickTableViewPrivate::rowsRemovedCallback);
4795 disconnect(aim, &QAbstractItemModel::columnsInserted, this, &QQuickTableViewPrivate::columnsInsertedCallback);
4796 disconnect(aim, &QAbstractItemModel::columnsRemoved, this, &QQuickTableViewPrivate::columnsRemovedCallback);
4797 disconnect(aim, &QAbstractItemModel::modelReset, this, &QQuickTableViewPrivate::modelResetCallback);
4798 disconnect(aim, &QAbstractItemModel::layoutChanged, this, &QQuickTableViewPrivate::layoutChangedCallback);
4799 disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQuickTableViewPrivate::dataChangedCallback);
4800 } else {
4801 QObjectPrivate::disconnect(model, &QQmlInstanceModel::modelUpdated, this, &QQuickTableViewPrivate::modelUpdated);
4802 }
4803
4804 if (tableModel) {
4805 QObject::disconnect(tableModel, &QQmlTableInstanceModel::modelChanged,
4806 q, &QQuickTableView::modelChanged);
4807 }
4808}
4809
4810void QQuickTableViewPrivate::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
4811{
4812 Q_UNUSED(changeSet);
4813 Q_UNUSED(reset);
4814
4815 Q_TABLEVIEW_ASSERT(!model->abstractItemModel(), "");
4816 scheduleRebuildTable(RebuildOption::ViewportOnly
4817 | RebuildOption::CalculateNewContentWidth
4818 | RebuildOption::CalculateNewContentHeight);
4819}
4820
4821void QQuickTableViewPrivate::rowsMovedCallback(const QModelIndex &parent, int, int, const QModelIndex &, int )
4822{
4823 if (parent != QModelIndex())
4824 return;
4825
4826 scheduleRebuildTable(RebuildOption::ViewportOnly);
4827}
4828
4829void QQuickTableViewPrivate::columnsMovedCallback(const QModelIndex &parent, int, int, const QModelIndex &, int)
4830{
4831 if (parent != QModelIndex())
4832 return;
4833
4834 scheduleRebuildTable(RebuildOption::ViewportOnly);
4835}
4836
4837void QQuickTableViewPrivate::rowsInsertedCallback(const QModelIndex &parent, int, int)
4838{
4839 if (parent != QModelIndex())
4840 return;
4841
4842 scheduleRebuildTable(RebuildOption::ViewportOnly | RebuildOption::CalculateNewContentHeight);
4843}
4844
4845void QQuickTableViewPrivate::rowsRemovedCallback(const QModelIndex &parent, int, int)
4846{
4847 Q_Q(QQuickTableView);
4848
4849 if (parent != QModelIndex())
4850 return;
4851
4852 // If editIndex was a part of the removed rows, it will now be invalid.
4853 if (!editIndex.isValid() && editItem)
4854 q->closeEditor();
4855
4856 scheduleRebuildTable(RebuildOption::ViewportOnly | RebuildOption::CalculateNewContentHeight);
4857}
4858
4859void QQuickTableViewPrivate::columnsInsertedCallback(const QModelIndex &parent, int, int)
4860{
4861 if (parent != QModelIndex())
4862 return;
4863
4864 // Adding a column (or row) can result in the table going from being
4865 // e.g completely inside the viewport to go outside. And in the latter
4866 // case, the user needs to be able to scroll the viewport, also if
4867 // flags such as Flickable.StopAtBounds is in use. So we need to
4868 // update contentWidth to support that case.
4869 scheduleRebuildTable(RebuildOption::ViewportOnly | RebuildOption::CalculateNewContentWidth);
4870}
4871
4872void QQuickTableViewPrivate::columnsRemovedCallback(const QModelIndex &parent, int, int)
4873{
4874 Q_Q(QQuickTableView);
4875
4876 if (parent != QModelIndex())
4877 return;
4878
4879 // If editIndex was a part of the removed columns, it will now be invalid.
4880 if (!editIndex.isValid() && editItem)
4881 q->closeEditor();
4882
4883 scheduleRebuildTable(RebuildOption::ViewportOnly | RebuildOption::CalculateNewContentWidth);
4884}
4885
4886void QQuickTableViewPrivate::layoutChangedCallback(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
4887{
4888 Q_UNUSED(parents);
4889 Q_UNUSED(hint);
4890
4891 scheduleRebuildTable(RebuildOption::ViewportOnly);
4892}
4893
4894void QQuickTableViewPrivate::fetchMoreData()
4895{
4896 if (tableModel && tableModel->canFetchMore()) {
4897 tableModel->fetchMore();
4898 scheduleRebuildTable(RebuildOption::ViewportOnly);
4899 }
4900}
4901
4902void QQuickTableViewPrivate::modelResetCallback()
4903{
4904 Q_Q(QQuickTableView);
4905 q->closeEditor();
4906 scheduleRebuildTable(RebuildOption::All);
4907}
4908
4909void QQuickTableViewPrivate::dataChangedCallback(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles)
4910{
4911 const auto *chooser = qobject_cast<const QQmlDelegateChooser *>(assignedDelegate);
4912 if (!chooser)
4913 return;
4914
4915 if (loadedItems.isEmpty()
4916 || topLeft.column() > rightColumn() || bottomRight.column() < leftColumn()
4917 || topLeft.row() > bottomRow() || bottomRight.row() < topRow()) {
4918 return;
4919 }
4920
4921 if (!roles.empty()) {
4922 const int roleIndex = topLeft.model()->roleNames().key(chooser->role().toUtf8());
4923 if (!roles.contains(roleIndex))
4924 return;
4925 }
4926
4927 scheduleRebuildTable(RebuildOption::ViewportOnly);
4928}
4929
4930void QQuickTableViewPrivate::positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect)
4931{
4932 Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom);
4933 Q_TABLEVIEW_ASSERT(verticalAlignment, alignment);
4934
4935 if (syncVertically) {
4936 syncView->d_func()->positionViewAtRow(row, verticalAlignment, offset, subRect);
4937 } else {
4938 if (!scrollToRow(row, verticalAlignment, offset, subRect)) {
4939 // Could not scroll, so rebuild instead
4940 assignedPositionViewAtRowAfterRebuild = row;
4941 positionViewAtRowAlignment = verticalAlignment;
4942 positionViewAtRowOffset = offset;
4943 positionViewAtRowSubRect = subRect;
4944 scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly |
4945 QQuickTableViewPrivate::RebuildOption::PositionViewAtRow);
4946 }
4947 }
4948}
4949
4950void QQuickTableViewPrivate::positionViewAtColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect)
4951{
4952 Qt::Alignment horizontalAlignment = alignment & (Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight);
4953 Q_TABLEVIEW_ASSERT(horizontalAlignment, alignment);
4954
4955 if (syncHorizontally) {
4956 syncView->d_func()->positionViewAtColumn(column, horizontalAlignment, offset, subRect);
4957 } else {
4958 if (!scrollToColumn(column, horizontalAlignment, offset, subRect)) {
4959 // Could not scroll, so rebuild instead
4960 assignedPositionViewAtColumnAfterRebuild = column;
4961 positionViewAtColumnAlignment = horizontalAlignment;
4962 positionViewAtColumnOffset = offset;
4963 positionViewAtColumnSubRect = subRect;
4964 scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly |
4965 QQuickTableViewPrivate::RebuildOption::PositionViewAtColumn);
4966 }
4967 }
4968}
4969
4970bool QQuickTableViewPrivate::scrollToRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect)
4971{
4972 Q_Q(QQuickTableView);
4973
4974 // This function will only scroll to rows that are loaded (since we
4975 // don't know the location of unloaded rows). But as an exception, to
4976 // allow moving currentIndex out of the viewport, we support scrolling
4977 // to a row that is adjacent to the loaded table. So start by checking
4978 // if we should load en extra row.
4979 if (row < topRow()) {
4980 if (row != nextVisibleEdgeIndex(Qt::TopEdge, topRow() - 1))
4981 return false;
4982 loadEdge(Qt::TopEdge, QQmlIncubator::Synchronous);
4983 } else if (row > bottomRow()) {
4984 if (row != nextVisibleEdgeIndex(Qt::BottomEdge, bottomRow() + 1))
4985 return false;
4986 loadEdge(Qt::BottomEdge, QQmlIncubator::Synchronous);
4987 } else if (row < topRow() || row > bottomRow()) {
4988 return false;
4989 }
4990
4991 if (!loadedRows.contains(row))
4992 return false;
4993
4994 const qreal newContentY = getAlignmentContentY(row, alignment, offset, subRect);
4995 if (qFuzzyCompare(newContentY, q->contentY()))
4996 return true;
4997
4998 if (animate) {
4999 const qreal diffY = qAbs(newContentY - q->contentY());
5000 const qreal duration = qBound(700., diffY * 5, 1500.);
5001 positionYAnimation.setTo(newContentY);
5002 positionYAnimation.setDuration(duration);
5003 positionYAnimation.restart();
5004 } else {
5005 positionYAnimation.stop();
5006 q->setContentY(newContentY);
5007 }
5008
5009 return true;
5010}
5011
5012bool QQuickTableViewPrivate::scrollToColumn(int column, Qt::Alignment alignment, qreal offset, const QRectF subRect)
5013{
5014 Q_Q(QQuickTableView);
5015
5016 // This function will only scroll to columns that are loaded (since we
5017 // don't know the location of unloaded columns). But as an exception, to
5018 // allow moving currentIndex out of the viewport, we support scrolling
5019 // to a column that is adjacent to the loaded table. So start by checking
5020 // if we should load en extra column.
5021 if (column < leftColumn()) {
5022 if (column != nextVisibleEdgeIndex(Qt::LeftEdge, leftColumn() - 1))
5023 return false;
5024 loadEdge(Qt::LeftEdge, QQmlIncubator::Synchronous);
5025 } else if (column > rightColumn()) {
5026 if (column != nextVisibleEdgeIndex(Qt::RightEdge, rightColumn() + 1))
5027 return false;
5028 loadEdge(Qt::RightEdge, QQmlIncubator::Synchronous);
5029 } else if (column < leftColumn() || column > rightColumn()) {
5030 return false;
5031 }
5032
5033 if (!loadedColumns.contains(column))
5034 return false;
5035
5036 const qreal newContentX = getAlignmentContentX(column, alignment, offset, subRect);
5037 if (qFuzzyCompare(newContentX, q->contentX()))
5038 return true;
5039
5040 if (animate) {
5041 const qreal diffX = qAbs(newContentX - q->contentX());
5042 const qreal duration = qBound(700., diffX * 5, 1500.);
5043 positionXAnimation.setTo(newContentX);
5044 positionXAnimation.setDuration(duration);
5045 positionXAnimation.restart();
5046 } else {
5047 positionXAnimation.stop();
5048 q->setContentX(newContentX);
5049 }
5050
5051 return true;
5052}
5053
5054void QQuickTableViewPrivate::scheduleRebuildIfFastFlick()
5055{
5056 Q_Q(QQuickTableView);
5057 // If the viewport has moved more than one page vertically or horizontally, we switch
5058 // strategy from refilling edges around the current table to instead rebuild the table
5059 // from scratch inside the new viewport. This will greatly improve performance when flicking
5060 // a long distance in one go, which can easily happen when dragging on scrollbars.
5061 // Note that we don't want to update the content size in this case, since first of all, the
5062 // content size should logically not change as a result of flicking. But more importantly, updating
5063 // the content size in combination with fast-flicking has a tendency to cause flicker in the viewport.
5064
5065 // Check the viewport moved more than one page vertically
5066 if (!viewportRect.intersects(QRectF(viewportRect.x(), q->contentY(), 1, q->height()))) {
5067 scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftRow;
5068 scheduledRebuildOptions |= RebuildOption::ViewportOnly;
5069 }
5070
5071 // Check the viewport moved more than one page horizontally
5072 if (!viewportRect.intersects(QRectF(q->contentX(), viewportRect.y(), q->width(), 1))) {
5073 scheduledRebuildOptions |= RebuildOption::CalculateNewTopLeftColumn;
5074 scheduledRebuildOptions |= RebuildOption::ViewportOnly;
5075 }
5076}
5077
5078void QQuickTableViewPrivate::setLocalViewportX(qreal contentX)
5079{
5080 // Set the new viewport position if changed, but don't trigger any
5081 // rebuilds or updates. We use this function internally to distinguish
5082 // external flicking from internal sync-ing of the content view.
5083 Q_Q(QQuickTableView);
5084 QScopedValueRollback blocker(inSetLocalViewportPos, true);
5085
5086 if (qFuzzyCompare(contentX, q->contentX()))
5087 return;
5088
5089 q->setContentX(contentX);
5090}
5091
5092void QQuickTableViewPrivate::setLocalViewportY(qreal contentY)
5093{
5094 // Set the new viewport position if changed, but don't trigger any
5095 // rebuilds or updates. We use this function internally to distinguish
5096 // external flicking from internal sync-ing of the content view.
5097 Q_Q(QQuickTableView);
5098 QScopedValueRollback blocker(inSetLocalViewportPos, true);
5099
5100 if (qFuzzyCompare(contentY, q->contentY()))
5101 return;
5102
5103 q->setContentY(contentY);
5104}
5105
5106void QQuickTableViewPrivate::syncViewportRect()
5107{
5108 // Sync viewportRect so that it contains the actual geometry of the viewport.
5109 // Since the column (and row) size of a sync child is decided by the column size
5110 // of its sync view, the viewport width of a sync view needs to be the maximum of
5111 // the sync views width, and its sync childrens width. This to ensure that no sync
5112 // child loads a column which is not yet loaded by the sync view, since then the
5113 // implicit column size cannot be resolved.
5114 Q_Q(QQuickTableView);
5115
5116 qreal w = q->width();
5117 qreal h = q->height();
5118
5119 for (auto syncChild : std::as_const(syncChildren)) {
5120 auto syncChild_d = syncChild->d_func();
5121 if (syncChild_d->syncHorizontally)
5122 w = qMax(w, syncChild->width());
5123 if (syncChild_d->syncVertically)
5124 h = qMax(h, syncChild->height());
5125 }
5126
5127 viewportRect = QRectF(q->contentX(), q->contentY(), w, h);
5128}
5129
5130void QQuickTableViewPrivate::init()
5131{
5132 Q_Q(QQuickTableView);
5133
5134 q->setFlag(QQuickItem::ItemIsFocusScope);
5135 q->setActiveFocusOnTab(true);
5136
5137 positionXAnimation.setTargetObject(q);
5138 positionXAnimation.setProperty(QStringLiteral("contentX"));
5139 positionXAnimation.setEasing(QEasingCurve::OutQuart);
5140
5141 positionYAnimation.setTargetObject(q);
5142 positionYAnimation.setProperty(QStringLiteral("contentY"));
5143 positionYAnimation.setEasing(QEasingCurve::OutQuart);
5144
5145 auto tapHandler = new QQuickTableViewTapHandler(q);
5146
5147 hoverHandler = new QQuickTableViewHoverHandler(q);
5148 resizeHandler = new QQuickTableViewResizeHandler(q);
5149
5150 hoverHandler->setEnabled(resizableRows || resizableColumns);
5151 resizeHandler->setEnabled(resizableRows || resizableColumns);
5152
5153 // To allow for a more snappy UX, we try to change the current index already upon
5154 // receiving a pointer press. But we should only do that if the view is not interactive
5155 // (so that it doesn't interfere with flicking), and if the resizeHandler is not
5156 // being hovered/dragged. For those cases, we fall back to setting the current index
5157 // on tap instead. A double tap on a resize area should also revert the section size
5158 // back to its implicit size.
5159 QObject::connect(tapHandler, &QQuickTapHandler::pressedChanged, q, [this, q, tapHandler] {
5160 if (!tapHandler->isPressed())
5161 return;
5162
5163 positionXAnimation.stop();
5164 positionYAnimation.stop();
5165
5166 if (!q->isInteractive())
5167 handleTap(tapHandler->point());
5168 });
5169
5170 QObject::connect(tapHandler, &QQuickTapHandler::singleTapped, q, [this, q, tapHandler] {
5171 if (q->isInteractive())
5172 handleTap(tapHandler->point());
5173 });
5174
5175 QObject::connect(tapHandler, &QQuickTapHandler::doubleTapped, q, [this, q, tapHandler] {
5176 const bool resizeRow = resizableRows && hoverHandler->m_row != -1;
5177 const bool resizeColumn = resizableColumns && hoverHandler->m_column != -1;
5178
5179 if (resizeRow || resizeColumn) {
5180 if (resizeRow)
5181 q->setRowHeight(hoverHandler->m_row, -1);
5182 if (resizeColumn)
5183 q->setColumnWidth(hoverHandler->m_column, -1);
5184 } else if (editTriggers & QQuickTableView::DoubleTapped) {
5185 const QPointF pos = tapHandler->point().pressPosition();
5186 const QPoint cell = q->cellAtPosition(pos);
5187 const QModelIndex index = q->modelIndex(cell);
5188 if (canEdit(index, false))
5189 q->edit(index);
5190 }
5191 });
5192}
5193
5194void QQuickTableViewPrivate::handleTap(const QQuickHandlerPoint &point)
5195{
5196 Q_Q(QQuickTableView);
5197
5198 if (keyNavigationEnabled)
5199 q->forceActiveFocus(Qt::MouseFocusReason);
5200
5201 if (point.modifiers() != Qt::NoModifier)
5202 return;
5203 if (resizableRows && hoverHandler->m_row != -1)
5204 return;
5205 if (resizableColumns && hoverHandler->m_column != -1)
5206 return;
5207 if (resizeHandler->state() != QQuickTableViewResizeHandler::Listening)
5208 return;
5209
5210 const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
5211 bool tappedCellIsSelected = false;
5212
5213 if (selectionModel)
5214 tappedCellIsSelected = selectionModel->isSelected(tappedIndex);
5215
5216 if (canEdit(tappedIndex, false)) {
5217 if (editTriggers & QQuickTableView::SingleTapped) {
5218 if (selectionBehavior != QQuickTableView::SelectionDisabled)
5219 clearSelection();
5220 q->edit(tappedIndex);
5221 return;
5222 } else if (editTriggers & QQuickTableView::SelectedTapped && tappedCellIsSelected) {
5223 q->edit(tappedIndex);
5224 return;
5225 }
5226 }
5227
5228 // Since the tap didn't result in selecting or editing cells, we clear
5229 // the current selection and move the current index instead.
5230 if (pointerNavigationEnabled) {
5231 closeEditorAndCommit();
5232 if (selectionBehavior != QQuickTableView::SelectionDisabled) {
5233 clearSelection();
5234 cancelSelectionTracking();
5235 }
5236 setCurrentIndexFromTap(point.position());
5237 }
5238}
5239
5240bool QQuickTableViewPrivate::canEdit(const QModelIndex tappedIndex, bool warn)
5241{
5242 // Check that a call to edit(tappedIndex) would not
5243 // result in warnings being printed.
5244 Q_Q(QQuickTableView);
5245
5246 if (!tappedIndex.isValid()) {
5247 if (warn)
5248 qmlWarning(q) << "cannot edit: index is not valid!";
5249 return false;
5250 }
5251
5252 if (auto const sourceModel = qaim(modelImpl())) {
5253 if (!(sourceModel->flags(tappedIndex) & Qt::ItemIsEditable)) {
5254 if (warn)
5255 qmlWarning(q) << "cannot edit: QAbstractItemModel::flags(index) doesn't contain Qt::ItemIsEditable";
5256 return false;
5257 }
5258 }
5259
5260 const QPoint cell = q->cellAtIndex(tappedIndex);
5261 const QQuickItem *cellItem = q->itemAtCell(cell);
5262 if (!cellItem) {
5263 if (warn)
5264 qmlWarning(q) << "cannot edit: the cell to edit is not inside the viewport!";
5265 return false;
5266 }
5267
5268 auto attached = getAttachedObject(cellItem);
5269 if (!attached || !attached->editDelegate()) {
5270 if (warn)
5271 qmlWarning(q) << "cannot edit: no TableView.editDelegate set!";
5272 return false;
5273 }
5274
5275 return true;
5276}
5277
5278void QQuickTableViewPrivate::syncViewportPosRecursive()
5279{
5280 Q_Q(QQuickTableView);
5281 QScopedValueRollback recursionGuard(inSyncViewportPosRecursive, true);
5282
5283 if (syncView) {
5284 auto syncView_d = syncView->d_func();
5285 if (!syncView_d->inSyncViewportPosRecursive) {
5286 if (syncHorizontally)
5287 syncView_d->setLocalViewportX(q->contentX());
5288 if (syncVertically)
5289 syncView_d->setLocalViewportY(q->contentY());
5290 syncView_d->syncViewportPosRecursive();
5291 }
5292 }
5293
5294 for (auto syncChild : std::as_const(syncChildren)) {
5295 auto syncChild_d = syncChild->d_func();
5296 if (!syncChild_d->inSyncViewportPosRecursive) {
5297 if (syncChild_d->syncHorizontally)
5298 syncChild_d->setLocalViewportX(q->contentX());
5299 if (syncChild_d->syncVertically)
5300 syncChild_d->setLocalViewportY(q->contentY());
5301 syncChild_d->syncViewportPosRecursive();
5302 }
5303 }
5304}
5305
5306void QQuickTableViewPrivate::setCurrentIndexFromTap(const QPointF &pos)
5307{
5308 Q_Q(QQuickTableView);
5309
5310 const QPoint cell = q->cellAtPosition(pos);
5311 if (!cellIsValid(cell))
5312 return;
5313
5314 setCurrentIndex(cell);
5315}
5316
5317void QQuickTableViewPrivate::setCurrentIndex(const QPoint &cell)
5318{
5319 if (!selectionModel)
5320 return;
5321
5322 const auto index = q_func()->modelIndex(cell);
5323 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
5324}
5325
5326bool QQuickTableViewPrivate::setCurrentIndexFromKeyEvent(QKeyEvent *e)
5327{
5328 Q_Q(QQuickTableView);
5329
5330 if (!selectionModel || !selectionModel->model())
5331 return false;
5332
5333 const QModelIndex currentIndex = selectionModel->currentIndex();
5334 const QPoint currentCell = q->cellAtIndex(currentIndex);
5335
5336 if (!q->activeFocusOnTab()) {
5337 switch (e->key()) {
5338 case Qt::Key_Tab:
5339 case Qt::Key_Backtab:
5340 return false;
5341 }
5342 }
5343
5344 if (!cellIsValid(currentCell)) {
5345 switch (e->key()) {
5346 case Qt::Key_Up:
5347 case Qt::Key_Down:
5348 case Qt::Key_Left:
5349 case Qt::Key_Right:
5350 case Qt::Key_PageUp:
5351 case Qt::Key_PageDown:
5352 case Qt::Key_Home:
5353 case Qt::Key_End:
5354 case Qt::Key_Tab:
5355 case Qt::Key_Backtab:
5356 if (!loadedRows.isEmpty() && !loadedColumns.isEmpty()) {
5357 // Special case: the current index doesn't map to a cell in the view (perhaps
5358 // because it isn't set yet). In that case, we set it to be the top-left cell.
5359 const QModelIndex topLeftIndex = q->index(topRow(), leftColumn());
5360 selectionModel->setCurrentIndex(topLeftIndex, QItemSelectionModel::NoUpdate);
5361 return true;
5362 }
5363 }
5364 return false;
5365 }
5366
5367 auto beginMoveCurrentIndex = [&](){
5368 const bool shouldSelect = (e->modifiers() & Qt::ShiftModifier) && (e->key() != Qt::Key_Backtab);
5369 const bool startNewSelection = selectionRectangle().isEmpty();
5370 if (!shouldSelect) {
5371 clearSelection();
5372 cancelSelectionTracking();
5373 } else if (startNewSelection) {
5374 // Try to start a new selection if no selection exists from before.
5375 // The startSelection() call is theoretically allowed to refuse, although this
5376 // is less likely when starting a selection using the keyboard.
5377 const int serializedStartIndex = modelIndexToCellIndex(selectionModel->currentIndex());
5378 if (loadedItems.contains(serializedStartIndex)) {
5379 const QRectF startGeometry = loadedItems.value(serializedStartIndex)->geometry();
5380 if (startSelection(startGeometry.center(), Qt::ShiftModifier)) {
5381 setSelectionStartPos(startGeometry.center());
5382 if (selectableCallbackFunction)
5383 selectableCallbackFunction(QQuickSelectable::CallBackFlag::SelectionRectangleChanged);
5384 }
5385 }
5386 }
5387 };
5388
5389 auto endMoveCurrentIndex = [&](const QPoint &cell){
5390 const bool isSelecting = selectionFlag != QItemSelectionModel::NoUpdate;
5391 if (isSelecting) {
5392 if (polishScheduled)
5393 forceLayout(true);
5394 const int serializedEndIndex = modelIndexAtCell(cell);
5395 if (loadedItems.contains(serializedEndIndex)) {
5396 const QRectF endGeometry = loadedItems.value(serializedEndIndex)->geometry();
5397 setSelectionEndPos(endGeometry.center());
5398 if (selectableCallbackFunction)
5399 selectableCallbackFunction(QQuickSelectable::CallBackFlag::SelectionRectangleChanged);
5400 }
5401 }
5402 selectionModel->setCurrentIndex(q->modelIndex(cell), QItemSelectionModel::NoUpdate);
5403 };
5404
5405 switch (e->key()) {
5406 case Qt::Key_Up: {
5407 beginMoveCurrentIndex();
5408 const int nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5409 if (nextRow == kEdgeIndexAtEnd)
5410 break;
5411 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5412 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5413 endMoveCurrentIndex({currentCell.x(), nextRow});
5414 break; }
5415 case Qt::Key_Down: {
5416 beginMoveCurrentIndex();
5417 const int nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5418 if (nextRow == kEdgeIndexAtEnd)
5419 break;
5420 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5421 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5422 endMoveCurrentIndex({currentCell.x(), nextRow});
5423 break; }
5424 case Qt::Key_Left: {
5425 beginMoveCurrentIndex();
5426 const int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5427 if (nextColumn == kEdgeIndexAtEnd)
5428 break;
5429 const qreal marginX = atTableEnd(Qt::LeftEdge, nextColumn - 1) ? -q->leftMargin() : 0;
5430 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5431 endMoveCurrentIndex({nextColumn, currentCell.y()});
5432 break; }
5433 case Qt::Key_Right: {
5434 beginMoveCurrentIndex();
5435 const int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5436 if (nextColumn == kEdgeIndexAtEnd)
5437 break;
5438 const qreal marginX = atTableEnd(Qt::RightEdge, nextColumn + 1) ? q->rightMargin() : 0;
5439 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5440 endMoveCurrentIndex({nextColumn, currentCell.y()});
5441 break; }
5442 case Qt::Key_PageDown: {
5443 int newBottomRow = -1;
5444 beginMoveCurrentIndex();
5445 if (currentCell.y() < bottomRow()) {
5446 // The first PageDown should just move currentIndex to the bottom
5447 newBottomRow = bottomRow();
5448 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignBottom, 0);
5449 } else {
5450 q->positionViewAtRow(bottomRow(), QQuickTableView::AlignTop, 0);
5451 positionYAnimation.complete();
5452 newBottomRow = topRow() != bottomRow() ? bottomRow() : bottomRow() + 1;
5453 const qreal marginY = atTableEnd(Qt::BottomEdge, newBottomRow + 1) ? q->bottomMargin() : 0;
5454 q->positionViewAtRow(newBottomRow, QQuickTableView::AlignTop | QQuickTableView::AlignBottom, marginY);
5455 positionYAnimation.complete();
5456 }
5457 endMoveCurrentIndex(QPoint(currentCell.x(), newBottomRow));
5458 break; }
5459 case Qt::Key_PageUp: {
5460 int newTopRow = -1;
5461 beginMoveCurrentIndex();
5462 if (currentCell.y() > topRow()) {
5463 // The first PageUp should just move currentIndex to the top
5464 newTopRow = topRow();
5465 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, 0);
5466 } else {
5467 q->positionViewAtRow(topRow(), QQuickTableView::AlignBottom, 0);
5468 positionYAnimation.complete();
5469 newTopRow = topRow() != bottomRow() ? topRow() : topRow() - 1;
5470 const qreal marginY = atTableEnd(Qt::TopEdge, newTopRow - 1) ? -q->topMargin() : 0;
5471 q->positionViewAtRow(newTopRow, QQuickTableView::AlignTop, marginY);
5472 positionYAnimation.complete();
5473 }
5474 endMoveCurrentIndex(QPoint(currentCell.x(), newTopRow));
5475 break; }
5476 case Qt::Key_Home: {
5477 beginMoveCurrentIndex();
5478 const int firstColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5479 q->positionViewAtColumn(firstColumn, QQuickTableView::AlignLeft, -q->leftMargin());
5480 endMoveCurrentIndex(QPoint(firstColumn, currentCell.y()));
5481 break; }
5482 case Qt::Key_End: {
5483 beginMoveCurrentIndex();
5484 const int lastColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5485 q->positionViewAtColumn(lastColumn, QQuickTableView::AlignRight, q->rightMargin());
5486 endMoveCurrentIndex(QPoint(lastColumn, currentCell.y()));
5487 break; }
5488 case Qt::Key_Tab: {
5489 beginMoveCurrentIndex();
5490 int nextRow = currentCell.y();
5491 int nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, currentCell.x() + 1);
5492 if (nextColumn == kEdgeIndexAtEnd) {
5493 nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, currentCell.y() + 1);
5494 if (nextRow == kEdgeIndexAtEnd)
5495 nextRow = nextVisibleEdgeIndex(Qt::BottomEdge, 0);
5496 nextColumn = nextVisibleEdgeIndex(Qt::RightEdge, 0);
5497 const qreal marginY = atTableEnd(Qt::BottomEdge, nextRow + 1) ? q->bottomMargin() : 0;
5498 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5499 }
5500
5501 qreal marginX = 0;
5502 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5503 marginX = q->leftMargin();
5504 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5505 marginX = -q->leftMargin();
5506
5507 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5508 endMoveCurrentIndex({nextColumn, nextRow});
5509 break; }
5510 case Qt::Key_Backtab: {
5511 beginMoveCurrentIndex();
5512 int nextRow = currentCell.y();
5513 int nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, currentCell.x() - 1);
5514 if (nextColumn == kEdgeIndexAtEnd) {
5515 nextRow = nextVisibleEdgeIndex(Qt::TopEdge, currentCell.y() - 1);
5516 if (nextRow == kEdgeIndexAtEnd)
5517 nextRow = nextVisibleEdgeIndex(Qt::TopEdge, tableSize.height() - 1);
5518 nextColumn = nextVisibleEdgeIndex(Qt::LeftEdge, tableSize.width() - 1);
5519 const qreal marginY = atTableEnd(Qt::TopEdge, nextRow - 1) ? -q->topMargin() : 0;
5520 q->positionViewAtRow(nextRow, QQuickTableView::Contain, marginY);
5521 }
5522
5523 qreal marginX = 0;
5524 if (atTableEnd(Qt::RightEdge, nextColumn + 1))
5525 marginX = q->leftMargin();
5526 else if (atTableEnd(Qt::LeftEdge, nextColumn - 1))
5527 marginX = -q->leftMargin();
5528
5529 q->positionViewAtColumn(nextColumn, QQuickTableView::Contain, marginX);
5530 endMoveCurrentIndex({nextColumn, nextRow});
5531 break; }
5532 default:
5533 return false;
5534 }
5535
5536 return true;
5537}
5538
5539bool QQuickTableViewPrivate::editFromKeyEvent(QKeyEvent *e)
5540{
5541 Q_Q(QQuickTableView);
5542
5543 if (editTriggers == QQuickTableView::NoEditTriggers)
5544 return false;
5545 if (!selectionModel || !selectionModel->model())
5546 return false;
5547
5548 const QModelIndex index = selectionModel->currentIndex();
5549 const QPoint cell = q->cellAtIndex(index);
5550 const QQuickItem *cellItem = q->itemAtCell(cell);
5551 if (!cellItem)
5552 return false;
5553
5554 auto attached = getAttachedObject(cellItem);
5555 if (!attached || !attached->editDelegate())
5556 return false;
5557
5558 bool anyKeyPressed = false;
5559 bool editKeyPressed = false;
5560
5561 switch (e->key()) {
5562 case Qt::Key_Return:
5563 case Qt::Key_Enter:
5564#ifndef Q_OS_MACOS
5565 case Qt::Key_F2:
5566#endif
5567 anyKeyPressed = true;
5568 editKeyPressed = true;
5569 break;
5570 case Qt::Key_Shift:
5571 case Qt::Key_Alt:
5572 case Qt::Key_Control:
5573 case Qt::Key_Meta:
5574 case Qt::Key_Tab:
5575 case Qt::Key_Backtab:
5576 break;
5577 default:
5578 anyKeyPressed = true;
5579 }
5580
5581 const bool anyKeyAccepted = anyKeyPressed && (editTriggers & QQuickTableView::AnyKeyPressed);
5582 const bool editKeyAccepted = editKeyPressed && (editTriggers & QQuickTableView::EditKeyPressed);
5583
5584 if (!(editKeyAccepted || anyKeyAccepted))
5585 return false;
5586
5587 if (!canEdit(index, false)) {
5588 // If canEdit() returns false at this point (e.g because currentIndex is not
5589 // editable), we still want to eat the key event, to keep a consistent behavior
5590 // when some cells are editable, but others not.
5591 return true;
5592 }
5593
5594 q->edit(index);
5595
5596 if (editIndex.isValid() && anyKeyAccepted && !editKeyPressed) {
5597 // Replay the key event to the focus object (which should at this point
5598 // be the edit item, or an item inside the edit item).
5599 QGuiApplication::sendEvent(QGuiApplication::focusObject(), e);
5600 }
5601
5602 return true;
5603}
5604
5605QObject *QQuickTableViewPrivate::installEventFilterOnFocusObjectInsideEditItem()
5606{
5607 // If the current focus object is inside the edit item, install an event filter
5608 // on it to handle Enter, Tab, and FocusOut. Note that the focusObject doesn't
5609 // need to be the editItem itself, in case the editItem is a FocusScope.
5610 // Return the focus object that we filter, or nullptr otherwise.
5611 Q_Q(QQuickTableView);
5612 if (QObject *focusObject = editItem->window()->focusObject()) {
5613 QQuickItem *focusItem = qobject_cast<QQuickItem *>(focusObject);
5614 if (focusItem == editItem || editItem->isAncestorOf(focusItem)) {
5615 focusItem->installEventFilter(q);
5616 return focusItem;
5617 }
5618 }
5619 return nullptr;
5620}
5621
5622void QQuickTableViewPrivate::closeEditorAndCommit()
5623{
5624 if (!editItem)
5625 return;
5626
5627 if (auto attached = getAttachedObject(editItem))
5628 emit attached->commit();
5629
5630 q_func()->closeEditor();
5631}
5632
5633#if QT_CONFIG(cursor)
5634void QQuickTableViewPrivate::updateCursor()
5635{
5636 int row = resizableRows ? hoverHandler->m_row : -1;
5637 int column = resizableColumns ? hoverHandler->m_column : -1;
5638
5639 const auto resizeState = resizeHandler->state();
5640 if (resizeState == QQuickTableViewResizeHandler::DraggingStarted
5641 || resizeState == QQuickTableViewResizeHandler::Dragging) {
5642 // Don't change the cursor while resizing, even if
5643 // the pointer is not actually hovering the grid.
5644 row = resizeHandler->m_row;
5645 column = resizeHandler->m_column;
5646 }
5647
5648 if (row != -1 || column != -1) {
5649 Qt::CursorShape shape;
5650 if (row != -1 && column != -1)
5651 shape = Qt::SizeFDiagCursor;
5652 else if (row != -1)
5653 shape = Qt::SplitVCursor;
5654 else
5655 shape = Qt::SplitHCursor;
5656
5657 if (m_cursorSet)
5658 qApp->changeOverrideCursor(shape);
5659 else
5660 qApp->setOverrideCursor(shape);
5661
5662 m_cursorSet = true;
5663 } else if (m_cursorSet) {
5664 qApp->restoreOverrideCursor();
5665 m_cursorSet = false;
5666 }
5667}
5668#endif
5669
5670void QQuickTableViewPrivate::updateEditItem()
5671{
5672 Q_Q(QQuickTableView);
5673
5674 if (!editItem)
5675 return;
5676
5677 const QPoint cell = q->cellAtIndex(editIndex);
5678 auto cellItem = q->itemAtCell(cell);
5679 if (!cellItem) {
5680 // The delegate item that is being edited has left the viewport. But since we
5681 // added an extra reference to it when editing began, the delegate item has
5682 // not been unloaded! It's therefore still on the content item (outside the
5683 // viewport), but its position will no longer be updated until the row and column
5684 // it's a part of enters the viewport again. To avoid glitches related to the
5685 // item showing up on wrong places (e.g after resizing a column in front of it),
5686 // we move it far out of the viewport. This way it will be "hidden", but continue
5687 // to have edit focus. When the row and column that it's a part of are eventually
5688 // flicked back in again, a relayout will move it back to the correct place.
5689 editItem->parentItem()->setX(-editItem->width() - 10000);
5690 }
5691}
5692
5693QQuickTableView::QQuickTableView(QQuickItem *parent)
5694 : QQuickFlickable(*(new QQuickTableViewPrivate), parent)
5695{
5696 d_func()->init();
5697}
5698
5699QQuickTableView::QQuickTableView(QQuickTableViewPrivate &dd, QQuickItem *parent)
5700 : QQuickFlickable(dd, parent)
5701{
5702 d_func()->init();
5703}
5704
5705QQuickTableView::~QQuickTableView()
5706{
5707 Q_D(QQuickTableView);
5708
5709 if (d->syncView) {
5710 // Remove this TableView as a sync child from the syncView
5711 auto syncView_d = d->syncView->d_func();
5712 syncView_d->syncChildren.removeOne(this);
5713 syncView_d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
5714 }
5715}
5716
5717void QQuickTableView::componentFinalized()
5718{
5719 // componentComplete() is called on us after all static values have been assigned, but
5720 // before bindings to any anchestors has been evaluated. Especially this means that
5721 // if our size is bound to the parents size, it will still be empty at that point.
5722 // And we cannot build the table without knowing our own size. We could wait until we
5723 // got the first updatePolish() callback, but at that time, any asynchronous loaders that we
5724 // might be inside have already finished loading, which means that we would load all
5725 // the delegate items synchronously instead of asynchronously. We therefore use componentFinalized
5726 // which gets called after all the bindings we rely on has been evaluated.
5727 // When receiving this call, we load the delegate items (and build the table).
5728
5729 // Now that all bindings are evaluated, and we know
5730 // our final geometery, we can build the table.
5731 Q_D(QQuickTableView);
5732 qCDebug(lcTableViewDelegateLifecycle);
5733 d->updatePolish();
5734}
5735
5736qreal QQuickTableView::minXExtent() const
5737{
5738 return QQuickFlickable::minXExtent() - d_func()->origin.x();
5739}
5740
5741qreal QQuickTableView::maxXExtent() const
5742{
5743 return QQuickFlickable::maxXExtent() - d_func()->endExtent.width();
5744}
5745
5746qreal QQuickTableView::minYExtent() const
5747{
5748 return QQuickFlickable::minYExtent() - d_func()->origin.y();
5749}
5750
5751qreal QQuickTableView::maxYExtent() const
5752{
5753 return QQuickFlickable::maxYExtent() - d_func()->endExtent.height();
5754}
5755
5756int QQuickTableView::rows() const
5757{
5758 return d_func()->tableSize.height();
5759}
5760
5761int QQuickTableView::columns() const
5762{
5763 return d_func()->tableSize.width();
5764}
5765
5766qreal QQuickTableView::rowSpacing() const
5767{
5768 return d_func()->cellSpacing.height();
5769}
5770
5771void QQuickTableView::setRowSpacing(qreal spacing)
5772{
5773 Q_D(QQuickTableView);
5774 if (qt_is_nan(spacing) || !qt_is_finite(spacing))
5775 return;
5776 if (qFuzzyCompare(d->cellSpacing.height(), spacing))
5777 return;
5778
5779 d->cellSpacing.setHeight(spacing);
5780 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::LayoutOnly
5781 | QQuickTableViewPrivate::RebuildOption::CalculateNewContentHeight);
5782 emit rowSpacingChanged();
5783}
5784
5785qreal QQuickTableView::columnSpacing() const
5786{
5787 return d_func()->cellSpacing.width();
5788}
5789
5790void QQuickTableView::setColumnSpacing(qreal spacing)
5791{
5792 Q_D(QQuickTableView);
5793 if (qt_is_nan(spacing) || !qt_is_finite(spacing))
5794 return;
5795 if (qFuzzyCompare(d->cellSpacing.width(), spacing))
5796 return;
5797
5798 d->cellSpacing.setWidth(spacing);
5799 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::LayoutOnly
5800 | QQuickTableViewPrivate::RebuildOption::CalculateNewContentWidth);
5801 emit columnSpacingChanged();
5802}
5803
5804QJSValue QQuickTableView::rowHeightProvider() const
5805{
5806 return d_func()->rowHeightProvider;
5807}
5808
5809void QQuickTableView::setRowHeightProvider(const QJSValue &provider)
5810{
5811 Q_D(QQuickTableView);
5812 if (provider.strictlyEquals(d->rowHeightProvider))
5813 return;
5814
5815 d->rowHeightProvider = provider;
5816 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly
5817 | QQuickTableViewPrivate::RebuildOption::CalculateNewContentHeight);
5818 emit rowHeightProviderChanged();
5819}
5820
5821QJSValue QQuickTableView::columnWidthProvider() const
5822{
5823 return d_func()->columnWidthProvider;
5824}
5825
5826void QQuickTableView::setColumnWidthProvider(const QJSValue &provider)
5827{
5828 Q_D(QQuickTableView);
5829 if (provider.strictlyEquals(d->columnWidthProvider))
5830 return;
5831
5832 d->columnWidthProvider = provider;
5833 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly
5834 | QQuickTableViewPrivate::RebuildOption::CalculateNewContentWidth);
5835 emit columnWidthProviderChanged();
5836}
5837
5838QVariant QQuickTableView::model() const
5839{
5840 return d_func()->modelImpl();
5841}
5842
5843void QQuickTableView::setModel(const QVariant &newModel)
5844{
5845 Q_D(QQuickTableView);
5846
5847 QVariant model = newModel;
5848 if (model.userType() == qMetaTypeId<QJSValue>())
5849 model = model.value<QJSValue>().toVariant();
5850
5851 if (model == d->assignedModel)
5852 return;
5853
5854 closeEditor();
5855 d->setModelImpl(model);
5856 if (d->selectionModel)
5857 d->selectionModel->setModel(d->selectionSourceModel());
5858}
5859
5860QQmlComponent *QQuickTableView::delegate() const
5861{
5862 return d_func()->assignedDelegate;
5863}
5864
5865void QQuickTableView::setDelegate(QQmlComponent *newDelegate)
5866{
5867 Q_D(QQuickTableView);
5868 if (newDelegate == d->assignedDelegate)
5869 return;
5870
5871 d->assignedDelegate = newDelegate;
5872 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
5873
5874 emit delegateChanged();
5875}
5876
5877QQuickTableView::EditTriggers QQuickTableView::editTriggers() const
5878{
5879 return d_func()->editTriggers;
5880}
5881
5882void QQuickTableView::setEditTriggers(QQuickTableView::EditTriggers editTriggers)
5883{
5884 Q_D(QQuickTableView);
5885 if (editTriggers == d->editTriggers)
5886 return;
5887
5888 d->editTriggers = editTriggers;
5889
5890 emit editTriggersChanged();
5891}
5892
5893/*!
5894 \qmlproperty enumeration QtQuick::TableView::delegateModelAccess
5895 \since 6.10
5896
5897 \include delegatemodelaccess.qdocinc
5898*/
5899QQmlDelegateModel::DelegateModelAccess QQuickTableView::delegateModelAccess() const
5900{
5901 Q_D(const QQuickTableView);
5902 return d->assignedDelegateModelAccess;
5903}
5904
5905void QQuickTableView::setDelegateModelAccess(
5906 QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
5907{
5908 Q_D(QQuickTableView);
5909 if (delegateModelAccess == d->assignedDelegateModelAccess)
5910 return;
5911
5912 d->assignedDelegateModelAccess = delegateModelAccess;
5913 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
5914
5915 emit delegateModelAccessChanged();
5916}
5917
5918bool QQuickTableView::reuseItems() const
5919{
5920 return bool(d_func()->reusableFlag == QQmlTableInstanceModel::Reusable);
5921}
5922
5923void QQuickTableView::setReuseItems(bool reuse)
5924{
5925 Q_D(QQuickTableView);
5926 if (reuseItems() == reuse)
5927 return;
5928
5929 d->reusableFlag = reuse ? QQmlTableInstanceModel::Reusable : QQmlTableInstanceModel::NotReusable;
5930
5931 if (!reuse && d->tableModel) {
5932 // When we're told to not reuse items, we
5933 // immediately, as documented, drain the pool.
5934 d->tableModel->drainReusableItemsPool(0);
5935 }
5936
5937 emit reuseItemsChanged();
5938}
5939
5940void QQuickTableView::setContentWidth(qreal width)
5941{
5942 Q_D(QQuickTableView);
5943 d->explicitContentWidth = width;
5944 QQuickFlickable::setContentWidth(width);
5945}
5946
5947void QQuickTableView::setContentHeight(qreal height)
5948{
5949 Q_D(QQuickTableView);
5950 d->explicitContentHeight = height;
5951 QQuickFlickable::setContentHeight(height);
5952}
5953
5954/*!
5955 \qmlproperty TableView QtQuick::TableView::syncView
5956
5957 If this property of a TableView is set to another TableView, both the
5958 tables will synchronize with regard to flicking, column widths/row heights,
5959 and spacing according to \l syncDirection.
5960
5961 If \l syncDirection contains \l {Qt::Horizontal}{Qt.Horizontal}, current
5962 tableView's column widths, column spacing, and horizontal flicking movement
5963 synchronizes with syncView's.
5964
5965 If \l syncDirection contains \l {Qt::Vertical}{Qt.Vertical}, current
5966 tableView's row heights, row spacing, and vertical flicking movement
5967 synchronizes with syncView's.
5968
5969 \sa syncDirection
5970*/
5971QQuickTableView *QQuickTableView::syncView() const
5972{
5973 return d_func()->assignedSyncView;
5974}
5975
5976void QQuickTableView::setSyncView(QQuickTableView *view)
5977{
5978 Q_D(QQuickTableView);
5979 if (d->assignedSyncView == view)
5980 return;
5981
5982 // Clear existing index mapping information maintained
5983 // in the current view
5984 d->clearIndexMapping();
5985
5986 d->assignedSyncView = view;
5987 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
5988
5989 emit syncViewChanged();
5990}
5991
5992/*!
5993 \qmlproperty Qt::Orientations QtQuick::TableView::syncDirection
5994
5995 If the \l syncView is set on a TableView, this property controls
5996 synchronization of flicking direction(s) for both tables. The default is \c
5997 {Qt.Horizontal | Qt.Vertical}, which means that if you flick either table
5998 in either direction, the other table is flicked the same amount in the
5999 same direction.
6000
6001 This property and \l syncView can be used to make two tableViews
6002 synchronize with each other smoothly in flicking regardless of the different
6003 overshoot/undershoot, velocity, acceleration/deceleration or rebound
6004 animation, and so on.
6005
6006 A typical use case is to make several headers flick along with the table.
6007
6008 \sa syncView
6009*/
6010Qt::Orientations QQuickTableView::syncDirection() const
6011{
6012 return d_func()->assignedSyncDirection;
6013}
6014
6015void QQuickTableView::setSyncDirection(Qt::Orientations direction)
6016{
6017 Q_D(QQuickTableView);
6018 if (d->assignedSyncDirection == direction)
6019 return;
6020
6021 d->assignedSyncDirection = direction;
6022 if (d->assignedSyncView)
6023 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
6024
6025 emit syncDirectionChanged();
6026}
6027
6028QItemSelectionModel *QQuickTableView::selectionModel() const
6029{
6030 return d_func()->selectionModel;
6031}
6032
6033void QQuickTableView::setSelectionModel(QItemSelectionModel *selectionModel)
6034{
6035 Q_D(QQuickTableView);
6036 if (d->selectionModel == selectionModel)
6037 return;
6038
6039 // Note: There is no need to rebuild the table when the selection model
6040 // changes, since selections only affect the internals of the delegate
6041 // items, and not the layout of the TableView.
6042
6043 if (d->selectionModel) {
6044 QQuickTableViewPrivate::disconnect(d->selectionModel, &QItemSelectionModel::selectionChanged,
6045 d, &QQuickTableViewPrivate::selectionChangedInSelectionModel);
6046 QQuickTableViewPrivate::disconnect(d->selectionModel, &QItemSelectionModel::currentChanged,
6047 d, &QQuickTableViewPrivate::currentChangedInSelectionModel);
6048 }
6049
6050 d->selectionModel = selectionModel;
6051
6052 if (d->selectionModel) {
6053 d->selectionModel->setModel(d->selectionSourceModel());
6054 QQuickTableViewPrivate::connect(d->selectionModel, &QItemSelectionModel::selectionChanged,
6055 d, &QQuickTableViewPrivate::selectionChangedInSelectionModel);
6056 QQuickTableViewPrivate::connect(d->selectionModel, &QItemSelectionModel::currentChanged,
6057 d, &QQuickTableViewPrivate::currentChangedInSelectionModel);
6058 }
6059
6060 d->updateSelectedOnAllDelegateItems();
6061
6062 emit selectionModelChanged();
6063}
6064
6065bool QQuickTableView::animate() const
6066{
6067 return d_func()->animate;
6068}
6069
6070void QQuickTableView::setAnimate(bool animate)
6071{
6072 Q_D(QQuickTableView);
6073 if (d->animate == animate)
6074 return;
6075
6076 d->animate = animate;
6077 if (!animate) {
6078 d->positionXAnimation.stop();
6079 d->positionYAnimation.stop();
6080 }
6081
6082 emit animateChanged();
6083}
6084
6085bool QQuickTableView::keyNavigationEnabled() const
6086{
6087 return d_func()->keyNavigationEnabled;
6088}
6089
6090void QQuickTableView::setKeyNavigationEnabled(bool enabled)
6091{
6092 Q_D(QQuickTableView);
6093 if (d->keyNavigationEnabled == enabled)
6094 return;
6095
6096 d->keyNavigationEnabled = enabled;
6097
6098 emit keyNavigationEnabledChanged();
6099}
6100
6101bool QQuickTableView::pointerNavigationEnabled() const
6102{
6103 return d_func()->pointerNavigationEnabled;
6104}
6105
6106void QQuickTableView::setPointerNavigationEnabled(bool enabled)
6107{
6108 Q_D(QQuickTableView);
6109 if (d->pointerNavigationEnabled == enabled)
6110 return;
6111
6112 d->pointerNavigationEnabled = enabled;
6113
6114 emit pointerNavigationEnabledChanged();
6115}
6116
6117int QQuickTableView::leftColumn() const
6118{
6119 Q_D(const QQuickTableView);
6120 return d->loadedItems.isEmpty() ? -1 : d_func()->leftColumn();
6121}
6122
6123int QQuickTableView::rightColumn() const
6124{
6125 Q_D(const QQuickTableView);
6126 return d->loadedItems.isEmpty() ? -1 : d_func()->rightColumn();
6127}
6128
6129int QQuickTableView::topRow() const
6130{
6131 Q_D(const QQuickTableView);
6132 return d->loadedItems.isEmpty() ? -1 : d_func()->topRow();
6133}
6134
6135int QQuickTableView::bottomRow() const
6136{
6137 Q_D(const QQuickTableView);
6138 return d->loadedItems.isEmpty() ? -1 : d_func()->bottomRow();
6139}
6140
6141int QQuickTableView::currentRow() const
6142{
6143 return d_func()->currentRow;
6144}
6145
6146int QQuickTableView::currentColumn() const
6147{
6148 return d_func()->currentColumn;
6149}
6150
6151void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset, const QRectF &subRect)
6152{
6153 Q_D(QQuickTableView);
6154 if (row < 0 || row >= rows() || d->loadedRows.isEmpty())
6155 return;
6156
6157 // Note: PositionMode::Contain is from here on translated to (Qt::AlignTop | Qt::AlignBottom).
6158 // This is an internal (unsupported) combination which means "align bottom if the whole cell
6159 // fits inside the viewport, otherwise align top".
6160
6161 if (mode & (AlignTop | AlignBottom | AlignVCenter)) {
6162 mode &= AlignTop | AlignBottom | AlignVCenter;
6163 d->positionViewAtRow(row, Qt::Alignment(int(mode)), offset, subRect);
6164 } else if (mode == Contain) {
6165 if (row < topRow()) {
6166 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6167 } else if (row > bottomRow()) {
6168 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6169 } else if (row == topRow()) {
6170 if (!subRect.isValid()) {
6171 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6172 } else {
6173 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
6174 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
6175 if (subRectTop < d->viewportRect.y())
6176 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6177 else if (subRectBottom > d->viewportRect.bottom())
6178 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6179 }
6180 } else if (row == bottomRow()) {
6181 if (!subRect.isValid()) {
6182 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6183 } else {
6184 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
6185 // visible in the viewport, which means that the top side of the subRect is visible.
6186 const qreal subRectBottom = d->loadedTableInnerRect.bottom() + subRect.bottom();
6187 if (subRectBottom > d->viewportRect.bottom())
6188 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6189 }
6190 }
6191 } else if (mode == Visible) {
6192 if (row < topRow()) {
6193 d->positionViewAtRow(row, Qt::AlignTop, -offset, subRect);
6194 } else if (row > bottomRow()) {
6195 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6196 } else if (subRect.isValid()) {
6197 if (row == topRow()) {
6198 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
6199 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
6200 if (subRectBottom < d->viewportRect.top())
6201 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6202 else if (subRectTop > d->viewportRect.bottom())
6203 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6204 } else if (row == bottomRow()) {
6205 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
6206 // visible in the viewport, which means that the top side of the subRect is visible.
6207 const qreal subRectTop = d->loadedTableInnerRect.bottom() + subRect.top();
6208 if (subRectTop > d->viewportRect.bottom())
6209 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6210 }
6211 }
6212 } else {
6213 qmlWarning(this) << "Unsupported mode:" << int(mode);
6214 }
6215}
6216
6217void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal offset, const QRectF &subRect)
6218{
6219 Q_D(QQuickTableView);
6220 if (column < 0 || column >= columns() || d->loadedColumns.isEmpty())
6221 return;
6222
6223 // Note: PositionMode::Contain is from here on translated to (Qt::AlignLeft | Qt::AlignRight).
6224 // This is an internal (unsupported) combination which means "align right if the whole cell
6225 // fits inside the viewport, otherwise align left".
6226
6227 if (mode & (AlignLeft | AlignRight | AlignHCenter)) {
6228 mode &= AlignLeft | AlignRight | AlignHCenter;
6229 d->positionViewAtColumn(column, Qt::Alignment(int(mode)), offset, subRect);
6230 } else if (mode == Contain) {
6231 if (column < leftColumn()) {
6232 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6233 } else if (column > rightColumn()) {
6234 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6235 } else if (column == leftColumn()) {
6236 if (!subRect.isValid()) {
6237 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6238 } else {
6239 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
6240 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
6241 if (subRectLeft < d->viewportRect.left())
6242 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6243 else if (subRectRight > d->viewportRect.right())
6244 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6245 }
6246 } else if (column == rightColumn()) {
6247 if (!subRect.isValid()) {
6248 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6249 } else {
6250 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
6251 // are visible in the viewport, which means that the left side of the subRect is visible.
6252 const qreal subRectRight = d->loadedTableInnerRect.right() + subRect.right();
6253 if (subRectRight > d->viewportRect.right())
6254 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6255 }
6256 }
6257 } else if (mode == Visible) {
6258 if (column < leftColumn()) {
6259 d->positionViewAtColumn(column, Qt::AlignLeft, -offset, subRect);
6260 } else if (column > rightColumn()) {
6261 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6262 } else if (subRect.isValid()) {
6263 if (column == leftColumn()) {
6264 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
6265 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
6266 if (subRectRight < d->viewportRect.left())
6267 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6268 else if (subRectLeft > d->viewportRect.right())
6269 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6270 } else if (column == rightColumn()) {
6271 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
6272 // are visible in the viewport, which means that the left side of the subRect is visible.
6273 const qreal subRectLeft = d->loadedTableInnerRect.right() + subRect.left();
6274 if (subRectLeft > d->viewportRect.right())
6275 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6276 }
6277 }
6278 } else {
6279 qmlWarning(this) << "Unsupported mode:" << int(mode);
6280 }
6281}
6282
6283void QQuickTableView::positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6284{
6285 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6286 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6287 if (!horizontalMode && !verticalMode) {
6288 qmlWarning(this) << "Unsupported mode:" << int(mode);
6289 return;
6290 }
6291
6292 if (horizontalMode)
6293 positionViewAtColumn(cell.x(), horizontalMode, offset.x(), subRect);
6294 if (verticalMode)
6295 positionViewAtRow(cell.y(), verticalMode, offset.y(), subRect);
6296}
6297
6298void QQuickTableView::positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6299{
6300 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6301 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6302 if (!horizontalMode && !verticalMode) {
6303 qmlWarning(this) << "Unsupported mode:" << int(mode);
6304 return;
6305 }
6306
6307 if (horizontalMode)
6308 positionViewAtColumn(columnAtIndex(index), horizontalMode, offset.x(), subRect);
6309 if (verticalMode)
6310 positionViewAtRow(rowAtIndex(index), verticalMode, offset.y(), subRect);
6311}
6312
6313#if QT_DEPRECATED_SINCE(6, 5)
6314void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6315{
6316 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6317 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6318 if (!horizontalMode && !verticalMode) {
6319 qmlWarning(this) << "Unsupported mode:" << int(mode);
6320 return;
6321 }
6322
6323 if (horizontalMode)
6324 positionViewAtColumn(column, horizontalMode, offset.x(), subRect);
6325 if (verticalMode)
6326 positionViewAtRow(row, verticalMode, offset.y(), subRect);
6327}
6328#endif
6329
6330void QQuickTableView::moveColumn(int source, int destination)
6331{
6332 Q_D(QQuickTableView);
6333 d->moveSection(source, destination, Qt::Horizontal);
6334}
6335
6336void QQuickTableView::moveRow(int source, int destination)
6337{
6338 Q_D(QQuickTableView);
6339 d->moveSection(source, destination, Qt::Vertical);
6340}
6341
6342void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientation orientation)
6343{
6344 Q_Q(QQuickTableView);
6345
6346 if (source < 0 || destination < 0 ||
6347 (orientation == Qt::Horizontal &&
6348 (source >= tableSize.width() || destination >= tableSize.width())) ||
6349 (orientation == Qt::Vertical &&
6350 (source >= tableSize.height() || destination >= tableSize.height())))
6351 return;
6352
6353 if (source == destination)
6354 return;
6355
6356 if (m_sectionState != SectionState::Moving) {
6357 m_sectionState = SectionState::Moving;
6358 if (syncView) {
6359 syncView->d_func()->moveSection(source, destination, orientation);
6360 } else {
6361 // Initialize the visual and logical index mapping
6362 initializeIndexMapping();
6363
6364 // Set current index mapping according to moving rows or columns
6365 auto &visualIndices = visualIndicesForOrientation(orientation);
6366 auto &logicalIndices = logicalIndicesForOrientation(orientation);
6367
6368 const int logical = logicalIndices.at(source).index;
6369 int visual = source;
6370
6371 if (destination > source) {
6372 while (visual < destination) {
6373 SectionData &visualData = visualIndices[logicalIndices[visual + 1].index];
6374 SectionData &logicalData = logicalIndices[visual];
6375 visualData.prevIndex = visualData.index;
6376 visualData.index = visual;
6377 logicalData.prevIndex = logicalData.index;
6378 logicalData.index = logicalIndices[visual + 1].index;
6379 ++visual;
6380 }
6381 } else {
6382 while (visual > destination) {
6383 SectionData &visualData = visualIndices[logicalIndices[visual - 1].index];
6384 SectionData &logicalData = logicalIndices[visual];
6385 visualData.prevIndex = visualData.index;
6386 visualData.index = visual;
6387 logicalData.prevIndex = logicalData.index;
6388 logicalData.index = logicalIndices[visual - 1].index;
6389 --visual;
6390 }
6391 }
6392
6393 visualIndices[logical].prevIndex = visualIndices[logical].index;
6394 visualIndices[logical].index = destination;
6395 logicalIndices[destination].prevIndex = logicalIndices[destination].index;
6396 logicalIndices[destination].index = logical;
6397
6398 // Trigger section move for horizontal and vertical child views
6399 // Used in a case where moveSection() triggered for table view
6400 for (auto syncChild : std::as_const(syncChildren)) {
6401 auto syncChild_d = syncChild->d_func();
6402 if (syncChild_d->m_sectionState != SectionState::Moving &&
6403 ((syncChild_d->syncHorizontally && orientation == Qt::Horizontal) ||
6404 (syncChild_d->syncVertically && orientation == Qt::Vertical)))
6405 syncChild_d->moveSection(source, destination, orientation);
6406 }
6407 }
6408
6409 // Rebuild the view to reflect the section order
6410 scheduleRebuildTable(RebuildOption::ViewportOnly);
6411 m_sectionState = SectionState::Idle;
6412
6413 // Emit section moved signal for the sections moved in the view
6414 const int startIndex = (source > destination) ? destination : source;
6415 const int endIndex = (source > destination) ? source : destination;
6416 const auto &logicalDataIndices = syncView
6417 ? syncView->d_func()->logicalIndicesForOrientation(orientation)
6418 : logicalIndicesForOrientation(orientation);
6419 const auto &visualDataIndices = syncView
6420 ? syncView->d_func()->visualIndicesForOrientation(orientation)
6421 : visualIndicesForOrientation(orientation);
6422 for (int index = startIndex; index <= endIndex; index++) {
6423 const int prevLogicalIndex = logicalDataIndices[index].prevIndex;
6424 if (orientation == Qt::Horizontal)
6425 emit q->columnMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
6426 else
6427 emit q->rowMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
6428 }
6429 }
6430}
6431
6432void QQuickTableView::clearColumnReordering()
6433{
6434 Q_D(QQuickTableView);
6435 d->clearSection(Qt::Horizontal);
6436}
6437
6438void QQuickTableView::clearRowReordering()
6439{
6440 Q_D(QQuickTableView);
6441 d->clearSection(Qt::Vertical);
6442}
6443
6444void QQuickTableViewPrivate::clearSection(Qt::Orientation orientation)
6445{
6446 Q_Q(QQuickTableView);
6447
6448 const auto &oldLogicalIndices = syncView
6449 ? syncView->d_func()->logicalIndicesForOrientation(orientation)
6450 : logicalIndicesForOrientation(orientation);
6451 const auto &oldVisualIndices = syncView
6452 ? syncView->d_func()->visualIndicesForOrientation(orientation)
6453 : visualIndicesForOrientation(orientation);
6454
6455 if (syncView) {
6456 syncView->d_func()->clearSection(orientation);
6457 } else {
6458 // Clear the index mapping and rebuild the table
6459 logicalIndicesForOrientation(orientation).clear();
6460 visualIndicesForOrientation(orientation).clear();
6461 scheduleRebuildTable(RebuildOption::ViewportOnly);
6462 }
6463
6464 // Emit section moved signal for the sections moved in the view
6465 for (int index = 0; index < int(oldLogicalIndices.size()); index++) {
6466 const auto &logicalDataIndices = oldLogicalIndices;
6467 const auto &visualDataIndices = oldVisualIndices;
6468 if (logicalDataIndices[index].index != index) {
6469 const int currentIndex = logicalDataIndices[index].index;
6470 if (orientation == Qt::Horizontal)
6471 emit q->columnMoved(currentIndex, visualDataIndices[currentIndex].index, index);
6472 else
6473 emit q->rowMoved(currentIndex, visualDataIndices[currentIndex].index, index);
6474 }
6475 }
6476}
6477
6478void QQuickTableViewPrivate::setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay)
6479{
6480 if (!modelIndex.isValid())
6481 return;
6482
6483 const int cellIndex = modelIndexToCellIndex(modelIndex);
6484 if (!loadedItems.contains(cellIndex))
6485 return;
6486 const QPoint cell = cellAtModelIndex(cellIndex);
6487 QQuickItem *item = loadedTableItem(cell)->item;
6488 setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(overlay), cellIndex, item, false);
6489}
6490
6491QQuickItem *QQuickTableView::itemAtCell(const QPoint &cell) const
6492{
6493 Q_D(const QQuickTableView);
6494 const int modelIndex = d->modelIndexAtCell(cell);
6495 if (!d->loadedItems.contains(modelIndex))
6496 return nullptr;
6497 return d->loadedItems.value(modelIndex)->item;
6498}
6499
6500#if QT_DEPRECATED_SINCE(6, 5)
6501QQuickItem *QQuickTableView::itemAtCell(int column, int row) const
6502{
6503 return itemAtCell(QPoint(column, row));
6504}
6505#endif
6506
6507QQuickItem *QQuickTableView::itemAtIndex(const QModelIndex &index) const
6508{
6509 Q_D(const QQuickTableView);
6510 const int serializedIndex = d->modelIndexToCellIndex(index);
6511 if (!d->loadedItems.contains(serializedIndex))
6512 return nullptr;
6513 return d->loadedItems.value(serializedIndex)->item;
6514}
6515
6516#if QT_DEPRECATED_SINCE(6, 4)
6517QPoint QQuickTableView::cellAtPos(qreal x, qreal y, bool includeSpacing) const
6518{
6519 return cellAtPosition(mapToItem(contentItem(), {x, y}), includeSpacing);
6520}
6521
6522QPoint QQuickTableView::cellAtPos(const QPointF &position, bool includeSpacing) const
6523{
6524 return cellAtPosition(mapToItem(contentItem(), position), includeSpacing);
6525}
6526#endif
6527
6528QPoint QQuickTableView::cellAtPosition(qreal x, qreal y, bool includeSpacing) const
6529{
6530 return cellAtPosition(QPoint(x, y), includeSpacing);
6531}
6532
6533QPoint QQuickTableView::cellAtPosition(const QPointF &position, bool includeSpacing) const
6534{
6535 Q_D(const QQuickTableView);
6536
6537 if (!d->loadedTableOuterRect.contains(position))
6538 return QPoint(-1, -1);
6539
6540 const qreal hSpace = d->cellSpacing.width();
6541 const qreal vSpace = d->cellSpacing.height();
6542 qreal currentColumnEnd = d->loadedTableOuterRect.x();
6543 qreal currentRowEnd = d->loadedTableOuterRect.y();
6544
6545 int foundColumn = -1;
6546 int foundRow = -1;
6547
6548 for (const int column : d->loadedColumns) {
6549 currentColumnEnd += d->getEffectiveColumnWidth(column);
6550 if (position.x() < currentColumnEnd) {
6551 foundColumn = column;
6552 break;
6553 }
6554 currentColumnEnd += hSpace;
6555 if (!includeSpacing && position.x() < currentColumnEnd) {
6556 // Hit spacing
6557 return QPoint(-1, -1);
6558 } else if (includeSpacing && position.x() < currentColumnEnd - (hSpace / 2)) {
6559 foundColumn = column;
6560 break;
6561 }
6562 }
6563
6564 for (const int row : d->loadedRows) {
6565 currentRowEnd += d->getEffectiveRowHeight(row);
6566 if (position.y() < currentRowEnd) {
6567 foundRow = row;
6568 break;
6569 }
6570 currentRowEnd += vSpace;
6571 if (!includeSpacing && position.y() < currentRowEnd) {
6572 // Hit spacing
6573 return QPoint(-1, -1);
6574 }
6575 if (includeSpacing && position.y() < currentRowEnd - (vSpace / 2)) {
6576 foundRow = row;
6577 break;
6578 }
6579 }
6580
6581 return QPoint(foundColumn, foundRow);
6582}
6583
6584bool QQuickTableView::isColumnLoaded(int column) const
6585{
6586 Q_D(const QQuickTableView);
6587 if (!d->loadedColumns.contains(column))
6588 return false;
6589
6590 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6591 // TableView is rebuilding, and none of the rows and columns
6592 // are completely loaded until we reach the layout phase.
6593 if (d->rebuildState < QQuickTableViewPrivate::RebuildState::LayoutTable)
6594 return false;
6595 }
6596
6597 return true;
6598}
6599
6600bool QQuickTableView::isRowLoaded(int row) const
6601{
6602 Q_D(const QQuickTableView);
6603 if (!d->loadedRows.contains(row))
6604 return false;
6605
6606 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6607 // TableView is rebuilding, and none of the rows and columns
6608 // are completely loaded until we reach the layout phase.
6609 if (d->rebuildState < QQuickTableViewPrivate::RebuildState::LayoutTable)
6610 return false;
6611 }
6612
6613 return true;
6614}
6615
6616qreal QQuickTableView::columnWidth(int column) const
6617{
6618 Q_D(const QQuickTableView);
6619 if (!isColumnLoaded(column))
6620 return -1;
6621
6622 return d->getEffectiveColumnWidth(column);
6623}
6624
6625qreal QQuickTableView::rowHeight(int row) const
6626{
6627 Q_D(const QQuickTableView);
6628 if (!isRowLoaded(row))
6629 return -1;
6630
6631 return d->getEffectiveRowHeight(row);
6632}
6633
6634qreal QQuickTableView::implicitColumnWidth(int column) const
6635{
6636 Q_D(const QQuickTableView);
6637 if (!isColumnLoaded(column))
6638 return -1;
6639
6640 return d->sizeHintForColumn(column);
6641}
6642
6643qreal QQuickTableView::implicitRowHeight(int row) const
6644{
6645 Q_D(const QQuickTableView);
6646 if (!isRowLoaded(row))
6647 return -1;
6648
6649 return d->sizeHintForRow(row);
6650}
6651
6652void QQuickTableView::setColumnWidth(int column, qreal size)
6653{
6654 Q_D(QQuickTableView);
6655 if (column < 0) {
6656 qmlWarning(this) << "column must be greather than, or equal to, zero";
6657 return;
6658 }
6659
6660 if (d->syncHorizontally) {
6661 d->syncView->setColumnWidth(column, size);
6662 return;
6663 }
6664
6665 if (qFuzzyCompare(explicitColumnWidth(column), size))
6666 return;
6667
6668 if (size < 0)
6669 d->explicitColumnWidths.remove(d->logicalColumnIndex(column));
6670 else
6671 d->explicitColumnWidths.insert(d->logicalColumnIndex(column), size);
6672
6673 if (d->loadedItems.isEmpty())
6674 return;
6675
6676 const bool allColumnsLoaded = d->atTableEnd(Qt::LeftEdge) && d->atTableEnd(Qt::RightEdge);
6677 if (column >= leftColumn() || column <= rightColumn() || allColumnsLoaded)
6678 d->forceLayout(false);
6679}
6680
6681void QQuickTableView::clearColumnWidths()
6682{
6683 Q_D(QQuickTableView);
6684
6685 if (d->syncHorizontally) {
6686 d->syncView->clearColumnWidths();
6687 return;
6688 }
6689
6690 if (d->explicitColumnWidths.isEmpty())
6691 return;
6692
6693 d->explicitColumnWidths.clear();
6694 d->forceLayout(false);
6695}
6696
6697qreal QQuickTableView::explicitColumnWidth(int column) const
6698{
6699 Q_D(const QQuickTableView);
6700
6701 if (d->syncHorizontally)
6702 return d->syncView->explicitColumnWidth(column);
6703
6704 const auto it = d->explicitColumnWidths.constFind(d->logicalColumnIndex(column));
6705 if (it != d->explicitColumnWidths.constEnd())
6706 return *it;
6707 return -1;
6708}
6709
6710void QQuickTableView::setRowHeight(int row, qreal size)
6711{
6712 Q_D(QQuickTableView);
6713 if (row < 0) {
6714 qmlWarning(this) << "row must be greather than, or equal to, zero";
6715 return;
6716 }
6717
6718 if (d->syncVertically) {
6719 d->syncView->setRowHeight(row, size);
6720 return;
6721 }
6722
6723 if (qFuzzyCompare(explicitRowHeight(row), size))
6724 return;
6725
6726 if (size < 0)
6727 d->explicitRowHeights.remove(d->logicalRowIndex(row));
6728 else
6729 d->explicitRowHeights.insert(d->logicalRowIndex(row), size);
6730
6731 if (d->loadedItems.isEmpty())
6732 return;
6733
6734 const bool allRowsLoaded = d->atTableEnd(Qt::TopEdge) && d->atTableEnd(Qt::BottomEdge);
6735 if (row >= topRow() || row <= bottomRow() || allRowsLoaded)
6736 d->forceLayout(false);
6737}
6738
6739void QQuickTableView::clearRowHeights()
6740{
6741 Q_D(QQuickTableView);
6742
6743 if (d->syncVertically) {
6744 d->syncView->clearRowHeights();
6745 return;
6746 }
6747
6748 if (d->explicitRowHeights.isEmpty())
6749 return;
6750
6751 d->explicitRowHeights.clear();
6752 d->forceLayout(false);
6753}
6754
6755qreal QQuickTableView::explicitRowHeight(int row) const
6756{
6757 Q_D(const QQuickTableView);
6758
6759 if (d->syncVertically)
6760 return d->syncView->explicitRowHeight(row);
6761
6762 const auto it = d->explicitRowHeights.constFind(d->logicalRowIndex(row));
6763 if (it != d->explicitRowHeights.constEnd())
6764 return *it;
6765 return -1;
6766}
6767
6768QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const
6769{
6770 Q_D(const QQuickTableView);
6771 if (cell.x() < 0 || cell.x() >= columns() || cell.y() < 0 || cell.y() >= rows())
6772 return {};
6773
6774 auto const qaim = d->model->abstractItemModel();
6775 if (!qaim)
6776 return {};
6777
6778 return qaim->index(d->logicalRowIndex(cell.y()), d->logicalColumnIndex(cell.x()));
6779}
6780
6781QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const
6782{
6783 if (!index.isValid() || index.parent().isValid())
6784 return {-1, -1};
6785 Q_D(const QQuickTableView);
6786 return {d->visualColumnIndex(index.column()), d->visualRowIndex(index.row())};
6787}
6788
6789#if QT_DEPRECATED_SINCE(6, 4)
6790QModelIndex QQuickTableView::modelIndex(int row, int column) const
6791{
6792 static bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
6793 if (compat6_4) {
6794 // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
6795 // being documented to be specified in the opposite order.
6796 // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
6797 // to continue accepting calls to modelIndex(column, row).
6798 return modelIndex({row, column});
6799 } else {
6800 qmlWarning(this) << "modelIndex(row, column) is deprecated. "
6801 "Use index(row, column) instead. For more information, see "
6802 "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
6803 return modelIndex({column, row});
6804 }
6805}
6806#endif
6807
6808QModelIndex QQuickTableView::index(int row, int column) const
6809{
6810 return modelIndex({column, row});
6811}
6812
6813int QQuickTableView::rowAtIndex(const QModelIndex &index) const
6814{
6815 return cellAtIndex(index).y();
6816}
6817
6818int QQuickTableView::columnAtIndex(const QModelIndex &index) const
6819{
6820 return cellAtIndex(index).x();
6821}
6822
6823void QQuickTableView::forceLayout()
6824{
6825 d_func()->forceLayout(true);
6826}
6827
6828void QQuickTableView::edit(const QModelIndex &index)
6829{
6830 Q_D(QQuickTableView);
6831
6832 if (!d->canEdit(index, true))
6833 return;
6834
6835 if (d->editIndex == index)
6836 return;
6837
6838 if (!d->tableModel)
6839 return;
6840
6841 if (!d->editModel) {
6842 d->editModel = new QQmlTableInstanceModel(qmlContext(this));
6843 d->editModel->useImportVersion(d->resolveImportVersion());
6844 QObject::connect(d->editModel, &QQmlInstanceModel::initItem, this,
6845 [this, d] (int serializedModelIndex, QObject *object) {
6846 // initItemCallback will call setRequiredProperty for each required property in the
6847 // delegate, both for this class, but also also for any subclasses. setRequiredProperty
6848 // is currently dependent of the QQmlTableInstanceModel that was used to create the object
6849 // in order to initialize required properties, so we need to set the editItem variable
6850 // early on, so that we can use it in setRequiredProperty.
6851 const QPoint cell = d->cellAtModelIndex(serializedModelIndex);
6852 d->editIndex = modelIndex({d->visualColumnIndex(cell.x()), d->visualRowIndex(cell.y())});
6853 d->editItem = qmlobject_cast<QQuickItem*>(object);
6854 if (!d->editItem)
6855 return;
6856 // Initialize required properties
6857 d->initItemCallback(serializedModelIndex, object);
6858 const auto cellItem = itemAtCell(cellAtIndex(d->editIndex));
6859 Q_ASSERT(cellItem);
6860 d->editItem->setParentItem(cellItem);
6861 // Move the cell item to the top of the other items, to ensure
6862 // that e.g a focus frame ends up on top of all the cells
6863 cellItem->setZ(2);
6864 });
6865 }
6866
6867 if (d->selectionModel)
6868 d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
6869
6870 // If the user is already editing another cell, close that editor first
6871 d->closeEditorAndCommit();
6872
6873 const auto cellItem = itemAtCell(cellAtIndex(index));
6874 Q_ASSERT(cellItem);
6875 const auto attached = d->getAttachedObject(cellItem);
6876 Q_ASSERT(attached);
6877
6878 d->editModel->setModel(d->tableModel->model());
6879 d->editModel->setDelegate(attached->editDelegate());
6880
6881 const int cellIndex = d->getEditCellIndex(index);
6882 QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous);
6883 if (!object) {
6884 d->editIndex = QModelIndex();
6885 d->editItem = nullptr;
6886 qmlWarning(this) << "cannot edit: TableView.editDelegate could not be instantiated!";
6887 return;
6888 }
6889
6890 // Note: at this point, editIndex and editItem has been set from initItem!
6891
6892 if (!d->editItem) {
6893 qmlWarning(this) << "cannot edit: TableView.editDelegate is not an Item!";
6894 d->editItem = nullptr;
6895 d->editIndex = QModelIndex();
6896 d->editModel->release(object, QQmlInstanceModel::NotReusable);
6897 return;
6898 }
6899
6900 // Reference the cell item once more, so that it doesn't
6901 // get reused or deleted if it leaves the viewport.
6902 d->model->object(cellIndex, QQmlIncubator::Synchronous);
6903
6904 // Inform the delegate, and the edit delegate, that they're being edited
6905 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(true), cellIndex, cellItem, false);
6906
6907 // Transfer focus to the edit item
6908 d->editItem->forceActiveFocus(Qt::MouseFocusReason);
6909 (void)d->installEventFilterOnFocusObjectInsideEditItem();
6910}
6911
6912void QQuickTableView::closeEditor()
6913{
6914 Q_D(QQuickTableView);
6915
6916 if (!d->editItem)
6917 return;
6918
6919 QQuickItem *cellItem = d->editItem->parentItem();
6920 d->editModel->release(d->editItem, QQmlInstanceModel::NotReusable);
6921 d->editItem = nullptr;
6922
6923 cellItem->setZ(1);
6924 const int cellIndex = d->getEditCellIndex(d->editIndex);
6925 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false);
6926 // Remove the extra reference we sat on the cell item from edit()
6927 d->model->release(cellItem, QQmlInstanceModel::NotReusable);
6928
6929 if (d->editIndex.isValid()) {
6930 // Note: we can have an invalid editIndex, even when we
6931 // have an editItem, if the model has changed (e.g been reset)!
6932 d->editIndex = QModelIndex();
6933 }
6934}
6935
6936QQuickTableViewAttached *QQuickTableView::qmlAttachedProperties(QObject *obj)
6937{
6938 return new QQuickTableViewAttached(obj);
6939}
6940
6941void QQuickTableView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
6942{
6943 Q_D(QQuickTableView);
6944 QQuickFlickable::geometryChange(newGeometry, oldGeometry);
6945
6946 if (d->tableModel) {
6947 // When the view changes size, we force the pool to
6948 // shrink by releasing all pooled items.
6949 d->tableModel->drainReusableItemsPool(0);
6950 }
6951
6952 d->forceLayout(false);
6953}
6954
6955void QQuickTableView::viewportMoved(Qt::Orientations orientation)
6956{
6957 Q_D(QQuickTableView);
6958
6959 // If the new viewport position was set from the setLocalViewportXY()
6960 // functions, we just update the position silently and return. Otherwise, if
6961 // the viewport was flicked by the user, or some other control, we
6962 // recursively sync all the views in the hierarchy to the same position.
6963 QQuickFlickable::viewportMoved(orientation);
6964 if (d->inSetLocalViewportPos)
6965 return;
6966
6967 // Move all views in the syncView hierarchy to the same contentX/Y.
6968 // We need to start from this view (and not the root syncView) to
6969 // ensure that we respect all the individual syncDirection flags
6970 // between the individual views in the hierarchy.
6971 d->syncViewportPosRecursive();
6972
6973 auto rootView = d->rootSyncView();
6974 auto rootView_d = rootView->d_func();
6975
6976 rootView_d->scheduleRebuildIfFastFlick();
6977
6978 if (!rootView_d->polishScheduled) {
6979 if (rootView_d->scheduledRebuildOptions) {
6980 // When we need to rebuild, collecting several viewport
6981 // moves and do a single polish gives a quicker UI.
6982 rootView->polish();
6983 } else {
6984 // Updating the table right away when flicking
6985 // slowly gives a smoother experience.
6986 const bool updated = rootView->d_func()->updateTableRecursive();
6987 if (!updated) {
6988 // One, or more, of the views are already in an
6989 // update, so we need to wait a cycle.
6990 rootView->polish();
6991 }
6992 }
6993 }
6994}
6995
6996void QQuickTableView::keyPressEvent(QKeyEvent *e)
6997{
6998 Q_D(QQuickTableView);
6999
7000 if (!d->keyNavigationEnabled) {
7001 QQuickFlickable::keyPressEvent(e);
7002 return;
7003 }
7004
7005 if (d->tableSize.isEmpty())
7006 return;
7007
7008 if (d->editIndex.isValid()) {
7009 // While editing, we limit the keys that we
7010 // handle to not interfere with editing.
7011 return;
7012 }
7013
7014 if (d->setCurrentIndexFromKeyEvent(e))
7015 return;
7016
7017 if (d->editFromKeyEvent(e))
7018 return;
7019
7020 QQuickFlickable::keyPressEvent(e);
7021}
7022
7023bool QQuickTableView::eventFilter(QObject *obj, QEvent *event)
7024{
7025 Q_D(QQuickTableView);
7026
7027 if (obj != d->editItem && !d->editItem->isAncestorOf(qobject_cast<QQuickItem *>(obj))) {
7028 // We might also receive events from old editItems that are about to be
7029 // destroyed (such as DefferedDelete events). Just ignore those events.
7030 return QQuickFlickable::eventFilter(obj, event);
7031 }
7032
7033 switch (event->type()) {
7034 case QEvent::KeyPress: {
7035 Q_ASSERT(d->editItem);
7036 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
7037 switch (keyEvent->key()) {
7038 case Qt::Key_Enter:
7039 case Qt::Key_Return:
7040 d->closeEditorAndCommit();
7041 return true;
7042 case Qt::Key_Tab:
7043 case Qt::Key_Backtab:
7044 if (activeFocusOnTab()) {
7045 if (d->setCurrentIndexFromKeyEvent(keyEvent)) {
7046 const QModelIndex currentIndex = d->selectionModel->currentIndex();
7047 if (d->canEdit(currentIndex, false))
7048 edit(currentIndex);
7049 }
7050 return true;
7051 }
7052 break;
7053 case Qt::Key_Escape:
7054 closeEditor();
7055 return true;
7056 }
7057 break; }
7058 case QEvent::FocusOut:
7059 // If focus was transferred within the edit delegate, we start to filter
7060 // the new focus object. Otherwise we close the edit delegate.
7061 if (!d->installEventFilterOnFocusObjectInsideEditItem())
7062 d->closeEditorAndCommit();
7063 break;
7064 default:
7065 break;
7066 }
7067
7068 return QQuickFlickable::eventFilter(obj, event);
7069}
7070
7071bool QQuickTableView::alternatingRows() const
7072{
7073 return d_func()->alternatingRows;
7074}
7075
7076void QQuickTableView::setAlternatingRows(bool alternatingRows)
7077{
7078 Q_D(QQuickTableView);
7079 if (d->alternatingRows == alternatingRows)
7080 return;
7081
7082 d->alternatingRows = alternatingRows;
7083 emit alternatingRowsChanged();
7084}
7085
7086QQuickTableView::SelectionBehavior QQuickTableView::selectionBehavior() const
7087{
7088 return d_func()->selectionBehavior;
7089}
7090
7091void QQuickTableView::setSelectionBehavior(SelectionBehavior selectionBehavior)
7092{
7093 Q_D(QQuickTableView);
7094 if (d->selectionBehavior == selectionBehavior)
7095 return;
7096
7097 d->selectionBehavior = selectionBehavior;
7098 emit selectionBehaviorChanged();
7099}
7100
7101QQuickTableView::SelectionMode QQuickTableView::selectionMode() const
7102{
7103 return d_func()->selectionMode;
7104}
7105
7106void QQuickTableView::setSelectionMode(SelectionMode selectionMode)
7107{
7108 Q_D(QQuickTableView);
7109 if (d->selectionMode == selectionMode)
7110 return;
7111
7112 d->selectionMode = selectionMode;
7113 emit selectionModeChanged();
7114}
7115
7116bool QQuickTableView::resizableColumns() const
7117{
7118 return d_func()->resizableColumns;
7119}
7120
7121void QQuickTableView::setResizableColumns(bool enabled)
7122{
7123 Q_D(QQuickTableView);
7124 if (d->resizableColumns == enabled)
7125 return;
7126
7127 d->resizableColumns = enabled;
7128 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
7129 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
7130
7131 emit resizableColumnsChanged();
7132}
7133
7134bool QQuickTableView::resizableRows() const
7135{
7136 return d_func()->resizableRows;
7137}
7138
7139void QQuickTableView::setResizableRows(bool enabled)
7140{
7141 Q_D(QQuickTableView);
7142 if (d->resizableRows == enabled)
7143 return;
7144
7145 d->resizableRows = enabled;
7146 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
7147 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
7148
7149 emit resizableRowsChanged();
7150}
7151
7152// ----------------------------------------------
7153QQuickTableViewHoverHandler::QQuickTableViewHoverHandler(QQuickTableView *view)
7154 : QQuickHoverHandler(view->contentItem())
7155{
7156 setMargin(5);
7157
7158 connect(this, &QQuickHoverHandler::hoveredChanged, this, [this] {
7159 if (!isHoveringGrid())
7160 return;
7161 m_row = -1;
7162 m_column = -1;
7163#if QT_CONFIG(cursor)
7164 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7165 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7166 tableViewPrivate->updateCursor();
7167#endif
7168 });
7169}
7170
7171void QQuickTableViewHoverHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
7172{
7173 QQuickHoverHandler::handleEventPoint(event, point);
7174
7175 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7176#if QT_CONFIG(cursor)
7177 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7178#endif
7179
7180 const QPoint cell = tableView->cellAtPosition(point.position(), true);
7181 const auto item = tableView->itemAtCell(cell);
7182 if (!item) {
7183 m_row = -1;
7184 m_column = -1;
7185#if QT_CONFIG(cursor)
7186 tableViewPrivate->updateCursor();
7187#endif
7188 return;
7189 }
7190
7191 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
7192 const bool hoveringRow = (itemPos.y() < margin() || itemPos.y() > item->height() - margin());
7193 const bool hoveringColumn = (itemPos.x() < margin() || itemPos.x() > item->width() - margin());
7194 m_row = hoveringRow ? itemPos.y() < margin() ? cell.y() - 1 : cell.y() : -1;
7195 m_column = hoveringColumn ? itemPos.x() < margin() ? cell.x() - 1 : cell.x() : -1;
7196#if QT_CONFIG(cursor)
7197 tableViewPrivate->updateCursor();
7198#endif
7199}
7200
7201// ----------------------------------------------
7202
7205{
7206 // Set a grab permission that stops the flickable, as well as
7207 // any drag handler inside the delegate, from stealing the drag.
7208 setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything);
7209}
7210
7211bool QQuickTableViewPointerHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
7212{
7213 if (!QQuickSinglePointHandler::wantsEventPoint(event, point))
7214 return false;
7215
7216 // If we have a mouse wheel event then we do not want to do anything related to resizing.
7217 if (event->type() == QEvent::Type::Wheel)
7218 return false;
7219
7220 // When the user is flicking, we disable resizing, so that
7221 // he doesn't start to resize by accident.
7222 const auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7223 return !tableView->isMoving();
7224}
7225
7226// ----------------------------------------------
7227
7228QQuickTableViewResizeHandler::QQuickTableViewResizeHandler(QQuickTableView *view)
7230{
7231 setMargin(5);
7232 setObjectName("tableViewResizeHandler");
7233}
7234
7235void QQuickTableViewResizeHandler::onGrabChanged(QQuickPointerHandler *grabber
7236 , QPointingDevice::GrabTransition transition
7237 , QPointerEvent *ev
7238 , QEventPoint &point)
7239{
7240 QQuickSinglePointHandler::onGrabChanged(grabber, transition, ev, point);
7241
7242 switch (transition) {
7243 case QPointingDevice::GrabPassive:
7244 case QPointingDevice::GrabExclusive:
7245 break;
7246 case QPointingDevice::UngrabPassive:
7247 case QPointingDevice::UngrabExclusive:
7248 case QPointingDevice::CancelGrabPassive:
7249 case QPointingDevice::CancelGrabExclusive:
7250 case QPointingDevice::OverrideGrabPassive:
7251 if (m_state == DraggingStarted || m_state == Dragging) {
7252 m_state = DraggingFinished;
7253 updateDrag(ev, point);
7254 }
7255 break;
7256 }
7257}
7258
7259void QQuickTableViewResizeHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
7260{
7261 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7262 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7263 const auto *activeHandler = tableViewPrivate->activePointerHandler();
7264 if (activeHandler && !qobject_cast<const QQuickTableViewResizeHandler *>(activeHandler))
7265 return;
7266
7267 // Resolve which state we're in first...
7268 updateState(point);
7269 // ...and act on it next
7270 updateDrag(event, point);
7271}
7272
7274{
7275 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7276 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7277
7278 if (m_state == DraggingFinished)
7279 m_state = Listening;
7280
7281 if (point.state() == QEventPoint::Pressed) {
7282 m_row = tableViewPrivate->resizableRows ? tableViewPrivate->hoverHandler->m_row : -1;
7283 m_column = tableViewPrivate->resizableColumns ? tableViewPrivate->hoverHandler->m_column : -1;
7284 if (m_row != -1 || m_column != -1)
7285 m_state = Tracking;
7286 } else if (point.state() == QEventPoint::Released) {
7287 if (m_state == DraggingStarted || m_state == Dragging)
7288 m_state = DraggingFinished;
7289 else
7290 m_state = Listening;
7291 } else if (point.state() == QEventPoint::Updated) {
7292 switch (m_state) {
7293 case Listening:
7294 break;
7295 case Tracking: {
7296 const qreal distX = m_column != -1 ? point.position().x() - point.pressPosition().x() : 0;
7297 const qreal distY = m_row != -1 ? point.position().y() - point.pressPosition().y() : 0;
7298 const qreal dragDist = qSqrt(distX * distX + distY * distY);
7299 if (dragDist > qApp->styleHints()->startDragDistance())
7300 m_state = DraggingStarted;
7301 break;}
7302 case DraggingStarted:
7303 m_state = Dragging;
7304 break;
7305 case Dragging:
7306 break;
7307 case DraggingFinished:
7308 // Handled at the top of the function
7309 Q_UNREACHABLE();
7310 break;
7311 }
7312 }
7313}
7314
7315void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint &point)
7316{
7317 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7318#if QT_CONFIG(cursor)
7319 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7320#endif
7321
7322 switch (m_state) {
7323 case Listening:
7324 break;
7325 case Tracking:
7326 setPassiveGrab(event, point, true);
7327 // Disable flicking while dragging. TableView uses filtering instead of
7328 // pointer handlers to do flicking, so setting an exclusive grab (together
7329 // with grab permissions) doens't work ATM.
7330 tableView->setFiltersChildMouseEvents(false);
7331#if QT_CONFIG(cursor)
7332 tableViewPrivate->setActivePointerHandler(this);
7333#endif
7334 break;
7335 case DraggingStarted:
7336 setExclusiveGrab(event, point, true);
7337 m_columnStartX = point.position().x();
7338 m_columnStartWidth = tableView->columnWidth(m_column);
7339 m_rowStartY = point.position().y();
7340 m_rowStartHeight = tableView->rowHeight(m_row);
7341#if QT_CONFIG(cursor)
7342 tableViewPrivate->updateCursor();
7343#endif
7344 Q_FALLTHROUGH();
7345 case Dragging: {
7346 const qreal distX = point.position().x() - m_columnStartX;
7347 const qreal distY = point.position().y() - m_rowStartY;
7348 if (m_column != -1)
7349 tableView->setColumnWidth(m_column, qMax(0.001, m_columnStartWidth + distX));
7350 if (m_row != -1)
7351 tableView->setRowHeight(m_row, qMax(0.001, m_rowStartHeight + distY));
7352 break; }
7353 case DraggingFinished: {
7354 tableView->setFiltersChildMouseEvents(true);
7355#if QT_CONFIG(cursor)
7356 tableViewPrivate->setActivePointerHandler(nullptr);
7357 tableViewPrivate->updateCursor();
7358#endif
7359 break; }
7360 }
7361}
7362
7363// ----------------------------------------------
7364#if QT_CONFIG(quick_draganddrop)
7365
7366QQuickTableViewSectionDragHandler::QQuickTableViewSectionDragHandler(QQuickTableView *view)
7367 : QQuickTableViewPointerHandler(view)
7368{
7369 setObjectName("tableViewDragHandler");
7370}
7371
7372QQuickTableViewSectionDragHandler::~QQuickTableViewSectionDragHandler()
7373{
7374 resetDragData();
7375}
7376
7377void QQuickTableViewSectionDragHandler::resetDragData()
7378{
7379 if (m_state != Listening) {
7380 m_state = Listening;
7381 resetSectionOverlay();
7382 m_source = -1;
7383 m_destination = -1;
7384 if (m_grabResult.data())
7385 m_grabResult.data()->disconnect();
7386 if (!m_drag.isNull()) {
7387 m_drag->disconnect();
7388 delete m_drag;
7389 }
7390 if (!m_dropArea.isNull()) {
7391 m_dropArea->disconnect();
7392 delete m_dropArea;
7393 }
7394 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7395 tableView->setFiltersChildMouseEvents(true);
7396 }
7397}
7398
7399void QQuickTableViewSectionDragHandler::resetSectionOverlay()
7400{
7401 if (m_destination != -1) {
7402 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7403 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7404 const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : m_destination;
7405 const int column = (m_sectionOrientation == Qt::Horizontal) ? m_destination : 0;
7406 tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), false);
7407 m_destination = -1;
7408 }
7409}
7410
7411void QQuickTableViewSectionDragHandler::grabSection()
7412{
7413 // Generate the transparent section image in pixmap
7414 QPixmap pixmap(m_grabResult->image().size());
7415 pixmap.fill(Qt::transparent);
7416 QPainter painter(&pixmap);
7417 painter.setOpacity(0.6);
7418 painter.drawImage(0, 0, m_grabResult->image());
7419 painter.end();
7420
7421 // Specify the pixmap and mime data to be as drag object
7422 auto *mimeData = new QMimeData();
7423 mimeData->setImageData(pixmap);
7424 m_drag->setMimeData(mimeData);
7425 m_drag->setPixmap(pixmap);
7426}
7427
7428void QQuickTableViewSectionDragHandler::handleDrop(QQuickDragEvent *event)
7429{
7430 Q_UNUSED(event);
7431
7432 if (m_state == Dragging) {
7433 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7434 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7435 tableViewPrivate->moveSection(m_source, m_destination, m_sectionOrientation);
7436 m_state = DraggingFinished;
7437 resetSectionOverlay();
7438 if (m_scrollTimer.isActive())
7439 m_scrollTimer.stop();
7440 event->accept();
7441 }
7442}
7443
7444void QQuickTableViewSectionDragHandler::handleDrag(QQuickDragEvent *event)
7445{
7446 Q_UNUSED(event);
7447
7448 if (m_state == Dragging) {
7449 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7450 const QPoint dragItemPosition(tableView->contentX() + event->x(), tableView->contentY() + event->y());
7451 const auto *sourceItem = qobject_cast<QQuickItem *>(m_drag->source());
7452 const QPoint targetCell = tableView->cellAtPosition(dragItemPosition, true);
7453
7454 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7455 const int newDestination = (m_sectionOrientation == Qt::Horizontal) ? targetCell.x() : targetCell.y();
7456 if (newDestination != m_destination) {
7457 // Reset the overlay property in the existing model delegate item
7458 resetSectionOverlay();
7459 // Set the overlay property in the new model delegate item
7460 const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : newDestination;
7461 const int column = (m_sectionOrientation == Qt::Horizontal) ? newDestination : 0;
7462 tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), true);
7463 m_destination = newDestination;
7464 }
7465
7466 // Scroll header view while section item moves out of the table boundary
7467 const QPoint dragItemStartPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() - sourceItem->width() / 2, dragItemPosition.y()) :
7468 QPoint(dragItemPosition.x(), dragItemPosition.y() - sourceItem->height() / 2);
7469 const QPoint dragItemEndPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() + sourceItem->width() / 2, dragItemPosition.y()) :
7470 QPoint(dragItemPosition.x(), dragItemPosition.y() + sourceItem->height() / 2);
7471 const bool useStartPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemStartPos.x() <= tableView->contentX()) : (dragItemStartPos.y() <= tableView->contentY());
7472 const bool useEndPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemEndPos.x() >= tableView->width()) : (dragItemEndPos.y() >= tableView->height());
7473 if (useStartPos || useEndPos) {
7474 if (!m_scrollTimer.isActive()) {
7475 m_dragPoint = (m_sectionOrientation == Qt::Horizontal) ? QPoint(useStartPos ? dragItemStartPos.x() : dragItemEndPos.x(), 0) :
7476 QPoint(0, useStartPos ? dragItemStartPos.y() : dragItemEndPos.y());
7477 m_scrollTimer.start(1);
7478 }
7479 } else {
7480 if (m_scrollTimer.isActive())
7481 m_scrollTimer.stop();
7482 }
7483 }
7484}
7485
7486void QQuickTableViewSectionDragHandler::handleDragDropAction(Qt::DropAction action)
7487{
7488 // Reset the overlay property in the model delegate item when drag or drop
7489 // happens outside specified drop area (i.e. during ignore action)
7490 if (action == Qt::IgnoreAction) {
7491 resetSectionOverlay();
7492 if (m_scrollTimer.isActive())
7493 m_scrollTimer.stop();
7494 }
7495}
7496
7497void QQuickTableViewSectionDragHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
7498{
7499 QQuickSinglePointHandler::handleEventPoint(event, point);
7500
7501 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7502 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7503 const auto *activeHandler = tableViewPrivate->activePointerHandler();
7504 if (activeHandler && !qobject_cast<const QQuickTableViewSectionDragHandler *>(activeHandler))
7505 return;
7506
7507 if (m_state == DraggingFinished) {
7508 if (m_scrollTimer.isActive())
7509 m_scrollTimer.stop();
7510 resetDragData();
7511 }
7512
7513 if (point.state() == QEventPoint::Pressed) {
7514 // Reset the information in the drag handler
7515 resetDragData();
7516 // Activate the passive grab to get further move updates
7517 setPassiveGrab(event, point, true);
7518 // Disable flicking while dragging. TableView uses filtering instead of
7519 // pointer handlers to do flicking, so setting an exclusive grab (together
7520 // with grab permissions) doens't work ATM.
7521 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7522 tableView->setFiltersChildMouseEvents(false);
7523 m_state = Tracking;
7524 } else if (point.state() == QEventPoint::Released) {
7525 // Reset the information in the drag handler
7526 if (m_scrollTimer.isActive())
7527 m_scrollTimer.stop();
7528 resetDragData();
7529 } else if (point.state() == QEventPoint::Updated) {
7530 // Check to see that the movement can be considered as dragging
7531 const qreal distX = point.position().x() - point.pressPosition().x();
7532 const qreal distY = point.position().y() - point.pressPosition().y();
7533 const qreal dragDist = qSqrt(distX * distX + distY * distY);
7534 if (dragDist > qApp->styleHints()->startDragDistance()) {
7535 switch (m_state) {
7536 case Tracking: {
7537 // Grab the image for dragging header
7538 const QPoint cell = tableView->cellAtPosition(point.position(), true);
7539 auto *item = tableView->itemAtCell(cell);
7540 if (!item)
7541 break;
7542 if (m_drag.isNull()) {
7543 m_drag = new QDrag(item);
7544 connect(m_drag.data(), &QDrag::actionChanged, this,
7545 &QQuickTableViewSectionDragHandler::handleDragDropAction);
7546 }
7547 // Connect the timer for scroling
7548 QObject::connect(&m_scrollTimer, &QTimer::timeout, this, [&]{
7549 const QSizeF dist = tableViewPrivate->scrollTowardsPoint(m_dragPoint, m_step);
7550 m_dragPoint.rx() += dist.width() > 0 ? m_step.width() : -m_step.width();
7551 m_dragPoint.ry() += dist.height() > 0 ? m_step.height() : -m_step.height();
7552 m_step = QSizeF(qAbs(dist.width() * 0.010), qAbs(dist.height() * 0.010));
7553 });
7554 // Set the drop area
7555 if (m_dropArea.isNull()) {
7556 m_dropArea = new QQuickDropArea(tableView);
7557 m_dropArea->setSize(tableView->size());
7558 connect(m_dropArea, &QQuickDropArea::positionChanged, this,
7559 &QQuickTableViewSectionDragHandler::handleDrag);
7560 connect(m_dropArea, &QQuickDropArea::dropped, this,
7561 &QQuickTableViewSectionDragHandler::handleDrop);
7562 }
7563 // Grab the image of the section
7564 m_grabResult = item->grabToImage();
7565 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this,
7566 &QQuickTableViewSectionDragHandler::grabSection);
7567 // Update source depending on the type of orientation
7568 m_source = (m_sectionOrientation == Qt::Horizontal) ? cell.x() : cell.y();
7569 m_state = DraggingStarted;
7570 // Set drag handler as active and it further handles section pointer events
7571 tableViewPrivate->setActivePointerHandler(this);
7572 }
7573 break;
7574
7575 case DraggingStarted: {
7576 if (m_drag && m_drag->mimeData()) {
7577 if (auto *item = qobject_cast<QQuickItem *>(m_drag->source())) {
7578 m_state = Dragging;
7579 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
7580 Q_UNUSED(itemPos);
7581 m_drag->setHotSpot(m_sectionOrientation == Qt::Horizontal ? QPoint(item->width()/2, itemPos.y()) : QPoint(itemPos.x(), item->height()/2));
7582 m_drag->exec();
7583 // If the state still remains dragging, means the drop happened outside the corresponding section handler's
7584 // drop area, better clear all the state.
7585 if (m_state == Dragging)
7586 resetDragData();
7587 // Reset the active handler
7588 tableViewPrivate->setActivePointerHandler(nullptr);
7589 }
7590 }
7591 }
7592 break;
7593
7594 default:
7595 break;
7596 }
7597 }
7598 }
7599}
7600
7601// ----------------------------------------------
7602void QQuickTableViewPrivate::initSectionDragHandler(Qt::Orientation orientation)
7603{
7604 if (!sectionDragHandler) {
7605 Q_Q(QQuickTableView);
7606 sectionDragHandler = new QQuickTableViewSectionDragHandler(q);
7607 sectionDragHandler->setSectionOrientation(orientation);
7608 }
7609}
7610
7611void QQuickTableViewPrivate::destroySectionDragHandler()
7612{
7613 if (sectionDragHandler) {
7614 delete sectionDragHandler;
7615 sectionDragHandler = nullptr;
7616 }
7617}
7618#endif // quick_draganddrop
7619
7620void QQuickTableViewPrivate::initializeIndexMapping()
7621{
7622 auto initIndices = [](auto& visualIndex, auto& logicalIndex, int size) {
7623 visualIndex.resize(size);
7624 logicalIndex.resize(size);
7625 for (int index = 0; index < size; ++index)
7626 visualIndex[index].index = logicalIndex[index].index = index;
7627 };
7628
7629 if (horizontalVisualIndices.size() != size_t(tableSize.width())
7630 || horizontalLogicalIndices.size() != size_t(tableSize.width()))
7631 initIndices(horizontalVisualIndices, horizontalLogicalIndices, tableSize.width());
7632
7633 if (verticalVisualIndices.size() != size_t(tableSize.height())
7634 || verticalLogicalIndices.size() != size_t(tableSize.height()))
7635 initIndices(verticalVisualIndices, verticalLogicalIndices, tableSize.height());
7636}
7637
7638void QQuickTableViewPrivate::clearIndexMapping()
7639{
7640 horizontalLogicalIndices.clear();
7641 horizontalVisualIndices.clear();
7642
7643 verticalLogicalIndices.clear();
7644 verticalVisualIndices.clear();
7645}
7646
7647int QQuickTableViewPrivate::logicalRowIndex(const int visualIndex) const
7648{
7649 if (syncView)
7650 return syncView->d_func()->logicalRowIndex(visualIndex);
7651 if (verticalLogicalIndices.empty() || visualIndex < 0)
7652 return visualIndex;
7653 return verticalLogicalIndices.at(visualIndex).index;
7654}
7655
7656int QQuickTableViewPrivate::logicalColumnIndex(const int visualIndex) const
7657{
7658 if (syncView)
7659 return syncView->d_func()->logicalColumnIndex(visualIndex);
7660 if (horizontalLogicalIndices.empty() || visualIndex < 0)
7661 return visualIndex;
7662 return horizontalLogicalIndices.at(visualIndex).index;
7663}
7664
7665int QQuickTableViewPrivate::visualRowIndex(const int logicalIndex) const
7666{
7667 if (syncView)
7668 return syncView->d_func()->visualRowIndex(logicalIndex);
7669 if (verticalVisualIndices.empty() || logicalIndex < 0)
7670 return logicalIndex;
7671 return verticalVisualIndices.at(logicalIndex).index;
7672}
7673
7674int QQuickTableViewPrivate::visualColumnIndex(const int logicalIndex) const
7675{
7676 if (syncView)
7677 return syncView->d_func()->visualColumnIndex(logicalIndex);
7678 if (horizontalVisualIndices.empty() || logicalIndex < 0)
7679 return logicalIndex;
7680 return horizontalVisualIndices.at(logicalIndex).index;
7681}
7682
7683int QQuickTableViewPrivate::getEditCellIndex(const QModelIndex &index) const
7684{
7685 // With subclasses that use a proxy model (e.g. TreeView),
7686 // always edit the cell at visual index.
7687 const bool hasProxyModel = (modelImpl() != assignedModel);
7688 return modelIndexToCellIndex(index, hasProxyModel);
7689}
7690
7691// ----------------------------------------------
7692
7693QQuickTableViewTapHandler::QQuickTableViewTapHandler(QQuickTableView *view)
7694 : QQuickTapHandler(view->contentItem())
7695{
7696 setObjectName("tableViewTapHandler");
7697}
7698
7699bool QQuickTableViewTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
7700{
7701 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7702 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7703 return tableViewPrivate->pointerNavigationEnabled && QQuickTapHandler::wantsEventPoint(event, point);
7704}
7705
7706QT_END_NAMESPACE
7707
7708#include "moc_qquicktableview_p.cpp"
7709#include "moc_qquicktableview_p_p.cpp"
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override
QQuickTableViewPointerHandler(QQuickTableView *view)
bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override
Returns true if the given point (as part of event) could be relevant at all to this handler,...
void onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *ev, QEventPoint &point) override
Notification that the grab has changed in some way which is relevant to this handler.
void updateState(QEventPoint &point)
void updateDrag(QPointerEvent *event, QEventPoint &point)
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override
bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override
Returns true if the given point (as part of event) could be relevant at all to this handler,...
QDebug operator<<(QDebug debug, QDir::Filters filters)
Definition qdir.cpp:2582
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
#define TV_REBUILDOPTION(OPTION)
static const Qt::Edge allTableEdges[]
#define Q_TABLEVIEW_ASSERT(cond, output)
#define Q_TABLEVIEW_UNREACHABLE(output)
#define TV_REBUILDSTATE(STATE)
static const char * kRequiredProperty_current
static const char * kRequiredProperty_tableView
static const char * kRequiredProperty_editing
static const char * kRequiredProperty_selected
static const char * kRequiredProperty_containsDrag
static const char * kRequiredProperties