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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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