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
5861 \include delegatemodelaccess.qdocinc
5862*/
5863QQmlDelegateModel::DelegateModelAccess QQuickTableView::delegateModelAccess() const
5864{
5865 Q_D(const QQuickTableView);
5866 return d->assignedDelegateModelAccess;
5867}
5868
5869void QQuickTableView::setDelegateModelAccess(
5870 QQmlDelegateModel::DelegateModelAccess delegateModelAccess)
5871{
5872 Q_D(QQuickTableView);
5873 if (delegateModelAccess == d->assignedDelegateModelAccess)
5874 return;
5875
5876 d->assignedDelegateModelAccess = delegateModelAccess;
5877 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
5878
5879 emit delegateModelAccessChanged();
5880}
5881
5882bool QQuickTableView::reuseItems() const
5883{
5884 return bool(d_func()->reusableFlag == QQmlTableInstanceModel::Reusable);
5885}
5886
5887void QQuickTableView::setReuseItems(bool reuse)
5888{
5889 Q_D(QQuickTableView);
5890 if (reuseItems() == reuse)
5891 return;
5892
5893 d->reusableFlag = reuse ? QQmlTableInstanceModel::Reusable : QQmlTableInstanceModel::NotReusable;
5894
5895 if (!reuse && d->tableModel) {
5896 // When we're told to not reuse items, we
5897 // immediately, as documented, drain the pool.
5898 d->tableModel->drainReusableItemsPool(0);
5899 }
5900
5901 emit reuseItemsChanged();
5902}
5903
5904void QQuickTableView::setContentWidth(qreal width)
5905{
5906 Q_D(QQuickTableView);
5907 d->explicitContentWidth = width;
5908 QQuickFlickable::setContentWidth(width);
5909}
5910
5911void QQuickTableView::setContentHeight(qreal height)
5912{
5913 Q_D(QQuickTableView);
5914 d->explicitContentHeight = height;
5915 QQuickFlickable::setContentHeight(height);
5916}
5917
5918/*!
5919 \qmlproperty TableView QtQuick::TableView::syncView
5920
5921 If this property of a TableView is set to another TableView, both the
5922 tables will synchronize with regard to flicking, column widths/row heights,
5923 and spacing according to \l syncDirection.
5924
5925 If \l syncDirection contains \l {Qt::Horizontal}{Qt.Horizontal}, current
5926 tableView's column widths, column spacing, and horizontal flicking movement
5927 synchronizes with syncView's.
5928
5929 If \l syncDirection contains \l {Qt::Vertical}{Qt.Vertical}, current
5930 tableView's row heights, row spacing, and vertical flicking movement
5931 synchronizes with syncView's.
5932
5933 \sa syncDirection
5934*/
5935QQuickTableView *QQuickTableView::syncView() const
5936{
5937 return d_func()->assignedSyncView;
5938}
5939
5940void QQuickTableView::setSyncView(QQuickTableView *view)
5941{
5942 Q_D(QQuickTableView);
5943 if (d->assignedSyncView == view)
5944 return;
5945
5946 // Clear existing index mapping information maintained
5947 // in the current view
5948 d->clearIndexMapping();
5949
5950 d->assignedSyncView = view;
5951 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
5952
5953 emit syncViewChanged();
5954}
5955
5956/*!
5957 \qmlproperty Qt::Orientations QtQuick::TableView::syncDirection
5958
5959 If the \l syncView is set on a TableView, this property controls
5960 synchronization of flicking direction(s) for both tables. The default is \c
5961 {Qt.Horizontal | Qt.Vertical}, which means that if you flick either table
5962 in either direction, the other table is flicked the same amount in the
5963 same direction.
5964
5965 This property and \l syncView can be used to make two tableViews
5966 synchronize with each other smoothly in flicking regardless of the different
5967 overshoot/undershoot, velocity, acceleration/deceleration or rebound
5968 animation, and so on.
5969
5970 A typical use case is to make several headers flick along with the table.
5971
5972 \sa syncView
5973*/
5974Qt::Orientations QQuickTableView::syncDirection() const
5975{
5976 return d_func()->assignedSyncDirection;
5977}
5978
5979void QQuickTableView::setSyncDirection(Qt::Orientations direction)
5980{
5981 Q_D(QQuickTableView);
5982 if (d->assignedSyncDirection == direction)
5983 return;
5984
5985 d->assignedSyncDirection = direction;
5986 if (d->assignedSyncView)
5987 d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly);
5988
5989 emit syncDirectionChanged();
5990}
5991
5992QItemSelectionModel *QQuickTableView::selectionModel() const
5993{
5994 return d_func()->selectionModel;
5995}
5996
5997void QQuickTableView::setSelectionModel(QItemSelectionModel *selectionModel)
5998{
5999 Q_D(QQuickTableView);
6000 if (d->selectionModel == selectionModel)
6001 return;
6002
6003 // Note: There is no need to rebuild the table when the selection model
6004 // changes, since selections only affect the internals of the delegate
6005 // items, and not the layout of the TableView.
6006
6007 if (d->selectionModel) {
6008 QQuickTableViewPrivate::disconnect(d->selectionModel, &QItemSelectionModel::selectionChanged,
6009 d, &QQuickTableViewPrivate::selectionChangedInSelectionModel);
6010 QQuickTableViewPrivate::disconnect(d->selectionModel, &QItemSelectionModel::currentChanged,
6011 d, &QQuickTableViewPrivate::currentChangedInSelectionModel);
6012 }
6013
6014 d->selectionModel = selectionModel;
6015
6016 if (d->selectionModel) {
6017 d->selectionModel->setModel(d->selectionSourceModel());
6018 QQuickTableViewPrivate::connect(d->selectionModel, &QItemSelectionModel::selectionChanged,
6019 d, &QQuickTableViewPrivate::selectionChangedInSelectionModel);
6020 QQuickTableViewPrivate::connect(d->selectionModel, &QItemSelectionModel::currentChanged,
6021 d, &QQuickTableViewPrivate::currentChangedInSelectionModel);
6022 }
6023
6024 d->updateSelectedOnAllDelegateItems();
6025
6026 emit selectionModelChanged();
6027}
6028
6029bool QQuickTableView::animate() const
6030{
6031 return d_func()->animate;
6032}
6033
6034void QQuickTableView::setAnimate(bool animate)
6035{
6036 Q_D(QQuickTableView);
6037 if (d->animate == animate)
6038 return;
6039
6040 d->animate = animate;
6041 if (!animate) {
6042 d->positionXAnimation.stop();
6043 d->positionYAnimation.stop();
6044 }
6045
6046 emit animateChanged();
6047}
6048
6049bool QQuickTableView::keyNavigationEnabled() const
6050{
6051 return d_func()->keyNavigationEnabled;
6052}
6053
6054void QQuickTableView::setKeyNavigationEnabled(bool enabled)
6055{
6056 Q_D(QQuickTableView);
6057 if (d->keyNavigationEnabled == enabled)
6058 return;
6059
6060 d->keyNavigationEnabled = enabled;
6061
6062 emit keyNavigationEnabledChanged();
6063}
6064
6065bool QQuickTableView::pointerNavigationEnabled() const
6066{
6067 return d_func()->pointerNavigationEnabled;
6068}
6069
6070void QQuickTableView::setPointerNavigationEnabled(bool enabled)
6071{
6072 Q_D(QQuickTableView);
6073 if (d->pointerNavigationEnabled == enabled)
6074 return;
6075
6076 d->pointerNavigationEnabled = enabled;
6077
6078 emit pointerNavigationEnabledChanged();
6079}
6080
6081int QQuickTableView::leftColumn() const
6082{
6083 Q_D(const QQuickTableView);
6084 return d->loadedItems.isEmpty() ? -1 : d_func()->leftColumn();
6085}
6086
6087int QQuickTableView::rightColumn() const
6088{
6089 Q_D(const QQuickTableView);
6090 return d->loadedItems.isEmpty() ? -1 : d_func()->rightColumn();
6091}
6092
6093int QQuickTableView::topRow() const
6094{
6095 Q_D(const QQuickTableView);
6096 return d->loadedItems.isEmpty() ? -1 : d_func()->topRow();
6097}
6098
6099int QQuickTableView::bottomRow() const
6100{
6101 Q_D(const QQuickTableView);
6102 return d->loadedItems.isEmpty() ? -1 : d_func()->bottomRow();
6103}
6104
6105int QQuickTableView::currentRow() const
6106{
6107 return d_func()->currentRow;
6108}
6109
6110int QQuickTableView::currentColumn() const
6111{
6112 return d_func()->currentColumn;
6113}
6114
6115void QQuickTableView::positionViewAtRow(int row, PositionMode mode, qreal offset, const QRectF &subRect)
6116{
6117 Q_D(QQuickTableView);
6118 if (row < 0 || row >= rows() || d->loadedRows.isEmpty())
6119 return;
6120
6121 // Note: PositionMode::Contain is from here on translated to (Qt::AlignTop | Qt::AlignBottom).
6122 // This is an internal (unsupported) combination which means "align bottom if the whole cell
6123 // fits inside the viewport, otherwise align top".
6124
6125 if (mode & (AlignTop | AlignBottom | AlignVCenter)) {
6126 mode &= AlignTop | AlignBottom | AlignVCenter;
6127 d->positionViewAtRow(row, Qt::Alignment(int(mode)), offset, subRect);
6128 } else if (mode == Contain) {
6129 if (row < topRow()) {
6130 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6131 } else if (row > bottomRow()) {
6132 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6133 } else if (row == topRow()) {
6134 if (!subRect.isValid()) {
6135 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6136 } else {
6137 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
6138 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
6139 if (subRectTop < d->viewportRect.y())
6140 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6141 else if (subRectBottom > d->viewportRect.bottom())
6142 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6143 }
6144 } else if (row == bottomRow()) {
6145 if (!subRect.isValid()) {
6146 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6147 } else {
6148 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
6149 // visible in the viewport, which means that the top side of the subRect is visible.
6150 const qreal subRectBottom = d->loadedTableInnerRect.bottom() + subRect.bottom();
6151 if (subRectBottom > d->viewportRect.bottom())
6152 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6153 }
6154 }
6155 } else if (mode == Visible) {
6156 if (row < topRow()) {
6157 d->positionViewAtRow(row, Qt::AlignTop, -offset, subRect);
6158 } else if (row > bottomRow()) {
6159 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6160 } else if (subRect.isValid()) {
6161 if (row == topRow()) {
6162 const qreal subRectTop = d->loadedTableOuterRect.top() + subRect.top();
6163 const qreal subRectBottom = d->loadedTableOuterRect.top() + subRect.bottom();
6164 if (subRectBottom < d->viewportRect.top())
6165 d->positionViewAtRow(row, Qt::AlignTop, offset, subRect);
6166 else if (subRectTop > d->viewportRect.bottom())
6167 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6168 } else if (row == bottomRow()) {
6169 // Note: entering here means that topRow() != bottomRow(). So at least two rows are
6170 // visible in the viewport, which means that the top side of the subRect is visible.
6171 const qreal subRectTop = d->loadedTableInnerRect.bottom() + subRect.top();
6172 if (subRectTop > d->viewportRect.bottom())
6173 d->positionViewAtRow(row, Qt::AlignTop | Qt::AlignBottom, offset, subRect);
6174 }
6175 }
6176 } else {
6177 qmlWarning(this) << "Unsupported mode:" << int(mode);
6178 }
6179}
6180
6181void QQuickTableView::positionViewAtColumn(int column, PositionMode mode, qreal offset, const QRectF &subRect)
6182{
6183 Q_D(QQuickTableView);
6184 if (column < 0 || column >= columns() || d->loadedColumns.isEmpty())
6185 return;
6186
6187 // Note: PositionMode::Contain is from here on translated to (Qt::AlignLeft | Qt::AlignRight).
6188 // This is an internal (unsupported) combination which means "align right if the whole cell
6189 // fits inside the viewport, otherwise align left".
6190
6191 if (mode & (AlignLeft | AlignRight | AlignHCenter)) {
6192 mode &= AlignLeft | AlignRight | AlignHCenter;
6193 d->positionViewAtColumn(column, Qt::Alignment(int(mode)), offset, subRect);
6194 } else if (mode == Contain) {
6195 if (column < leftColumn()) {
6196 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6197 } else if (column > rightColumn()) {
6198 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6199 } else if (column == leftColumn()) {
6200 if (!subRect.isValid()) {
6201 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6202 } else {
6203 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
6204 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
6205 if (subRectLeft < d->viewportRect.left())
6206 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6207 else if (subRectRight > d->viewportRect.right())
6208 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6209 }
6210 } else if (column == rightColumn()) {
6211 if (!subRect.isValid()) {
6212 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6213 } else {
6214 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
6215 // are visible in the viewport, which means that the left side of the subRect is visible.
6216 const qreal subRectRight = d->loadedTableInnerRect.right() + subRect.right();
6217 if (subRectRight > d->viewportRect.right())
6218 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6219 }
6220 }
6221 } else if (mode == Visible) {
6222 if (column < leftColumn()) {
6223 d->positionViewAtColumn(column, Qt::AlignLeft, -offset, subRect);
6224 } else if (column > rightColumn()) {
6225 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6226 } else if (subRect.isValid()) {
6227 if (column == leftColumn()) {
6228 const qreal subRectLeft = d->loadedTableOuterRect.left() + subRect.left();
6229 const qreal subRectRight = d->loadedTableOuterRect.left() + subRect.right();
6230 if (subRectRight < d->viewportRect.left())
6231 d->positionViewAtColumn(column, Qt::AlignLeft, offset, subRect);
6232 else if (subRectLeft > d->viewportRect.right())
6233 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6234 } else if (column == rightColumn()) {
6235 // Note: entering here means that leftColumn() != rightColumn(). So at least two columns
6236 // are visible in the viewport, which means that the left side of the subRect is visible.
6237 const qreal subRectLeft = d->loadedTableInnerRect.right() + subRect.left();
6238 if (subRectLeft > d->viewportRect.right())
6239 d->positionViewAtColumn(column, Qt::AlignLeft | Qt::AlignRight, offset, subRect);
6240 }
6241 }
6242 } else {
6243 qmlWarning(this) << "Unsupported mode:" << int(mode);
6244 }
6245}
6246
6247void QQuickTableView::positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6248{
6249 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6250 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6251 if (!horizontalMode && !verticalMode) {
6252 qmlWarning(this) << "Unsupported mode:" << int(mode);
6253 return;
6254 }
6255
6256 if (horizontalMode)
6257 positionViewAtColumn(cell.x(), horizontalMode, offset.x(), subRect);
6258 if (verticalMode)
6259 positionViewAtRow(cell.y(), verticalMode, offset.y(), subRect);
6260}
6261
6262void QQuickTableView::positionViewAtIndex(const QModelIndex &index, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6263{
6264 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6265 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6266 if (!horizontalMode && !verticalMode) {
6267 qmlWarning(this) << "Unsupported mode:" << int(mode);
6268 return;
6269 }
6270
6271 if (horizontalMode)
6272 positionViewAtColumn(columnAtIndex(index), horizontalMode, offset.x(), subRect);
6273 if (verticalMode)
6274 positionViewAtRow(rowAtIndex(index), verticalMode, offset.y(), subRect);
6275}
6276
6277#if QT_DEPRECATED_SINCE(6, 5)
6278void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset, const QRectF &subRect)
6279{
6280 PositionMode horizontalMode = mode & ~(AlignTop | AlignBottom | AlignVCenter);
6281 PositionMode verticalMode = mode & ~(AlignLeft | AlignRight | AlignHCenter);
6282 if (!horizontalMode && !verticalMode) {
6283 qmlWarning(this) << "Unsupported mode:" << int(mode);
6284 return;
6285 }
6286
6287 if (horizontalMode)
6288 positionViewAtColumn(column, horizontalMode, offset.x(), subRect);
6289 if (verticalMode)
6290 positionViewAtRow(row, verticalMode, offset.y(), subRect);
6291}
6292#endif
6293
6294void QQuickTableView::moveColumn(int source, int destination)
6295{
6296 Q_D(QQuickTableView);
6297 d->moveSection(source, destination, Qt::Horizontal);
6298}
6299
6300void QQuickTableView::moveRow(int source, int destination)
6301{
6302 Q_D(QQuickTableView);
6303 d->moveSection(source, destination, Qt::Vertical);
6304}
6305
6306void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientations orientation)
6307{
6308 Q_Q(QQuickTableView);
6309
6310 if (source < 0 || destination < 0 ||
6311 (orientation == Qt::Horizontal &&
6312 (source >= tableSize.width() || destination >= tableSize.width())) ||
6313 (orientation == Qt::Vertical &&
6314 (source >= tableSize.height() || destination >= tableSize.height())))
6315 return;
6316
6317 if (source == destination)
6318 return;
6319
6320 if (m_sectionState != SectionState::Moving) {
6321 m_sectionState = SectionState::Moving;
6322 if (syncView)
6323 syncView->d_func()->moveSection(source, destination, orientation);
6324 else {
6325 // Initialize the visual and logical index mapping
6326 initializeIndexMapping();
6327
6328 // Set current index mapping according to moving rows or columns
6329 SectionData *visualIndex = nullptr;
6330 SectionData *logicalIndex = nullptr;
6331
6332 if (orientation == Qt::Horizontal) {
6333 visualIndex = visualIndices[0].data();
6334 logicalIndex = logicalIndices[0].data();
6335 } else if (orientation == Qt::Vertical) {
6336 visualIndex = visualIndices[1].data();
6337 logicalIndex = logicalIndices[1].data();
6338 }
6339
6340 const int logical = logicalIndex[source].index;
6341 int visual = source;
6342
6343 if (destination > source) {
6344 while (visual < destination) {
6345 SectionData &visualData = visualIndex[logicalIndex[visual + 1].index];
6346 SectionData &logicalData = logicalIndex[visual];
6347 visualData.prevIndex = visualData.index;
6348 visualData.index = visual;
6349 logicalData.prevIndex = logicalData.index;
6350 logicalData.index = logicalIndex[visual + 1].index;
6351 ++visual;
6352 }
6353 } else {
6354 while (visual > destination) {
6355 SectionData &visualData = visualIndex[logicalIndex[visual - 1].index];
6356 SectionData &logicalData = logicalIndex[visual];
6357 visualData.prevIndex = visualData.index;
6358 visualData.index = visual;
6359 logicalData.prevIndex = logicalData.index;
6360 logicalData.index = logicalIndex[visual - 1].index;
6361 --visual;
6362 }
6363 }
6364
6365 visualIndex[logical].prevIndex = visualIndex[logical].index;
6366 visualIndex[logical].index = destination;
6367 logicalIndex[destination].prevIndex = logicalIndex[destination].index;
6368 logicalIndex[destination].index = logical;
6369
6370 // Trigger section move for horizontal and vertical child views
6371 // Used in a case where moveSection() triggered for table view
6372 for (auto syncChild : std::as_const(syncChildren)) {
6373 auto syncChild_d = syncChild->d_func();
6374 if (syncChild_d->m_sectionState != SectionState::Moving &&
6375 ((syncChild_d->syncHorizontally && orientation == Qt::Horizontal) ||
6376 (syncChild_d->syncVertically && orientation == Qt::Vertical)))
6377 syncChild_d->moveSection(source, destination, orientation);
6378 }
6379 }
6380
6381 // Rebuild the view to reflect the section order
6382 scheduleRebuildTable(RebuildOption::ViewportOnly);
6383 m_sectionState = SectionState::Idle;
6384
6385 // Emit section moved signal for the sections moved in the view
6386 const int startIndex = (source > destination) ? destination : source;
6387 const int endIndex = (source > destination) ? source : destination;
6388 const int mapIndex = static_cast<int>(orientation) - 1;
6389 for (int index = startIndex; index <= endIndex; index++) {
6390 const SectionData *logicalDataIndices = (syncView ? syncView->d_func()->logicalIndices[mapIndex].constData() : logicalIndices[mapIndex].constData());
6391 const SectionData *visualDataIndices = syncView ? syncView->d_func()->visualIndices[mapIndex].constData() : visualIndices[mapIndex].constData();
6392 const int prevLogicalIndex = logicalDataIndices[index].prevIndex;
6393 if (orientation == Qt::Horizontal)
6394 emit q->columnMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
6395 else
6396 emit q->rowMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index);
6397 }
6398 }
6399}
6400
6401void QQuickTableView::clearColumnReordering()
6402{
6403 Q_D(QQuickTableView);
6404 d->clearSection(Qt::Horizontal);
6405}
6406
6407void QQuickTableView::clearRowReordering()
6408{
6409 Q_D(QQuickTableView);
6410 d->clearSection(Qt::Vertical);
6411}
6412
6413void QQuickTableViewPrivate::clearSection(Qt::Orientations orientation)
6414{
6415 Q_Q(QQuickTableView);
6416
6417 const int mapIndex = static_cast<int>(orientation) - 1;
6418 const QList<SectionData> oldLogicalIndices = syncView ? syncView->d_func()->logicalIndices[mapIndex] : logicalIndices[mapIndex];
6419 const QList<SectionData> oldVisualIndices = syncView ? syncView->d_func()->visualIndices[mapIndex] : visualIndices[mapIndex];;
6420
6421 if (syncView)
6422 syncView->d_func()->clearSection(orientation);
6423 else {
6424 // Clear the index mapping and rebuild the table
6425 logicalIndices[mapIndex].clear();
6426 visualIndices[mapIndex].clear();
6427 scheduleRebuildTable(RebuildOption::ViewportOnly);
6428 }
6429
6430 // Emit section moved signal for the sections moved in the view
6431 for (int index = 0; index < oldLogicalIndices.size(); index++) {
6432 const SectionData *logicalDataIndices = oldLogicalIndices.constData();
6433 const SectionData *visualDataIndices = oldVisualIndices.constData();
6434 if (logicalDataIndices[index].index != index) {
6435 const int currentIndex = logicalDataIndices[index].index;
6436 if (orientation == Qt::Horizontal)
6437 emit q->columnMoved(currentIndex, visualDataIndices[currentIndex].index, index);
6438 else
6439 emit q->rowMoved(currentIndex, visualDataIndices[currentIndex].index, index);
6440 }
6441 }
6442}
6443
6444void QQuickTableViewPrivate::setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay)
6445{
6446 if (!modelIndex.isValid())
6447 return;
6448
6449 const int cellIndex = modelIndexToCellIndex(modelIndex);
6450 if (!loadedItems.contains(cellIndex))
6451 return;
6452 const QPoint cell = cellAtModelIndex(cellIndex);
6453 QQuickItem *item = loadedTableItem(cell)->item;
6454 setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(overlay), cellIndex, item, false);
6455}
6456
6457QQuickItem *QQuickTableView::itemAtCell(const QPoint &cell) const
6458{
6459 Q_D(const QQuickTableView);
6460 const int modelIndex = d->modelIndexAtCell(cell);
6461 if (!d->loadedItems.contains(modelIndex))
6462 return nullptr;
6463 return d->loadedItems.value(modelIndex)->item;
6464}
6465
6466#if QT_DEPRECATED_SINCE(6, 5)
6467QQuickItem *QQuickTableView::itemAtCell(int column, int row) const
6468{
6469 return itemAtCell(QPoint(column, row));
6470}
6471#endif
6472
6473QQuickItem *QQuickTableView::itemAtIndex(const QModelIndex &index) const
6474{
6475 Q_D(const QQuickTableView);
6476 const int serializedIndex = d->modelIndexToCellIndex(index);
6477 if (!d->loadedItems.contains(serializedIndex))
6478 return nullptr;
6479 return d->loadedItems.value(serializedIndex)->item;
6480}
6481
6482#if QT_DEPRECATED_SINCE(6, 4)
6483QPoint QQuickTableView::cellAtPos(qreal x, qreal y, bool includeSpacing) const
6484{
6485 return cellAtPosition(mapToItem(contentItem(), {x, y}), includeSpacing);
6486}
6487
6488QPoint QQuickTableView::cellAtPos(const QPointF &position, bool includeSpacing) const
6489{
6490 return cellAtPosition(mapToItem(contentItem(), position), includeSpacing);
6491}
6492#endif
6493
6494QPoint QQuickTableView::cellAtPosition(qreal x, qreal y, bool includeSpacing) const
6495{
6496 return cellAtPosition(QPoint(x, y), includeSpacing);
6497}
6498
6499QPoint QQuickTableView::cellAtPosition(const QPointF &position, bool includeSpacing) const
6500{
6501 Q_D(const QQuickTableView);
6502
6503 if (!d->loadedTableOuterRect.contains(position))
6504 return QPoint(-1, -1);
6505
6506 const qreal hSpace = d->cellSpacing.width();
6507 const qreal vSpace = d->cellSpacing.height();
6508 qreal currentColumnEnd = d->loadedTableOuterRect.x();
6509 qreal currentRowEnd = d->loadedTableOuterRect.y();
6510
6511 int foundColumn = -1;
6512 int foundRow = -1;
6513
6514 for (const int column : d->loadedColumns) {
6515 currentColumnEnd += d->getEffectiveColumnWidth(column);
6516 if (position.x() < currentColumnEnd) {
6517 foundColumn = column;
6518 break;
6519 }
6520 currentColumnEnd += hSpace;
6521 if (!includeSpacing && position.x() < currentColumnEnd) {
6522 // Hit spacing
6523 return QPoint(-1, -1);
6524 } else if (includeSpacing && position.x() < currentColumnEnd - (hSpace / 2)) {
6525 foundColumn = column;
6526 break;
6527 }
6528 }
6529
6530 for (const int row : d->loadedRows) {
6531 currentRowEnd += d->getEffectiveRowHeight(row);
6532 if (position.y() < currentRowEnd) {
6533 foundRow = row;
6534 break;
6535 }
6536 currentRowEnd += vSpace;
6537 if (!includeSpacing && position.y() < currentRowEnd) {
6538 // Hit spacing
6539 return QPoint(-1, -1);
6540 }
6541 if (includeSpacing && position.y() < currentRowEnd - (vSpace / 2)) {
6542 foundRow = row;
6543 break;
6544 }
6545 }
6546
6547 return QPoint(foundColumn, foundRow);
6548}
6549
6550bool QQuickTableView::isColumnLoaded(int column) const
6551{
6552 Q_D(const QQuickTableView);
6553 if (!d->loadedColumns.contains(column))
6554 return false;
6555
6556 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6557 // TableView is rebuilding, and none of the rows and columns
6558 // are completely loaded until we reach the layout phase.
6559 if (d->rebuildState < QQuickTableViewPrivate::RebuildState::LayoutTable)
6560 return false;
6561 }
6562
6563 return true;
6564}
6565
6566bool QQuickTableView::isRowLoaded(int row) const
6567{
6568 Q_D(const QQuickTableView);
6569 if (!d->loadedRows.contains(row))
6570 return false;
6571
6572 if (d->rebuildState != QQuickTableViewPrivate::RebuildState::Done) {
6573 // TableView is rebuilding, and none of the rows and columns
6574 // are completely loaded until we reach the layout phase.
6575 if (d->rebuildState < QQuickTableViewPrivate::RebuildState::LayoutTable)
6576 return false;
6577 }
6578
6579 return true;
6580}
6581
6582qreal QQuickTableView::columnWidth(int column) const
6583{
6584 Q_D(const QQuickTableView);
6585 if (!isColumnLoaded(column))
6586 return -1;
6587
6588 return d->getEffectiveColumnWidth(column);
6589}
6590
6591qreal QQuickTableView::rowHeight(int row) const
6592{
6593 Q_D(const QQuickTableView);
6594 if (!isRowLoaded(row))
6595 return -1;
6596
6597 return d->getEffectiveRowHeight(row);
6598}
6599
6600qreal QQuickTableView::implicitColumnWidth(int column) const
6601{
6602 Q_D(const QQuickTableView);
6603 if (!isColumnLoaded(column))
6604 return -1;
6605
6606 return d->sizeHintForColumn(column);
6607}
6608
6609qreal QQuickTableView::implicitRowHeight(int row) const
6610{
6611 Q_D(const QQuickTableView);
6612 if (!isRowLoaded(row))
6613 return -1;
6614
6615 return d->sizeHintForRow(row);
6616}
6617
6618void QQuickTableView::setColumnWidth(int column, qreal size)
6619{
6620 Q_D(QQuickTableView);
6621 if (column < 0) {
6622 qmlWarning(this) << "column must be greather than, or equal to, zero";
6623 return;
6624 }
6625
6626 if (d->syncHorizontally) {
6627 d->syncView->setColumnWidth(column, size);
6628 return;
6629 }
6630
6631 if (qFuzzyCompare(explicitColumnWidth(column), size))
6632 return;
6633
6634 if (size < 0)
6635 d->explicitColumnWidths.remove(d->logicalColumnIndex(column));
6636 else
6637 d->explicitColumnWidths.insert(d->logicalColumnIndex(column), size);
6638
6639 if (d->loadedItems.isEmpty())
6640 return;
6641
6642 const bool allColumnsLoaded = d->atTableEnd(Qt::LeftEdge) && d->atTableEnd(Qt::RightEdge);
6643 if (column >= leftColumn() || column <= rightColumn() || allColumnsLoaded)
6644 d->forceLayout(false);
6645}
6646
6647void QQuickTableView::clearColumnWidths()
6648{
6649 Q_D(QQuickTableView);
6650
6651 if (d->syncHorizontally) {
6652 d->syncView->clearColumnWidths();
6653 return;
6654 }
6655
6656 if (d->explicitColumnWidths.isEmpty())
6657 return;
6658
6659 d->explicitColumnWidths.clear();
6660 d->forceLayout(false);
6661}
6662
6663qreal QQuickTableView::explicitColumnWidth(int column) const
6664{
6665 Q_D(const QQuickTableView);
6666
6667 if (d->syncHorizontally)
6668 return d->syncView->explicitColumnWidth(column);
6669
6670 const auto it = d->explicitColumnWidths.constFind(d->logicalColumnIndex(column));
6671 if (it != d->explicitColumnWidths.constEnd())
6672 return *it;
6673 return -1;
6674}
6675
6676void QQuickTableView::setRowHeight(int row, qreal size)
6677{
6678 Q_D(QQuickTableView);
6679 if (row < 0) {
6680 qmlWarning(this) << "row must be greather than, or equal to, zero";
6681 return;
6682 }
6683
6684 if (d->syncVertically) {
6685 d->syncView->setRowHeight(row, size);
6686 return;
6687 }
6688
6689 if (qFuzzyCompare(explicitRowHeight(row), size))
6690 return;
6691
6692 if (size < 0)
6693 d->explicitRowHeights.remove(d->logicalRowIndex(row));
6694 else
6695 d->explicitRowHeights.insert(d->logicalRowIndex(row), size);
6696
6697 if (d->loadedItems.isEmpty())
6698 return;
6699
6700 const bool allRowsLoaded = d->atTableEnd(Qt::TopEdge) && d->atTableEnd(Qt::BottomEdge);
6701 if (row >= topRow() || row <= bottomRow() || allRowsLoaded)
6702 d->forceLayout(false);
6703}
6704
6705void QQuickTableView::clearRowHeights()
6706{
6707 Q_D(QQuickTableView);
6708
6709 if (d->syncVertically) {
6710 d->syncView->clearRowHeights();
6711 return;
6712 }
6713
6714 if (d->explicitRowHeights.isEmpty())
6715 return;
6716
6717 d->explicitRowHeights.clear();
6718 d->forceLayout(false);
6719}
6720
6721qreal QQuickTableView::explicitRowHeight(int row) const
6722{
6723 Q_D(const QQuickTableView);
6724
6725 if (d->syncVertically)
6726 return d->syncView->explicitRowHeight(row);
6727
6728 const auto it = d->explicitRowHeights.constFind(d->logicalRowIndex(row));
6729 if (it != d->explicitRowHeights.constEnd())
6730 return *it;
6731 return -1;
6732}
6733
6734QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const
6735{
6736 Q_D(const QQuickTableView);
6737 if (cell.x() < 0 || cell.x() >= columns() || cell.y() < 0 || cell.y() >= rows())
6738 return {};
6739
6740 auto const qaim = d->model->abstractItemModel();
6741 if (!qaim)
6742 return {};
6743
6744 return qaim->index(d->logicalRowIndex(cell.y()), d->logicalColumnIndex(cell.x()));
6745}
6746
6747QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const
6748{
6749 if (!index.isValid() || index.parent().isValid())
6750 return {-1, -1};
6751 Q_D(const QQuickTableView);
6752 return {d->visualColumnIndex(index.column()), d->visualRowIndex(index.row())};
6753}
6754
6755#if QT_DEPRECATED_SINCE(6, 4)
6756QModelIndex QQuickTableView::modelIndex(int row, int column) const
6757{
6758 static bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
6759 if (compat6_4) {
6760 // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
6761 // being documented to be specified in the opposite order.
6762 // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
6763 // to continue accepting calls to modelIndex(column, row).
6764 return modelIndex({row, column});
6765 } else {
6766 qmlWarning(this) << "modelIndex(row, column) is deprecated. "
6767 "Use index(row, column) instead. For more information, see "
6768 "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
6769 return modelIndex({column, row});
6770 }
6771}
6772#endif
6773
6774QModelIndex QQuickTableView::index(int row, int column) const
6775{
6776 return modelIndex({column, row});
6777}
6778
6779int QQuickTableView::rowAtIndex(const QModelIndex &index) const
6780{
6781 return cellAtIndex(index).y();
6782}
6783
6784int QQuickTableView::columnAtIndex(const QModelIndex &index) const
6785{
6786 return cellAtIndex(index).x();
6787}
6788
6789void QQuickTableView::forceLayout()
6790{
6791 d_func()->forceLayout(true);
6792}
6793
6794void QQuickTableView::edit(const QModelIndex &index)
6795{
6796 Q_D(QQuickTableView);
6797
6798 if (!d->canEdit(index, true))
6799 return;
6800
6801 if (d->editIndex == index)
6802 return;
6803
6804 if (!d->tableModel)
6805 return;
6806
6807 if (!d->editModel) {
6808 d->editModel = new QQmlTableInstanceModel(qmlContext(this));
6809 d->editModel->useImportVersion(d->resolveImportVersion());
6810 QObject::connect(d->editModel, &QQmlInstanceModel::initItem,
6811 [this, d] (int serializedModelIndex, QObject *object) {
6812 // initItemCallback will call setRequiredProperty for each required property in the
6813 // delegate, both for this class, but also also for any subclasses. setRequiredProperty
6814 // is currently dependent of the QQmlTableInstanceModel that was used to create the object
6815 // in order to initialize required properties, so we need to set the editItem variable
6816 // early on, so that we can use it in setRequiredProperty.
6817 const QPoint cell = d->cellAtModelIndex(serializedModelIndex);
6818 d->editIndex = modelIndex({d->visualColumnIndex(cell.x()), d->visualRowIndex(cell.y())});
6819 d->editItem = qmlobject_cast<QQuickItem*>(object);
6820 if (!d->editItem)
6821 return;
6822 // Initialize required properties
6823 d->initItemCallback(serializedModelIndex, object);
6824 const auto cellItem = itemAtCell(cellAtIndex(d->editIndex));
6825 Q_ASSERT(cellItem);
6826 d->editItem->setParentItem(cellItem);
6827 // Move the cell item to the top of the other items, to ensure
6828 // that e.g a focus frame ends up on top of all the cells
6829 cellItem->setZ(2);
6830 });
6831 }
6832
6833 if (d->selectionModel)
6834 d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
6835
6836 // If the user is already editing another cell, close that editor first
6837 d->closeEditorAndCommit();
6838
6839 const auto cellItem = itemAtCell(cellAtIndex(index));
6840 Q_ASSERT(cellItem);
6841 const auto attached = d->getAttachedObject(cellItem);
6842 Q_ASSERT(attached);
6843
6844 d->editModel->setModel(d->tableModel->model());
6845 d->editModel->setDelegate(attached->editDelegate());
6846
6847 const int cellIndex = d->getEditCellIndex(index);
6848 QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous);
6849 if (!object) {
6850 d->editIndex = QModelIndex();
6851 d->editItem = nullptr;
6852 qmlWarning(this) << "cannot edit: TableView.editDelegate could not be instantiated!";
6853 return;
6854 }
6855
6856 // Note: at this point, editIndex and editItem has been set from initItem!
6857
6858 if (!d->editItem) {
6859 qmlWarning(this) << "cannot edit: TableView.editDelegate is not an Item!";
6860 d->editItem = nullptr;
6861 d->editIndex = QModelIndex();
6862 d->editModel->release(object, QQmlInstanceModel::NotReusable);
6863 return;
6864 }
6865
6866 // Reference the cell item once more, so that it doesn't
6867 // get reused or deleted if it leaves the viewport.
6868 d->model->object(cellIndex, QQmlIncubator::Synchronous);
6869
6870 // Inform the delegate, and the edit delegate, that they're being edited
6871 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(true), cellIndex, cellItem, false);
6872
6873 // Transfer focus to the edit item
6874 d->editItem->forceActiveFocus(Qt::MouseFocusReason);
6875 (void)d->installEventFilterOnFocusObjectInsideEditItem();
6876}
6877
6878void QQuickTableView::closeEditor()
6879{
6880 Q_D(QQuickTableView);
6881
6882 if (!d->editItem)
6883 return;
6884
6885 QQuickItem *cellItem = d->editItem->parentItem();
6886 d->editModel->release(d->editItem, QQmlInstanceModel::NotReusable);
6887 d->editItem = nullptr;
6888
6889 cellItem->setZ(1);
6890 const int cellIndex = d->getEditCellIndex(d->editIndex);
6891 d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false);
6892 // Remove the extra reference we sat on the cell item from edit()
6893 d->model->release(cellItem, QQmlInstanceModel::NotReusable);
6894
6895 if (d->editIndex.isValid()) {
6896 // Note: we can have an invalid editIndex, even when we
6897 // have an editItem, if the model has changed (e.g been reset)!
6898 d->editIndex = QModelIndex();
6899 }
6900}
6901
6902QQuickTableViewAttached *QQuickTableView::qmlAttachedProperties(QObject *obj)
6903{
6904 return new QQuickTableViewAttached(obj);
6905}
6906
6907void QQuickTableView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
6908{
6909 Q_D(QQuickTableView);
6910 QQuickFlickable::geometryChange(newGeometry, oldGeometry);
6911
6912 if (d->tableModel) {
6913 // When the view changes size, we force the pool to
6914 // shrink by releasing all pooled items.
6915 d->tableModel->drainReusableItemsPool(0);
6916 }
6917
6918 d->forceLayout(false);
6919}
6920
6921void QQuickTableView::viewportMoved(Qt::Orientations orientation)
6922{
6923 Q_D(QQuickTableView);
6924
6925 // If the new viewport position was set from the setLocalViewportXY()
6926 // functions, we just update the position silently and return. Otherwise, if
6927 // the viewport was flicked by the user, or some other control, we
6928 // recursively sync all the views in the hierarchy to the same position.
6929 QQuickFlickable::viewportMoved(orientation);
6930 if (d->inSetLocalViewportPos)
6931 return;
6932
6933 // Move all views in the syncView hierarchy to the same contentX/Y.
6934 // We need to start from this view (and not the root syncView) to
6935 // ensure that we respect all the individual syncDirection flags
6936 // between the individual views in the hierarchy.
6937 d->syncViewportPosRecursive();
6938
6939 auto rootView = d->rootSyncView();
6940 auto rootView_d = rootView->d_func();
6941
6942 rootView_d->scheduleRebuildIfFastFlick();
6943
6944 if (!rootView_d->polishScheduled) {
6945 if (rootView_d->scheduledRebuildOptions) {
6946 // When we need to rebuild, collecting several viewport
6947 // moves and do a single polish gives a quicker UI.
6948 rootView->polish();
6949 } else {
6950 // Updating the table right away when flicking
6951 // slowly gives a smoother experience.
6952 const bool updated = rootView->d_func()->updateTableRecursive();
6953 if (!updated) {
6954 // One, or more, of the views are already in an
6955 // update, so we need to wait a cycle.
6956 rootView->polish();
6957 }
6958 }
6959 }
6960}
6961
6962void QQuickTableView::keyPressEvent(QKeyEvent *e)
6963{
6964 Q_D(QQuickTableView);
6965
6966 if (!d->keyNavigationEnabled) {
6967 QQuickFlickable::keyPressEvent(e);
6968 return;
6969 }
6970
6971 if (d->tableSize.isEmpty())
6972 return;
6973
6974 if (d->editIndex.isValid()) {
6975 // While editing, we limit the keys that we
6976 // handle to not interfere with editing.
6977 return;
6978 }
6979
6980 if (d->setCurrentIndexFromKeyEvent(e))
6981 return;
6982
6983 if (d->editFromKeyEvent(e))
6984 return;
6985
6986 QQuickFlickable::keyPressEvent(e);
6987}
6988
6989bool QQuickTableView::eventFilter(QObject *obj, QEvent *event)
6990{
6991 Q_D(QQuickTableView);
6992
6993 if (obj != d->editItem && !d->editItem->isAncestorOf(qobject_cast<QQuickItem *>(obj))) {
6994 // We might also receive events from old editItems that are about to be
6995 // destroyed (such as DefferedDelete events). Just ignore those events.
6996 return QQuickFlickable::eventFilter(obj, event);
6997 }
6998
6999 switch (event->type()) {
7000 case QEvent::KeyPress: {
7001 Q_ASSERT(d->editItem);
7002 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
7003 switch (keyEvent->key()) {
7004 case Qt::Key_Enter:
7005 case Qt::Key_Return:
7006 d->closeEditorAndCommit();
7007 return true;
7008 case Qt::Key_Tab:
7009 case Qt::Key_Backtab:
7010 if (activeFocusOnTab()) {
7011 if (d->setCurrentIndexFromKeyEvent(keyEvent)) {
7012 const QModelIndex currentIndex = d->selectionModel->currentIndex();
7013 if (d->canEdit(currentIndex, false))
7014 edit(currentIndex);
7015 }
7016 return true;
7017 }
7018 break;
7019 case Qt::Key_Escape:
7020 closeEditor();
7021 return true;
7022 }
7023 break; }
7024 case QEvent::FocusOut:
7025 // If focus was transferred within the edit delegate, we start to filter
7026 // the new focus object. Otherwise we close the edit delegate.
7027 if (!d->installEventFilterOnFocusObjectInsideEditItem())
7028 d->closeEditorAndCommit();
7029 break;
7030 default:
7031 break;
7032 }
7033
7034 return QQuickFlickable::eventFilter(obj, event);
7035}
7036
7037bool QQuickTableView::alternatingRows() const
7038{
7039 return d_func()->alternatingRows;
7040}
7041
7042void QQuickTableView::setAlternatingRows(bool alternatingRows)
7043{
7044 Q_D(QQuickTableView);
7045 if (d->alternatingRows == alternatingRows)
7046 return;
7047
7048 d->alternatingRows = alternatingRows;
7049 emit alternatingRowsChanged();
7050}
7051
7052QQuickTableView::SelectionBehavior QQuickTableView::selectionBehavior() const
7053{
7054 return d_func()->selectionBehavior;
7055}
7056
7057void QQuickTableView::setSelectionBehavior(SelectionBehavior selectionBehavior)
7058{
7059 Q_D(QQuickTableView);
7060 if (d->selectionBehavior == selectionBehavior)
7061 return;
7062
7063 d->selectionBehavior = selectionBehavior;
7064 emit selectionBehaviorChanged();
7065}
7066
7067QQuickTableView::SelectionMode QQuickTableView::selectionMode() const
7068{
7069 return d_func()->selectionMode;
7070}
7071
7072void QQuickTableView::setSelectionMode(SelectionMode selectionMode)
7073{
7074 Q_D(QQuickTableView);
7075 if (d->selectionMode == selectionMode)
7076 return;
7077
7078 d->selectionMode = selectionMode;
7079 emit selectionModeChanged();
7080}
7081
7082bool QQuickTableView::resizableColumns() const
7083{
7084 return d_func()->resizableColumns;
7085}
7086
7087void QQuickTableView::setResizableColumns(bool enabled)
7088{
7089 Q_D(QQuickTableView);
7090 if (d->resizableColumns == enabled)
7091 return;
7092
7093 d->resizableColumns = enabled;
7094 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
7095 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
7096
7097 emit resizableColumnsChanged();
7098}
7099
7100bool QQuickTableView::resizableRows() const
7101{
7102 return d_func()->resizableRows;
7103}
7104
7105void QQuickTableView::setResizableRows(bool enabled)
7106{
7107 Q_D(QQuickTableView);
7108 if (d->resizableRows == enabled)
7109 return;
7110
7111 d->resizableRows = enabled;
7112 d->resizeHandler->setEnabled(d->resizableRows || d->resizableColumns);
7113 d->hoverHandler->setEnabled(d->resizableRows || d->resizableColumns);
7114
7115 emit resizableRowsChanged();
7116}
7117
7118// ----------------------------------------------
7119QQuickTableViewHoverHandler::QQuickTableViewHoverHandler(QQuickTableView *view)
7120 : QQuickHoverHandler(view->contentItem())
7121{
7122 setMargin(5);
7123
7124 connect(this, &QQuickHoverHandler::hoveredChanged, [this] {
7125 if (!isHoveringGrid())
7126 return;
7127 m_row = -1;
7128 m_column = -1;
7129#if QT_CONFIG(cursor)
7130 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7131 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7132 tableViewPrivate->updateCursor();
7133#endif
7134 });
7135}
7136
7137void QQuickTableViewHoverHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
7138{
7139 QQuickHoverHandler::handleEventPoint(event, point);
7140
7141 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7142#if QT_CONFIG(cursor)
7143 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7144#endif
7145
7146 const QPoint cell = tableView->cellAtPosition(point.position(), true);
7147 const auto item = tableView->itemAtCell(cell);
7148 if (!item) {
7149 m_row = -1;
7150 m_column = -1;
7151#if QT_CONFIG(cursor)
7152 tableViewPrivate->updateCursor();
7153#endif
7154 return;
7155 }
7156
7157 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
7158 const bool hoveringRow = (itemPos.y() < margin() || itemPos.y() > item->height() - margin());
7159 const bool hoveringColumn = (itemPos.x() < margin() || itemPos.x() > item->width() - margin());
7160 m_row = hoveringRow ? itemPos.y() < margin() ? cell.y() - 1 : cell.y() : -1;
7161 m_column = hoveringColumn ? itemPos.x() < margin() ? cell.x() - 1 : cell.x() : -1;
7162#if QT_CONFIG(cursor)
7163 tableViewPrivate->updateCursor();
7164#endif
7165}
7166
7167// ----------------------------------------------
7168
7171{
7172 // Set a grab permission that stops the flickable, as well as
7173 // any drag handler inside the delegate, from stealing the drag.
7174 setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything);
7175}
7176
7177bool QQuickTableViewPointerHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
7178{
7179 if (!QQuickSinglePointHandler::wantsEventPoint(event, point))
7180 return false;
7181
7182 // If we have a mouse wheel event then we do not want to do anything related to resizing.
7183 if (event->type() == QEvent::Type::Wheel)
7184 return false;
7185
7186 // When the user is flicking, we disable resizing, so that
7187 // he doesn't start to resize by accident.
7188 const auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7189 return !tableView->isMoving();
7190}
7191
7192// ----------------------------------------------
7193
7194QQuickTableViewResizeHandler::QQuickTableViewResizeHandler(QQuickTableView *view)
7196{
7197 setMargin(5);
7198 setObjectName("tableViewResizeHandler");
7199}
7200
7201void QQuickTableViewResizeHandler::onGrabChanged(QQuickPointerHandler *grabber
7202 , QPointingDevice::GrabTransition transition
7203 , QPointerEvent *ev
7204 , QEventPoint &point)
7205{
7206 QQuickSinglePointHandler::onGrabChanged(grabber, transition, ev, point);
7207
7208 switch (transition) {
7209 case QPointingDevice::GrabPassive:
7210 case QPointingDevice::GrabExclusive:
7211 break;
7212 case QPointingDevice::UngrabPassive:
7213 case QPointingDevice::UngrabExclusive:
7214 case QPointingDevice::CancelGrabPassive:
7215 case QPointingDevice::CancelGrabExclusive:
7216 case QPointingDevice::OverrideGrabPassive:
7217 if (m_state == DraggingStarted || m_state == Dragging) {
7218 m_state = DraggingFinished;
7219 updateDrag(ev, point);
7220 }
7221 break;
7222 }
7223}
7224
7225void QQuickTableViewResizeHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
7226{
7227 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7228 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7229 const auto *activeHandler = tableViewPrivate->activePointerHandler();
7230 if (activeHandler && !qobject_cast<const QQuickTableViewResizeHandler *>(activeHandler))
7231 return;
7232
7233 // Resolve which state we're in first...
7234 updateState(point);
7235 // ...and act on it next
7236 updateDrag(event, point);
7237}
7238
7240{
7241 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7242 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7243
7244 if (m_state == DraggingFinished)
7245 m_state = Listening;
7246
7247 if (point.state() == QEventPoint::Pressed) {
7248 m_row = tableViewPrivate->resizableRows ? tableViewPrivate->hoverHandler->m_row : -1;
7249 m_column = tableViewPrivate->resizableColumns ? tableViewPrivate->hoverHandler->m_column : -1;
7250 if (m_row != -1 || m_column != -1)
7251 m_state = Tracking;
7252 } else if (point.state() == QEventPoint::Released) {
7253 if (m_state == DraggingStarted || m_state == Dragging)
7254 m_state = DraggingFinished;
7255 else
7256 m_state = Listening;
7257 } else if (point.state() == QEventPoint::Updated) {
7258 switch (m_state) {
7259 case Listening:
7260 break;
7261 case Tracking: {
7262 const qreal distX = m_column != -1 ? point.position().x() - point.pressPosition().x() : 0;
7263 const qreal distY = m_row != -1 ? point.position().y() - point.pressPosition().y() : 0;
7264 const qreal dragDist = qSqrt(distX * distX + distY * distY);
7265 if (dragDist > qApp->styleHints()->startDragDistance())
7266 m_state = DraggingStarted;
7267 break;}
7268 case DraggingStarted:
7269 m_state = Dragging;
7270 break;
7271 case Dragging:
7272 break;
7273 case DraggingFinished:
7274 // Handled at the top of the function
7275 Q_UNREACHABLE();
7276 break;
7277 }
7278 }
7279}
7280
7281void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint &point)
7282{
7283 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7284#if QT_CONFIG(cursor)
7285 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7286#endif
7287
7288 switch (m_state) {
7289 case Listening:
7290 break;
7291 case Tracking:
7292 setPassiveGrab(event, point, true);
7293 // Disable flicking while dragging. TableView uses filtering instead of
7294 // pointer handlers to do flicking, so setting an exclusive grab (together
7295 // with grab permissions) doens't work ATM.
7296 tableView->setFiltersChildMouseEvents(false);
7297 tableViewPrivate->setActivePointerHandler(this);
7298 break;
7299 case DraggingStarted:
7300 setExclusiveGrab(event, point, true);
7301 m_columnStartX = point.position().x();
7302 m_columnStartWidth = tableView->columnWidth(m_column);
7303 m_rowStartY = point.position().y();
7304 m_rowStartHeight = tableView->rowHeight(m_row);
7305#if QT_CONFIG(cursor)
7306 tableViewPrivate->updateCursor();
7307#endif
7308 Q_FALLTHROUGH();
7309 case Dragging: {
7310 const qreal distX = point.position().x() - m_columnStartX;
7311 const qreal distY = point.position().y() - m_rowStartY;
7312 if (m_column != -1)
7313 tableView->setColumnWidth(m_column, qMax(0.001, m_columnStartWidth + distX));
7314 if (m_row != -1)
7315 tableView->setRowHeight(m_row, qMax(0.001, m_rowStartHeight + distY));
7316 break; }
7317 case DraggingFinished: {
7318 tableView->setFiltersChildMouseEvents(true);
7319 tableViewPrivate->setActivePointerHandler(nullptr);
7320#if QT_CONFIG(cursor)
7321 tableViewPrivate->updateCursor();
7322#endif
7323 break; }
7324 }
7325}
7326
7327// ----------------------------------------------
7328#if QT_CONFIG(quick_draganddrop)
7329
7330QQuickTableViewSectionDragHandler::QQuickTableViewSectionDragHandler(QQuickTableView *view)
7331 : QQuickTableViewPointerHandler(view)
7332{
7333 setObjectName("tableViewDragHandler");
7334}
7335
7336QQuickTableViewSectionDragHandler::~QQuickTableViewSectionDragHandler()
7337{
7338 resetDragData();
7339}
7340
7341void QQuickTableViewSectionDragHandler::resetDragData()
7342{
7343 if (m_state != Listening) {
7344 m_state = Listening;
7345 resetSectionOverlay();
7346 m_source = -1;
7347 m_destination = -1;
7348 if (m_grabResult.data())
7349 m_grabResult.data()->disconnect();
7350 if (!m_drag.isNull()) {
7351 m_drag->disconnect();
7352 delete m_drag;
7353 }
7354 if (!m_dropArea.isNull()) {
7355 m_dropArea->disconnect();
7356 delete m_dropArea;
7357 }
7358 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7359 tableView->setFiltersChildMouseEvents(true);
7360 }
7361}
7362
7363void QQuickTableViewSectionDragHandler::resetSectionOverlay()
7364{
7365 if (m_destination != -1) {
7366 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7367 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7368 const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : m_destination;
7369 const int column = (m_sectionOrientation == Qt::Horizontal) ? m_destination : 0;
7370 tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), false);
7371 m_destination = -1;
7372 }
7373}
7374
7375void QQuickTableViewSectionDragHandler::grabSection()
7376{
7377 // Generate the transparent section image in pixmap
7378 QPixmap pixmap(m_grabResult->image().size());
7379 pixmap.fill(Qt::transparent);
7380 QPainter painter(&pixmap);
7381 painter.setOpacity(0.6);
7382 painter.drawImage(0, 0, m_grabResult->image());
7383 painter.end();
7384
7385 // Specify the pixmap and mime data to be as drag object
7386 auto *mimeData = new QMimeData();
7387 mimeData->setImageData(pixmap);
7388 m_drag->setMimeData(mimeData);
7389 m_drag->setPixmap(pixmap);
7390}
7391
7392void QQuickTableViewSectionDragHandler::handleDrop(QQuickDragEvent *event)
7393{
7394 Q_UNUSED(event);
7395
7396 if (m_state == Dragging) {
7397 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7398 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7399 tableViewPrivate->moveSection(m_source, m_destination, m_sectionOrientation);
7400 m_state = DraggingFinished;
7401 resetSectionOverlay();
7402 if (m_scrollTimer.isActive())
7403 m_scrollTimer.stop();
7404 event->accept();
7405 }
7406}
7407
7408void QQuickTableViewSectionDragHandler::handleDrag(QQuickDragEvent *event)
7409{
7410 Q_UNUSED(event);
7411
7412 if (m_state == Dragging) {
7413 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7414 const QPoint dragItemPosition(tableView->contentX() + event->x(), tableView->contentY() + event->y());
7415 const auto *sourceItem = qobject_cast<QQuickItem *>(m_drag->source());
7416 const QPoint targetCell = tableView->cellAtPosition(dragItemPosition, true);
7417
7418 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7419 const int newDestination = (m_sectionOrientation == Qt::Horizontal) ? targetCell.x() : targetCell.y();
7420 if (newDestination != m_destination) {
7421 // Reset the overlay property in the existing model delegate item
7422 resetSectionOverlay();
7423 // Set the overlay property in the new model delegate item
7424 const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : newDestination;
7425 const int column = (m_sectionOrientation == Qt::Horizontal) ? newDestination : 0;
7426 tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), true);
7427 m_destination = newDestination;
7428 }
7429
7430 // Scroll header view while section item moves out of the table boundary
7431 const QPoint dragItemStartPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() - sourceItem->width() / 2, dragItemPosition.y()) :
7432 QPoint(dragItemPosition.x(), dragItemPosition.y() - sourceItem->height() / 2);
7433 const QPoint dragItemEndPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() + sourceItem->width() / 2, dragItemPosition.y()) :
7434 QPoint(dragItemPosition.x(), dragItemPosition.y() + sourceItem->height() / 2);
7435 const bool useStartPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemStartPos.x() <= tableView->contentX()) : (dragItemStartPos.y() <= tableView->contentY());
7436 const bool useEndPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemEndPos.x() >= tableView->width()) : (dragItemEndPos.y() >= tableView->height());
7437 if (useStartPos || useEndPos) {
7438 if (!m_scrollTimer.isActive()) {
7439 m_dragPoint = (m_sectionOrientation == Qt::Horizontal) ? QPoint(useStartPos ? dragItemStartPos.x() : dragItemEndPos.x(), 0) :
7440 QPoint(0, useStartPos ? dragItemStartPos.y() : dragItemEndPos.y());
7441 m_scrollTimer.start(1);
7442 }
7443 } else {
7444 if (m_scrollTimer.isActive())
7445 m_scrollTimer.stop();
7446 }
7447 }
7448}
7449
7450void QQuickTableViewSectionDragHandler::handleDragDropAction(Qt::DropAction action)
7451{
7452 // Reset the overlay property in the model delegate item when drag or drop
7453 // happens outside specified drop area (i.e. during ignore action)
7454 if (action == Qt::IgnoreAction) {
7455 resetSectionOverlay();
7456 if (m_scrollTimer.isActive())
7457 m_scrollTimer.stop();
7458 }
7459}
7460
7461void QQuickTableViewSectionDragHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
7462{
7463 QQuickSinglePointHandler::handleEventPoint(event, point);
7464
7465 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7466 auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7467 const auto *activeHandler = tableViewPrivate->activePointerHandler();
7468 if (activeHandler && !qobject_cast<const QQuickTableViewSectionDragHandler *>(activeHandler))
7469 return;
7470
7471 if (m_state == DraggingFinished) {
7472 if (m_scrollTimer.isActive())
7473 m_scrollTimer.stop();
7474 resetDragData();
7475 }
7476
7477 if (point.state() == QEventPoint::Pressed) {
7478 // Reset the information in the drag handler
7479 resetDragData();
7480 // Activate the passive grab to get further move updates
7481 setPassiveGrab(event, point, true);
7482 // Disable flicking while dragging. TableView uses filtering instead of
7483 // pointer handlers to do flicking, so setting an exclusive grab (together
7484 // with grab permissions) doens't work ATM.
7485 auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7486 tableView->setFiltersChildMouseEvents(false);
7487 m_state = Tracking;
7488 } else if (point.state() == QEventPoint::Released) {
7489 // Reset the information in the drag handler
7490 if (m_scrollTimer.isActive())
7491 m_scrollTimer.stop();
7492 resetDragData();
7493 } else if (point.state() == QEventPoint::Updated) {
7494 // Check to see that the movement can be considered as dragging
7495 const qreal distX = point.position().x() - point.pressPosition().x();
7496 const qreal distY = point.position().y() - point.pressPosition().y();
7497 const qreal dragDist = qSqrt(distX * distX + distY * distY);
7498 if (dragDist > qApp->styleHints()->startDragDistance()) {
7499 switch (m_state) {
7500 case Tracking: {
7501 // Grab the image for dragging header
7502 const QPoint cell = tableView->cellAtPosition(point.position(), true);
7503 auto *item = tableView->itemAtCell(cell);
7504 if (!item)
7505 break;
7506 if (m_drag.isNull()) {
7507 m_drag = new QDrag(item);
7508 connect(m_drag.data(), &QDrag::actionChanged, this,
7509 &QQuickTableViewSectionDragHandler::handleDragDropAction);
7510 }
7511 // Connect the timer for scroling
7512 QObject::connect(&m_scrollTimer, &QTimer::timeout, [&]{
7513 const QSizeF dist = tableViewPrivate->scrollTowardsPoint(m_dragPoint, m_step);
7514 m_dragPoint.rx() += dist.width() > 0 ? m_step.width() : -m_step.width();
7515 m_dragPoint.ry() += dist.height() > 0 ? m_step.height() : -m_step.height();
7516 m_step = QSizeF(qAbs(dist.width() * 0.010), qAbs(dist.height() * 0.010));
7517 });
7518 // Set the drop area
7519 if (m_dropArea.isNull()) {
7520 m_dropArea = new QQuickDropArea(tableView);
7521 m_dropArea->setSize(tableView->size());
7522 connect(m_dropArea, &QQuickDropArea::positionChanged, this,
7523 &QQuickTableViewSectionDragHandler::handleDrag);
7524 connect(m_dropArea, &QQuickDropArea::dropped, this,
7525 &QQuickTableViewSectionDragHandler::handleDrop);
7526 }
7527 // Grab the image of the section
7528 m_grabResult = item->grabToImage();
7529 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this,
7530 &QQuickTableViewSectionDragHandler::grabSection);
7531 // Update source depending on the type of orientation
7532 m_source = (m_sectionOrientation == Qt::Horizontal) ? cell.x() : cell.y();
7533 m_state = DraggingStarted;
7534 // Set drag handler as active and it further handles section pointer events
7535 tableViewPrivate->setActivePointerHandler(this);
7536 }
7537 break;
7538
7539 case DraggingStarted: {
7540 if (m_drag && m_drag->mimeData()) {
7541 if (auto *item = qobject_cast<QQuickItem *>(m_drag->source())) {
7542 m_state = Dragging;
7543 const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position());
7544 Q_UNUSED(itemPos);
7545 m_drag->setHotSpot(m_sectionOrientation == Qt::Horizontal ? QPoint(item->width()/2, itemPos.y()) : QPoint(itemPos.x(), item->height()/2));
7546 m_drag->exec();
7547 // If the state still remains dragging, means the drop happened outside the corresponding section handler's
7548 // drop area, better clear all the state.
7549 if (m_state == Dragging)
7550 resetDragData();
7551 // Reset the active handler
7552 tableViewPrivate->setActivePointerHandler(nullptr);
7553 }
7554 }
7555 }
7556 break;
7557
7558 default:
7559 break;
7560 }
7561 }
7562 }
7563}
7564
7565// ----------------------------------------------
7566void QQuickTableViewPrivate::initSectionDragHandler(Qt::Orientation orientation)
7567{
7568 if (!sectionDragHandler) {
7569 Q_Q(QQuickTableView);
7570 sectionDragHandler = new QQuickTableViewSectionDragHandler(q);
7571 sectionDragHandler->setSectionOrientation(orientation);
7572 }
7573}
7574
7575void QQuickTableViewPrivate::destroySectionDragHandler()
7576{
7577 if (sectionDragHandler) {
7578 delete sectionDragHandler;
7579 sectionDragHandler = nullptr;
7580 }
7581}
7582#endif // quick_draganddrop
7583
7584void QQuickTableViewPrivate::initializeIndexMapping()
7585{
7586 auto initIndices = [](auto& visualIndex, auto& logicalIndex, int size) {
7587 visualIndex.resize(size);
7588 logicalIndex.resize(size);
7589 for (int index = 0; index < size; ++index)
7590 visualIndex[index].index = logicalIndex[index].index = index;
7591 };
7592
7593 if (visualIndices[0].size() != tableSize.width()
7594 || logicalIndices[0].size() != tableSize.width())
7595 initIndices(visualIndices[0], logicalIndices[0], tableSize.width());
7596
7597 if (visualIndices[1].size() != tableSize.height()
7598 || logicalIndices[1].size() != tableSize.height())
7599 initIndices(visualIndices[1], logicalIndices[1], tableSize.height());
7600}
7601
7602void QQuickTableViewPrivate::clearIndexMapping()
7603{
7604 logicalIndices[0].clear();
7605 visualIndices[0].clear();
7606
7607 logicalIndices[1].clear();
7608 visualIndices[1].clear();
7609}
7610
7611int QQuickTableViewPrivate::logicalRowIndex(const int visualIndex) const
7612{
7613 if (syncView)
7614 return syncView->d_func()->logicalRowIndex(visualIndex);
7615 if (logicalIndices[1].isEmpty() || visualIndex < 0)
7616 return visualIndex;
7617 return logicalIndices[1].constData()[visualIndex].index;
7618}
7619
7620int QQuickTableViewPrivate::logicalColumnIndex(const int visualIndex) const
7621{
7622 if (syncView)
7623 return syncView->d_func()->logicalColumnIndex(visualIndex);
7624 if (logicalIndices[0].isEmpty() || visualIndex < 0)
7625 return visualIndex;
7626 return logicalIndices[0].constData()[visualIndex].index;
7627}
7628
7629int QQuickTableViewPrivate::visualRowIndex(const int logicalIndex) const
7630{
7631 if (syncView)
7632 return syncView->d_func()->visualRowIndex(logicalIndex);
7633 if (visualIndices[1].isEmpty() || logicalIndex < 0)
7634 return logicalIndex;
7635 return visualIndices[1].constData()[logicalIndex].index;
7636}
7637
7638int QQuickTableViewPrivate::visualColumnIndex(const int logicalIndex) const
7639{
7640 if (syncView)
7641 return syncView->d_func()->visualColumnIndex(logicalIndex);
7642 if (visualIndices[0].isEmpty() || logicalIndex < 0)
7643 return logicalIndex;
7644 return visualIndices[0].constData()[logicalIndex].index;
7645}
7646
7647int QQuickTableViewPrivate::getEditCellIndex(const QModelIndex &index) const
7648{
7649 // With subclasses that use a proxy model (e.g. TreeView),
7650 // always edit the cell at visual index.
7651 const bool hasProxyModel = (modelImpl() != assignedModel);
7652 return modelIndexToCellIndex(index, hasProxyModel);
7653}
7654
7655// ----------------------------------------------
7656
7657QQuickTableViewTapHandler::QQuickTableViewTapHandler(QQuickTableView *view)
7658 : QQuickTapHandler(view->contentItem())
7659{
7660 setObjectName("tableViewTapHandler");
7661}
7662
7663bool QQuickTableViewTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
7664{
7665 auto tableView = static_cast<QQuickTableView *>(parentItem()->parent());
7666 auto tableViewPrivate = QQuickTableViewPrivate::get(tableView);
7667 return tableViewPrivate->pointerNavigationEnabled && QQuickTapHandler::wantsEventPoint(event, point);
7668}
7669
7670QT_END_NAMESPACE
7671
7672#include "moc_qquicktableview_p.cpp"
7673#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