1// Copyright (C) 2021 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
4#include "qquicktreeview_p_p.h"
5
6#include <QtCore/qobject.h>
7#include <QtQml/qqmlcontext.h>
8#include <QtQuick/private/qquicktaphandler_p.h>
9
10#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>
11
12/*!
13 \qmltype TreeView
14 \inqmlmodule QtQuick
15 \ingroup qtquick-views
16 \since 6.3
17 \inherits TableView
18 \brief Provides a tree view to display data from a QAbstractItemModel.
19
20 A TreeView has a \l model that defines the data to be displayed, and a
21 \l delegate that defines how the data should be displayed.
22
23 TreeView inherits \l TableView. This means that even if the model
24 has a parent-child tree structure, TreeView is internally using a
25 proxy model that converts that structure into a flat table
26 model that can be rendered by TableView. Each node in the tree ends up
27 occupying one row in the table, where the first column renders the tree
28 itself. By indenting each delegate item in that column according to its
29 parent-child depth in the model, it will end up looking like a tree, even
30 if it's technically still just a flat list of items.
31
32 To allow for maximum flexibility, TreeView itself will not position the delegate
33 items into a tree structure. This burden is placed on the delegate.
34 \l {Qt Quick Controls} offers a ready-made TreeViewDelegate that can be
35 used for this, which has the advantage that it works out-of-the-box and
36 renders a tree which follows the style of the platform where the application runs.
37
38 Even if TreeViewDelegate is customizable, there might be situations
39 where you want to render the tree in a different way, or ensure that
40 the delegate ends up as minimal as possible, perhaps for performance reasons.
41 Creating your own delegate from scratch is easy, since TreeView offers
42 a set of properties that can be used to position and render each node
43 in the tree correctly.
44
45 An example of a custom delegate is shown below:
46
47 \snippet qml/treeview/qml-customdelegate.qml 0
48
49 The properties that are marked as \c required will be filled in by
50 TreeView, and are similar to attached properties. By marking them as
51 required, the delegate indirectly informs TreeView that it should take
52 responsibility for assigning them values. The following required properties
53 can be added to a delegate:
54
55 \list
56 \li \c {required property TreeView treeView}
57 - Points to the TreeView that contains the delegate item.
58 \li \c {required property bool isTreeNode}
59 - Is \c true if the delegate item represents a node in
60 the tree. Only one column in the view will be used to draw the tree, and
61 therefore, only delegate items in that column will have this
62 property set to \c true.
63 A node in the tree should typically be indented according to its
64 \c depth, and show an indicator if \c hasChildren is \c true.
65 Delegate items in other columns will have this property set to
66 \c false, and will show data from the remaining columns
67 in the model (and typically not be indented).
68 \li \c {required property bool expanded}
69 - Is \c true if the model item drawn by the delegate is expanded
70 in the view.
71 \li \c {required property bool hasChildren}
72 - Is \c true if the model item drawn by the delegate has children
73 in the model.
74 \li \c {required property int depth}
75 - Contains the depth of the model item drawn by the delegate.
76 The depth of a model item is the same as the number of ancestors
77 it has in the model.
78 \endlist
79
80 See also \l {Required Properties}.
81
82 By default, TreeView \l {toggleExpanded()}{toggles} the expanded state
83 of a row when you double tap on it. Since this is in conflict with
84 double tapping to edit a cell, TreeView sets \l {TableView::}{editTriggers} to
85 \c TableView.EditKeyPressed by default (which is different from TableView,
86 which uses \c {TableView.EditKeyPressed | TableView.DoubleTapped}.
87 If you change \l {TableView::}{editTriggers} to also contain \c TableView.DoubleTapped,
88 toggling the expanded state with a double tap will be disabled.
89
90 \note A TreeView only accepts a model that inherits \l QAbstractItemModel.
91*/
92
93/*!
94 \qmlproperty QModelIndex QtQuick::TreeView::rootIndex
95 \since 6.6
96
97 This property holds the model index of the root item in the tree.
98 By default, this is the same as the root index in the model, but you can
99 set it to be a child index instead, to show only a branch of the tree.
100 Set it to \c undefined to show the whole model.
101*/
102
103/*!
104 \qmlmethod int QtQuick::TreeView::depth(row)
105
106 Returns the depth (the number of parents up to the root) of the given \a row.
107
108 \a row should be the row in the view (table row), and not a row in the model.
109 If \a row is not between \c 0 and \l {TableView::}{rows}, the return value will
110 be \c -1.
111
112 \sa {TableView::}{modelIndex()}
113*/
114
115/*!
116 \qmlmethod bool QtQuick::TreeView::isExpanded(row)
117
118 Returns if the given \a row in the view is shown as expanded.
119
120 \a row should be the row in the view (table row), and not a row in the model.
121 If \a row is not between \c 0 and \l {TableView::}{rows}, the return value will
122 be \c false.
123*/
124
125/*!
126 \qmlmethod QtQuick::TreeView::expand(row)
127
128 Expands the tree node at the given \a row in the view.
129
130 \a row should be the row in the view (table row), and not a row in the model.
131
132 \note this function will not affect the model, only
133 the visual representation in the view.
134
135 \sa collapse(), isExpanded(), expandRecursively()
136*/
137
138/*!
139 \qmlmethod QtQuick::TreeView::expandRecursively(row = -1, depth = -1)
140 \since 6.4
141
142 Expands the tree node at the given \a row in the view recursively down to
143 \a depth. \a depth should be relative to the depth of \a row. If
144 \a depth is \c -1, the tree will be expanded all the way down to all leaves.
145
146 For a model that has more than one root, you can also call this function
147 with \a row equal to \c -1. This will expand all roots. Hence, calling
148 expandRecursively(-1, -1), or simply expandRecursively(), will expand
149 all nodes in the model.
150
151 \a row should be the row in the view (table row), and not a row in the model.
152
153 \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more} data.
154 \note This function will not affect the model, only the visual representation in the view.
155 \warning If the model contains a large number of items, this function will
156 take some time to execute.
157
158 \sa collapseRecursively(), expand(), collapse(), isExpanded(), depth()
159*/
160
161/*!
162 \qmlmethod QtQuick::TreeView::expandToIndex(QModelIndex index)
163 \since 6.4
164
165 Expands the tree from the given model \a index, and recursively all the way up
166 to the root. The result will be that the delegate item that represents \a index
167 becomes visible in the view (unless it ends up outside the viewport). To
168 ensure that the row ends up visible in the viewport, you can do:
169
170 \code
171 expandToIndex(index)
172 forceLayout()
173 positionViewAtRow(rowAtIndex(index), Qt.AlignVCenter)
174 \endcode
175
176 \sa expand(), expandRecursively()
177*/
178
179/*!
180 \qmlmethod QtQuick::TreeView::collapse(row)
181
182 Collapses the tree node at the given \a row in the view.
183
184 \a row should be the row in the view (table row), and not a row in the model.
185
186 \note this function will not affect the model, only
187 the visual representation in the view.
188
189 \sa expand(), isExpanded()
190*/
191
192/*!
193 \qmlmethod QtQuick::TreeView::collapseRecursively(row = -1)
194 \since 6.4
195
196 Collapses the tree node at the given \a row in the view recursively down to
197 all leaves.
198
199 For a model has more than one root, you can also call this function
200 with \a row equal to \c -1. This will collapse all roots. Hence, calling
201 collapseRecursively(-1), or simply collapseRecursively(), will collapse
202 all nodes in the model.
203
204 \a row should be the row in the view (table row), and not a row in the model.
205
206 \note this function will not affect the model, only
207 the visual representation in the view.
208
209 \sa expandRecursively(), expand(), collapse(), isExpanded(), depth()
210*/
211
212/*!
213 \qmlmethod QtQuick::TreeView::toggleExpanded(row)
214
215 Toggles if the tree node at the given \a row should be expanded.
216 This is a convenience for doing:
217
218 \code
219 if (isExpanded(row))
220 collapse(row)
221 else
222 expand(row)
223 \endcode
224
225 \a row should be the row in the view (table row), and not a row in the model.
226*/
227
228/*!
229 \qmlsignal QtQuick::TreeView::expanded(row, depth)
230
231 This signal is emitted when a \a row is expanded in the view.
232 \a row and \a depth will be equal to the arguments given to the call
233 that caused the expansion to happen (\l expand() or \l expandRecursively()).
234 In case of \l expand(), \a depth will always be \c 1.
235 In case of \l expandToIndex(), \a depth will be the depth of the
236 target index.
237
238 \note when a row is expanded recursively, the expanded signal will
239 only be emitted for that one row, and not for its descendants.
240
241 \sa collapsed(), expand(), collapse(), toggleExpanded()
242*/
243
244/*!
245 \qmlsignal QtQuick::TreeView::collapsed(row, recursively)
246
247 This signal is emitted when a \a row is collapsed in the view.
248 \a row will be equal to the argument given to the call that caused
249 the collapse to happen (\l collapse() or \l collapseRecursively()).
250 If the row was collapsed recursively, \a recursively will be \c true.
251
252 \note when a row is collapsed recursively, the collapsed signal will
253 only be emitted for that one row, and not for its descendants.
254
255 \sa expanded(), expand(), collapse(), toggleExpanded()
256*/
257
258// Hard-code the tree column to be 0 for now
259static const int kTreeColumn = 0;
260
261QT_BEGIN_NAMESPACE
262
263QQuickTreeViewPrivate::QQuickTreeViewPrivate()
264 : QQuickTableViewPrivate()
265{
266}
267
268QQuickTreeViewPrivate::~QQuickTreeViewPrivate()
269{
270}
271
272QVariant QQuickTreeViewPrivate::modelImpl() const
273{
274 return m_assignedModel;
275}
276
277void QQuickTreeViewPrivate::setModelImpl(const QVariant &newModel)
278{
279 Q_Q(QQuickTreeView);
280
281 if (newModel == m_assignedModel)
282 return;
283
284 m_assignedModel = newModel;
285 QVariant effectiveModel = m_assignedModel;
286 if (effectiveModel.userType() == qMetaTypeId<QJSValue>())
287 effectiveModel = effectiveModel.value<QJSValue>().toVariant();
288
289 if (effectiveModel.isNull())
290 m_treeModelToTableModel.setModel(nullptr);
291 else if (const auto qaim = qvariant_cast<QAbstractItemModel*>(v: effectiveModel))
292 m_treeModelToTableModel.setModel(qaim);
293 else
294 qmlWarning(me: q) << "TreeView only accepts a model of type QAbstractItemModel";
295
296
297 scheduleRebuildTable(options: QQuickTableViewPrivate::RebuildOption::All);
298 emit q->modelChanged();
299}
300
301void QQuickTreeViewPrivate::initItemCallback(int serializedModelIndex, QObject *object)
302{
303 updateRequiredProperties(serializedModelIndex, object, init: true);
304 QQuickTableViewPrivate::initItemCallback(modelIndex: serializedModelIndex, item: object);
305}
306
307void QQuickTreeViewPrivate::itemReusedCallback(int serializedModelIndex, QObject *object)
308{
309 updateRequiredProperties(serializedModelIndex, object, init: false);
310 QQuickTableViewPrivate::itemReusedCallback(modelIndex: serializedModelIndex, object);
311}
312
313void QQuickTreeViewPrivate::dataChangedCallback(
314 const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
315{
316 Q_Q(QQuickTreeView);
317 Q_UNUSED(roles);
318
319 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
320 for (int column = topLeft.column(); column <= bottomRight.column(); ++column) {
321 const QPoint cell(column, row);
322 auto item = q->itemAtCell(cell);
323 if (!item)
324 continue;
325
326 const int serializedModelIndex = modelIndexAtCell(cell: QPoint(column, row));
327 updateRequiredProperties(serializedModelIndex, object: item, init: false);
328 }
329 }
330}
331
332void QQuickTreeViewPrivate::updateRequiredProperties(int serializedModelIndex, QObject *object, bool init)
333{
334 Q_Q(QQuickTreeView);
335 const QPoint cell = cellAtModelIndex(modelIndex: serializedModelIndex);
336 const int row = cell.y();
337 const int column = cell.x();
338
339 setRequiredProperty(property: "treeView", value: QVariant::fromValue(value: q), serializedModelIndex, object, init);
340 setRequiredProperty(property: "isTreeNode", value: column == kTreeColumn, serializedModelIndex, object, init);
341 setRequiredProperty(property: "hasChildren", value: m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init);
342 setRequiredProperty(property: "expanded", value: q->isExpanded(row), serializedModelIndex, object, init);
343 setRequiredProperty(property: "depth", value: m_treeModelToTableModel.depthAtRow(row), serializedModelIndex, object, init);
344}
345
346void QQuickTreeViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection)
347{
348 Q_Q(QQuickTreeView);
349
350 const QRect oldRect = oldSelection.normalized();
351 const QRect newRect = newSelection.normalized();
352
353 if (oldSelection == newSelection)
354 return;
355
356 // Select the rows inside newRect that doesn't overlap with oldRect
357 for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row) {
358 if (oldRect.y() != -1 && oldRect.y() <= row && row <= oldRect.y() + oldRect.height())
359 continue;
360 const QModelIndex startIndex = q->index(row, column: newRect.x());
361 const QModelIndex endIndex = q->index(row, column: newRect.x() + newRect.width());
362 selectionModel->select(selection: QItemSelection(startIndex, endIndex), command: QItemSelectionModel::Select);
363 }
364
365 if (oldRect.x() != -1) {
366 // Since oldRect is valid, this update is a continuation of an already existing selection!
367
368 // Select the columns inside newRect that don't overlap with oldRect
369 for (int column = newRect.x(); column <= newRect.x() + newRect.width(); ++column) {
370 if (oldRect.x() <= column && column <= oldRect.x() + oldRect.width())
371 continue;
372 for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row)
373 selectionModel->select(index: q->index(row, column), command: QItemSelectionModel::Select);
374 }
375
376 // Unselect the rows inside oldRect that don't overlap with newRect
377 for (int row = oldRect.y(); row <= oldRect.y() + oldRect.height(); ++row) {
378 if (newRect.y() <= row && row <= newRect.y() + newRect.height())
379 continue;
380 const QModelIndex startIndex = q->index(row, column: oldRect.x());
381 const QModelIndex endIndex = q->index(row, column: oldRect.x() + oldRect.width());
382 selectionModel->select(selection: QItemSelection(startIndex, endIndex), command: QItemSelectionModel::Deselect);
383 }
384
385 // Unselect the columns inside oldRect that don't overlap with newRect
386 for (int column = oldRect.x(); column <= oldRect.x() + oldRect.width(); ++column) {
387 if (newRect.x() <= column && column <= newRect.x() + newRect.width())
388 continue;
389 // Since we're not allowed to call select/unselect on the selectionModel with
390 // indices from different parents, and since indicies from different parents are
391 // expected when working with trees, we need to unselect the indices in the column
392 // one by one, rather than the whole column in one go. This, however, can cause a
393 // lot of selection fragments in the selectionModel, which eventually can hurt
394 // performance. But large selections containing a lot of columns is not normally
395 // the case for a treeview, so accept this potential corner case for now.
396 for (int row = newRect.y(); row <= newRect.y() + newRect.height(); ++row)
397 selectionModel->select(index: q->index(row, column), command: QItemSelectionModel::Deselect);
398 }
399 }
400}
401
402QQuickTreeView::QQuickTreeView(QQuickItem *parent)
403 : QQuickTableView(*(new QQuickTreeViewPrivate), parent)
404{
405 Q_D(QQuickTreeView);
406
407 setSelectionBehavior(SelectRows);
408 setEditTriggers(EditKeyPressed);
409
410 // Note: QQuickTableView will only ever see the table model m_treeModelToTableModel, and
411 // never the actual tree model that is assigned to us by the application.
412 const auto modelAsVariant = QVariant::fromValue(value: std::addressof(r&: d->m_treeModelToTableModel));
413 d->QQuickTableViewPrivate::setModelImpl(modelAsVariant);
414 QObjectPrivate::connect(sender: &d->m_treeModelToTableModel, signal: &QAbstractItemModel::dataChanged,
415 receiverPrivate: d, slot: &QQuickTreeViewPrivate::dataChangedCallback);
416 QObject::connect(sender: &d->m_treeModelToTableModel, signal: &QQmlTreeModelToTableModel::rootIndexChanged,
417 context: this, slot: &QQuickTreeView::rootIndexChanged);
418
419 auto tapHandler = new QQuickTapHandler(this);
420 tapHandler->setAcceptedModifiers(Qt::NoModifier);
421 connect(sender: tapHandler, signal: &QQuickTapHandler::doubleTapped, slot: [this, tapHandler]{
422 if (!pointerNavigationEnabled())
423 return;
424 if (editTriggers() & DoubleTapped)
425 return;
426
427 const int row = cellAtPosition(position: tapHandler->point().pressPosition()).y();
428 toggleExpanded(row);
429 });
430}
431
432QQuickTreeView::~QQuickTreeView()
433{
434}
435
436QModelIndex QQuickTreeView::rootIndex() const
437{
438 return d_func()->m_treeModelToTableModel.rootIndex();
439}
440
441void QQuickTreeView::setRootIndex(const QModelIndex &index)
442{
443 Q_D(QQuickTreeView);
444 d->m_treeModelToTableModel.setRootIndex(index);
445 positionViewAtCell(cell: {0, 0}, mode: QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
446}
447
448void QQuickTreeView::resetRootIndex()
449{
450 Q_D(QQuickTreeView);
451 d->m_treeModelToTableModel.resetRootIndex();
452 positionViewAtCell(cell: {0, 0}, mode: QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
453}
454
455int QQuickTreeView::depth(int row) const
456{
457 Q_D(const QQuickTreeView);
458 if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
459 return -1;
460
461 return d->m_treeModelToTableModel.depthAtRow(row);
462}
463
464bool QQuickTreeView::isExpanded(int row) const
465{
466 Q_D(const QQuickTreeView);
467 if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
468 return false;
469
470 return d->m_treeModelToTableModel.isExpanded(row);
471}
472
473void QQuickTreeView::expand(int row)
474{
475 if (row >= 0)
476 expandRecursively(row, depth: 1);
477}
478
479void QQuickTreeView::expandRecursively(int row, int depth)
480{
481 Q_D(QQuickTreeView);
482 if (row >= d->m_treeModelToTableModel.rowCount())
483 return;
484 if (row < 0 && row != -1)
485 return;
486 if (depth == 0 || depth < -1)
487 return;
488
489 auto expandRowRecursively = [this, d, depth](int startRow) {
490 d->m_treeModelToTableModel.expandRecursively(row: startRow, depth);
491 // Update the expanded state of the startRow. The descendant rows that gets
492 // expanded will get the correct state set from initItem/itemReused instead.
493 for (int c = leftColumn(); c <= rightColumn(); ++c) {
494 const QPoint treeNodeCell(c, startRow);
495 if (const auto item = itemAtCell(cell: treeNodeCell))
496 d->setRequiredProperty(property: "expanded", value: true, serializedModelIndex: d->modelIndexAtCell(cell: treeNodeCell), object: item, init: false);
497 }
498 };
499
500 if (row >= 0) {
501 // Expand only one row recursively
502 const bool isExpanded = d->m_treeModelToTableModel.isExpanded(row);
503 if (isExpanded && depth == 1)
504 return;
505 expandRowRecursively(row);
506 } else {
507 // Expand all root nodes recursively
508 const auto model = d->m_treeModelToTableModel.model();
509 for (int r = 0; r < model->rowCount(); ++r) {
510 const int rootRow = d->m_treeModelToTableModel.itemIndex(index: model->index(row: r, column: 0));
511 if (rootRow != -1)
512 expandRowRecursively(rootRow);
513 }
514 }
515
516 emit expanded(row, depth);
517}
518
519void QQuickTreeView::expandToIndex(const QModelIndex &index)
520{
521 Q_D(QQuickTreeView);
522
523 if (!index.isValid()) {
524 qmlWarning(me: this) << "index is not valid: " << index;
525 return;
526 }
527
528 if (index.model() != d->m_treeModelToTableModel.model()) {
529 qmlWarning(me: this) << "index doesn't belong to correct model: " << index;
530 return;
531 }
532
533 if (rowAtIndex(index) != -1) {
534 // index is already visible
535 return;
536 }
537
538 int depth = 1;
539 QModelIndex parent = index.parent();
540 int row = rowAtIndex(index: parent);
541
542 while (parent.isValid()) {
543 if (row != -1) {
544 // The node is already visible, since it maps to a row in the table!
545 d->m_treeModelToTableModel.expandRow(n: row);
546
547 // Update the state of the already existing delegate item
548 for (int c = leftColumn(); c <= rightColumn(); ++c) {
549 const QPoint treeNodeCell(c, row);
550 if (const auto item = itemAtCell(cell: treeNodeCell))
551 d->setRequiredProperty(property: "expanded", value: true, serializedModelIndex: d->modelIndexAtCell(cell: treeNodeCell), object: item, init: false);
552 }
553
554 // When we hit a node that is visible, we know that all other nodes
555 // up to the parent have to be visible as well, so we can stop.
556 break;
557 } else {
558 d->m_treeModelToTableModel.expand(parent);
559 parent = parent.parent();
560 row = rowAtIndex(index: parent);
561 depth++;
562 }
563 }
564
565 emit expanded(row, depth);
566}
567
568void QQuickTreeView::collapse(int row)
569{
570 Q_D(QQuickTreeView);
571 if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
572 return;
573
574 if (!d->m_treeModelToTableModel.isExpanded(row))
575 return;
576
577 d_func()->m_treeModelToTableModel.collapseRow(n: row);
578
579 for (int c = leftColumn(); c <= rightColumn(); ++c) {
580 const QPoint treeNodeCell(c, row);
581 if (const auto item = itemAtCell(cell: treeNodeCell))
582 d->setRequiredProperty(property: "expanded", value: false, serializedModelIndex: d->modelIndexAtCell(cell: treeNodeCell), object: item, init: false);
583 }
584
585 emit collapsed(row, recursively: false);
586}
587
588void QQuickTreeView::collapseRecursively(int row)
589{
590 Q_D(QQuickTreeView);
591 if (row >= d->m_treeModelToTableModel.rowCount())
592 return;
593 if (row < 0 && row != -1)
594 return;
595
596 auto collapseRowRecursive = [this, d](int startRow) {
597 // Always collapse descendants recursively,
598 // even if the top row itself is already collapsed.
599 d->m_treeModelToTableModel.collapseRecursively(row: startRow);
600 // Update the expanded state of the (still visible) startRow
601 for (int c = leftColumn(); c <= rightColumn(); ++c) {
602 const QPoint treeNodeCell(c, startRow);
603 if (const auto item = itemAtCell(cell: treeNodeCell))
604 d->setRequiredProperty(property: "expanded", value: false, serializedModelIndex: d->modelIndexAtCell(cell: treeNodeCell), object: item, init: false);
605 }
606 };
607
608 if (row >= 0) {
609 collapseRowRecursive(row);
610 } else {
611 // Collapse all root nodes recursively
612 const auto model = d->m_treeModelToTableModel.model();
613 for (int r = 0; r < model->rowCount(); ++r) {
614 const int rootRow = d->m_treeModelToTableModel.itemIndex(index: model->index(row: r, column: 0));
615 if (rootRow != -1)
616 collapseRowRecursive(rootRow);
617 }
618 }
619
620 emit collapsed(row, recursively: true);
621}
622
623void QQuickTreeView::toggleExpanded(int row)
624{
625 if (isExpanded(row))
626 collapse(row);
627 else
628 expand(row);
629}
630
631QModelIndex QQuickTreeView::modelIndex(const QPoint &cell) const
632{
633 Q_D(const QQuickTreeView);
634 const QModelIndex tableIndex = d->m_treeModelToTableModel.index(row: cell.y(), column: cell.x());
635 return d->m_treeModelToTableModel.mapToModel(index: tableIndex);
636}
637
638QPoint QQuickTreeView::cellAtIndex(const QModelIndex &index) const
639{
640 const QModelIndex tableIndex = d_func()->m_treeModelToTableModel.mapFromModel(index);
641 return QPoint(tableIndex.column(), tableIndex.row());
642}
643
644#if QT_DEPRECATED_SINCE(6, 4)
645QModelIndex QQuickTreeView::modelIndex(int row, int column) const
646{
647 static const bool compat6_4 = qEnvironmentVariable(varName: "QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
648 if (compat6_4) {
649 // XXX Qt 7: Remove this compatibility path here and in QQuickTableView.
650 // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
651 // being documented to be specified in the opposite order.
652 // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
653 // to continue accepting calls to modelIndex(column, row).
654 return modelIndex(cell: {row, column});
655 } else {
656 qmlWarning(me: this) << "modelIndex(row, column) is deprecated. "
657 "Use index(row, column) instead. For more information, see "
658 "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
659 return modelIndex(cell: {column, row});
660 }
661}
662#endif
663
664void QQuickTreeView::keyPressEvent(QKeyEvent *event)
665{
666 event->ignore();
667
668 if (!keyNavigationEnabled())
669 return;
670 if (!selectionModel())
671 return;
672
673 const int row = cellAtIndex(index: selectionModel()->currentIndex()).y();
674 switch (event->key()) {
675 case Qt::Key_Left:
676 collapse(row);
677 event->accept();
678 break;
679 case Qt::Key_Right:
680 expand(row);
681 event->accept();
682 break;
683 default:
684 break;
685 }
686
687 if (!event->isAccepted())
688 QQuickTableView::keyPressEvent(e: event);
689}
690
691QT_END_NAMESPACE
692
693#include "moc_qquicktreeview_p.cpp"
694

source code of qtdeclarative/src/quick/items/qquicktreeview.cpp