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