aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquicktreeview.cpp
blob: 033884d58d9408f2b79a372fdea52a909db06bf2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qquicktreeview_p_p.h"

#include <QtCore/qobject.h>
#include <QtQml/qqmlcontext.h>
#include <QtQuick/private/qquicktaphandler_p.h>

#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>

/*!
    \qmltype TreeView
    \inqmlmodule QtQuick
    \ingroup qtquick-views
    \since 6.3
    \inherits TableView
    \brief Provides a tree view to display data from a QAbstractItemModel.

    A TreeView has a \l model that defines the data to be displayed, and a
    \l delegate that defines how the data should be displayed.

    TreeView inherits \l TableView. This means that even if the model
    has a parent-child tree structure, TreeView is internally using a
    proxy model that converts that structure into a flat table
    model that can be rendered by TableView. Each node in the tree ends up
    occupying one row in the table, where the first column renders the tree
    itself. By indenting each delegate item in that column according to its
    parent-child depth in the model, it will end up looking like a tree, even
    if it's technically still just a flat list of items.

    To allow for maximum flexibility, TreeView itself will not position the delegate
    items into a tree structure. This burden is placed on the delegate.
    \l {Qt Quick Controls} offers a ready-made TreeViewDelegate that can be
    used for this, which has the advantage that it works out-of-the-box and
    renders a tree which follows the style of the platform where the application runs.

    Even if TreeViewDelegate is customizable, there might be situations
    where you want to render the tree in a different way, or ensure that
    the delegate ends up as minimal as possible, perhaps for performance reasons.
    Creating your own delegate from scratch is easy, since TreeView offers
    a set of properties that can be used to position and render each node
    in the tree correctly.

    An example of a custom delegate with an animating indicator is shown below:

    \snippet qml/treeview/qml-customdelegate.qml 0

    The properties that are marked as \c required will be filled in by
    TreeView, and are similar to attached properties. By marking them as
    required, the delegate indirectly informs TreeView that it should take
    responsibility for assigning them values. The following required properties
    can be added to a delegate:

    \list
        \li \c {required property TreeView treeView}
                - Points to the TreeView that contains the delegate item.
        \li \c {required property bool isTreeNode}
                - Is \c true if the delegate item represents a node in
                the tree. Only one column in the view will be used to draw the tree, and
                therefore, only delegate items in that column will have this
                property set to \c true.
                A node in the tree should typically be indented according to its
                \c depth, and show an indicator if \c hasChildren is \c true.
                Delegate items in other columns will have this property set to
                \c false, and will show data from the remaining columns
                in the model (and typically not be indented).
        \li \c {required property bool expanded}
                - Is \c true if the model item drawn by the delegate is expanded
                in the view.
        \li \c {required property bool hasChildren}
                - Is \c true if the model item drawn by the delegate has children
                in the model.
        \li \c {required property int depth}
                - Contains the depth of the model item drawn by the delegate.
                The depth of a model item is the same as the number of ancestors
                it has in the model.
    \endlist

    See also \l {Required Properties}.

    By default, TreeView \l {toggleExpanded()}{toggles} the expanded state
    of a row when you double tap on it. Since this is in conflict with
    double tapping to edit a cell, TreeView sets \l {TableView::}{editTriggers} to
    \c TableView.EditKeyPressed by default (which is different from TableView,
    which uses \c {TableView.EditKeyPressed | TableView.DoubleTapped}.
    If you change \l {TableView::}{editTriggers} to also contain \c TableView.DoubleTapped,
    toggling the expanded state with a double tap will be disabled.

    \note A TreeView only accepts a model that inherits \l QAbstractItemModel.
*/

/*!
    \qmlproperty QModelIndex QtQuick::TreeView::rootIndex
    \since 6.6

    This property holds the model index of the root item in the tree.
    By default, this is the same as the root index in the model, but you can
    set it to be a child index instead, to show only a branch of the tree.
    Set it to \c undefined to show the whole model.
*/

/*!
    \qmlmethod int QtQuick::TreeView::depth(row)

    Returns the depth (the number of parents up to the root) of the given \a row.

    \a row should be the row in the view (table row), and not a row in the model.
    If \a row is not between \c 0 and \l {TableView::}{rows}, the return value will
    be \c -1.

    \sa {TableView::}{modelIndex()}
*/

/*!
    \qmlmethod bool QtQuick::TreeView::isExpanded(row)

    Returns if the given \a row in the view is shown as expanded.

    \a row should be the row in the view (table row), and not a row in the model.
    If \a row is not between \c 0 and \l {TableView::}{rows}, the return value will
    be \c false.
*/

/*!
    \qmlmethod QtQuick::TreeView::expand(row)

    Expands the tree node at the given \a row in the view.

    \a row should be the row in the view (table row), and not a row in the model.

    \note this function will not affect the model, only
    the visual representation in the view.

    \sa collapse(), isExpanded(), expandRecursively()
*/

/*!
    \qmlmethod QtQuick::TreeView::expandRecursively(row = -1, depth = -1)
    \since 6.4

    Expands the tree node at the given \a row in the view recursively down to
    \a depth. \a depth should be relative to the depth of \a row. If
    \a depth is \c -1, the tree will be expanded all the way down to all leaves.

    For a model that has more than one root, you can also call this function
    with \a row equal to \c -1. This will expand all roots. Hence, calling
    expandRecursively(-1, -1), or simply expandRecursively(), will expand
    all nodes in the model.

    \a row should be the row in the view (table row), and not a row in the model.

    \note This function will not try to \l{QAbstractItemModel::fetchMore}{fetch more} data.
    \note This function will not affect the model, only the visual representation in the view.
    \warning If the model contains a large number of items, this function will
    take some time to execute.

    \sa collapseRecursively(), expand(), collapse(), isExpanded(), depth()
*/

/*!
    \qmlmethod QtQuick::TreeView::expandToIndex(QModelIndex index)
    \since 6.4

    Expands the tree from the given model \a index, and recursively all the way up
    to the root. The result will be that the delegate item that represents \a index
    becomes visible in the view (unless it ends up outside the viewport). To
    ensure that the row ends up visible in the viewport, you can do:

    \code
        expandToIndex(index)
        forceLayout()
        positionViewAtRow(rowAtIndex(index), Qt.AlignVCenter)
    \endcode

    \sa expand(), expandRecursively()
*/

/*!
    \qmlmethod QtQuick::TreeView::collapse(row)

    Collapses the tree node at the given \a row in the view.

    \a row should be the row in the view (table row), and not a row in the model.

    \note this function will not affect the model, only
    the visual representation in the view.

    \sa expand(), isExpanded()
*/

/*!
    \qmlmethod QtQuick::TreeView::collapseRecursively(row = -1)
    \since 6.4

    Collapses the tree node at the given \a row in the view recursively down to
    all leaves.

    For a model has more than one root, you can also call this function
    with \a row equal to \c -1. This will collapse all roots. Hence, calling
    collapseRecursively(-1), or simply collapseRecursively(), will collapse
    all nodes in the model.

    \a row should be the row in the view (table row), and not a row in the model.

    \note this function will not affect the model, only
    the visual representation in the view.

    \sa expandRecursively(), expand(), collapse(), isExpanded(), depth()
*/

/*!
    \qmlmethod QtQuick::TreeView::toggleExpanded(row)

    Toggles if the tree node at the given \a row should be expanded.
    This is a convenience for doing:

    \code
    if (isExpanded(row))
        collapse(row)
    else
        expand(row)
    \endcode

    \a row should be the row in the view (table row), and not a row in the model.
*/

/*!
    \qmlsignal QtQuick::TreeView::expanded(row, depth)

    This signal is emitted when a \a row is expanded in the view.
    \a row and \a depth will be equal to the arguments given to the call
    that caused the expansion to happen (\l expand() or \l expandRecursively()).
    In case of \l expand(), \a depth will always be \c 1.
    In case of \l expandToIndex(), \a depth will be the depth of the
    target index.

    \note when a row is expanded recursively, the expanded signal will
    only be emitted for that one row, and not for its descendants.

    \sa collapsed(), expand(), collapse(), toggleExpanded()
*/

/*!
    \qmlsignal QtQuick::TreeView::collapsed(row, recursively)

    This signal is emitted when a \a row is collapsed in the view.
    \a row will be equal to the argument given to the call that caused
    the collapse to happen (\l collapse() or \l collapseRecursively()).
    If the row was collapsed recursively, \a recursively will be \c true.

    \note when a row is collapsed recursively, the collapsed signal will
    only be emitted for that one row, and not for its descendants.

    \sa expanded(), expand(), collapse(), toggleExpanded()
*/

// Hard-code the tree column to be 0 for now
static const int kTreeColumn = 0;

QT_BEGIN_NAMESPACE

QQuickTreeViewPrivate::QQuickTreeViewPrivate()
    : QQuickTableViewPrivate()
{
}

QQuickTreeViewPrivate::~QQuickTreeViewPrivate()
{
}

QVariant QQuickTreeViewPrivate::modelImpl() const
{
    return m_assignedModel;
}

void QQuickTreeViewPrivate::setModelImpl(const QVariant &newModel)
{
    Q_Q(QQuickTreeView);

    m_assignedModel = newModel;
    QVariant effectiveModel = m_assignedModel;
    if (effectiveModel.userType() == qMetaTypeId<QJSValue>())
        effectiveModel = effectiveModel.value<QJSValue>().toVariant();

    if (effectiveModel.isNull())
        m_treeModelToTableModel.setModel(nullptr);
    else if (const auto qaim = qvariant_cast<QAbstractItemModel*>(effectiveModel))
        m_treeModelToTableModel.setModel(qaim);
    else
        qmlWarning(q) << "TreeView only accepts a model of type QAbstractItemModel";


    scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::All);
    emit q->modelChanged();
}

void QQuickTreeViewPrivate::initItemCallback(int serializedModelIndex, QObject *object)
{
    updateRequiredProperties(serializedModelIndex, object, true);
    QQuickTableViewPrivate::initItemCallback(serializedModelIndex, object);
}

void QQuickTreeViewPrivate::itemReusedCallback(int serializedModelIndex, QObject *object)
{
    updateRequiredProperties(serializedModelIndex, object, false);
    QQuickTableViewPrivate::itemReusedCallback(serializedModelIndex, object);
}

void QQuickTreeViewPrivate::dataChangedCallback(
        const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
    Q_Q(QQuickTreeView);
    Q_UNUSED(roles);

    for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
        for (int column = topLeft.column(); column <= bottomRight.column(); ++column) {
            const QPoint cell(column, row);
            auto item = q->itemAtCell(cell);
            if (!item)
                continue;

            const int serializedModelIndex = modelIndexAtCell(QPoint(column, row));
            updateRequiredProperties(serializedModelIndex, item, false);
        }
    }
}

void QQuickTreeViewPrivate::updateRequiredProperties(int serializedModelIndex, QObject *object, bool init)
{
    Q_Q(QQuickTreeView);
    const QPoint cell = cellAtModelIndex(serializedModelIndex);
    const int row = cell.y();
    const int column = cell.x();

    setRequiredProperty("treeView", QVariant::fromValue(q), serializedModelIndex, object, init);
    setRequiredProperty("isTreeNode", column == kTreeColumn, serializedModelIndex, object, init);
    setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init);
    setRequiredProperty("expanded", q->isExpanded(row), serializedModelIndex, object, init);
    setRequiredProperty("depth", m_treeModelToTableModel.depthAtRow(row), serializedModelIndex, object, init);
}

void QQuickTreeViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection)
{
    Q_Q(QQuickTreeView);

    if (oldSelection == newSelection)
        return;

    QItemSelection select;
    QItemSelection deselect;

    // Because each row can have a different parent, we need to create separate QItemSelections
    // per row. But all the cells in a given row have the same parent, so they can be combined.
    // As a result, the final QItemSelection can end up more fragmented compared to a selection
    // in QQuickTableView, where all cells have the same parent. In the end, if TreeView has
    // a lot of columns and the selection mode is "SelectCells", using the mouse to adjust
    // a selection containing a _large_ number of columns can be slow.
    const QRect cells = newSelection.normalized();
    for (int row = cells.y(); row <= cells.y() + cells.height(); ++row) {
        const QModelIndex startIndex = q->index(row, cells.x());
        const QModelIndex endIndex = q->index(row, cells.x() + cells.width());
        select.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select);
    }

    const QModelIndexList indexes = selectionModel->selection().indexes();
    for (const QModelIndex &index : indexes) {
        if (!select.contains(index) && !existingSelection.contains(index))
            deselect.merge(QItemSelection(index, index), QItemSelectionModel::Select);
    }

    if (selectionFlag == QItemSelectionModel::Select) {
        selectionModel->select(deselect, QItemSelectionModel::Deselect);
        selectionModel->select(select, QItemSelectionModel::Select);
    } else {
        QItemSelection oldSelection = existingSelection;
        oldSelection.merge(select, QItemSelectionModel::Deselect);
        selectionModel->select(oldSelection, QItemSelectionModel::Select);
        selectionModel->select(select, QItemSelectionModel::Deselect);
    }
}

QQuickTreeView::QQuickTreeView(QQuickItem *parent)
    : QQuickTableView(*(new QQuickTreeViewPrivate), parent)
{
    Q_D(QQuickTreeView);

    setSelectionBehavior(SelectRows);
    setEditTriggers(EditKeyPressed);

    // Note: QQuickTableView will only ever see the table model m_treeModelToTableModel, and
    // never the actual tree model that is assigned to us by the application.
    const auto modelAsVariant = QVariant::fromValue(std::addressof(d->m_treeModelToTableModel));
    d->QQuickTableViewPrivate::setModelImpl(modelAsVariant);
    QObjectPrivate::connect(&d->m_treeModelToTableModel, &QAbstractItemModel::dataChanged,
                            d, &QQuickTreeViewPrivate::dataChangedCallback);
    QObject::connect(&d->m_treeModelToTableModel, &QQmlTreeModelToTableModel::rootIndexChanged,
                     this, &QQuickTreeView::rootIndexChanged);

    auto tapHandler = new QQuickTapHandler(this);
    tapHandler->setAcceptedModifiers(Qt::NoModifier);
    connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, tapHandler]{
        if (!pointerNavigationEnabled())
            return;
        if (editTriggers() & DoubleTapped)
            return;

        const int row = cellAtPosition(tapHandler->point().pressPosition()).y();
        toggleExpanded(row);
    });
}

QQuickTreeView::~QQuickTreeView()
{
}

QModelIndex QQuickTreeView::rootIndex() const
{
    return d_func()->m_treeModelToTableModel.rootIndex();
}

void QQuickTreeView::setRootIndex(const QModelIndex &index)
{
    Q_D(QQuickTreeView);
    d->m_treeModelToTableModel.setRootIndex(index);
    positionViewAtCell({0, 0}, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
}

void QQuickTreeView::resetRootIndex()
{
    Q_D(QQuickTreeView);
    d->m_treeModelToTableModel.resetRootIndex();
    positionViewAtCell({0, 0}, QQuickTableView::AlignTop | QQuickTableView::AlignLeft);
}

int QQuickTreeView::depth(int row) const
{
    Q_D(const QQuickTreeView);
    if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
        return -1;

    return d->m_treeModelToTableModel.depthAtRow(row);
}

bool QQuickTreeView::isExpanded(int row) const
{
    Q_D(const QQuickTreeView);
    if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
        return false;

    return d->m_treeModelToTableModel.isExpanded(row);
}

void QQuickTreeView::expand(int row)
{
    if (row >= 0)
        expandRecursively(row, 1);
}

void QQuickTreeView::expandRecursively(int row, int depth)
{
    Q_D(QQuickTreeView);
    if (row >= d->m_treeModelToTableModel.rowCount())
        return;
    if (row < 0 && row != -1)
        return;
    if (depth == 0 || depth < -1)
        return;

    auto expandRowRecursively = [this, d, depth](int startRow) {
        d->m_treeModelToTableModel.expandRecursively(startRow, depth);
        // Update the expanded state of the startRow. The descendant rows that gets
        // expanded will get the correct state set from initItem/itemReused instead.
        for (int c = leftColumn(); c <= rightColumn(); ++c) {
            const QPoint treeNodeCell(c, startRow);
            if (const auto item = itemAtCell(treeNodeCell))
                d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, false);
        }
    };

    if (row >= 0) {
        // Expand only one row recursively
        const bool isExpanded = d->m_treeModelToTableModel.isExpanded(row);
        if (isExpanded && depth == 1)
            return;
        expandRowRecursively(row);
    } else {
        // Expand all root nodes recursively
        const auto model = d->m_treeModelToTableModel.model();
        for (int r = 0; r < model->rowCount(); ++r) {
            const int rootRow = d->m_treeModelToTableModel.itemIndex(model->index(r, 0));
            if (rootRow != -1)
                expandRowRecursively(rootRow);
        }
    }

    emit expanded(row, depth);
}

void QQuickTreeView::expandToIndex(const QModelIndex &index)
{
    Q_D(QQuickTreeView);

    if (!index.isValid()) {
        qmlWarning(this) << "index is not valid: " << index;
        return;
    }

    if (index.model() != d->m_treeModelToTableModel.model()) {
        qmlWarning(this) << "index doesn't belong to correct model: " << index;
        return;
    }

    if (rowAtIndex(index) != -1) {
        // index is already visible
        return;
    }

    int depth = 1;
    QModelIndex parent = index.parent();
    int row = rowAtIndex(parent);

    while (parent.isValid()) {
        if (row != -1) {
            // The node is already visible, since it maps to a row in the table!
            d->m_treeModelToTableModel.expandRow(row);

            // Update the state of the already existing delegate item
            for (int c = leftColumn(); c <= rightColumn(); ++c) {
                const QPoint treeNodeCell(c, row);
                if (const auto item = itemAtCell(treeNodeCell))
                    d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, false);
            }

            // When we hit a node that is visible, we know that all other nodes
            // up to the parent have to be visible as well, so we can stop.
            break;
        } else {
            d->m_treeModelToTableModel.expand(parent);
            parent = parent.parent();
            row = rowAtIndex(parent);
            depth++;
        }
    }

    emit expanded(row, depth);
}

void QQuickTreeView::collapse(int row)
{
    Q_D(QQuickTreeView);
    if (row < 0 || row >= d->m_treeModelToTableModel.rowCount())
        return;

    if (!d->m_treeModelToTableModel.isExpanded(row))
        return;

    d_func()->m_treeModelToTableModel.collapseRow(row);

    for (int c = leftColumn(); c <= rightColumn(); ++c) {
        const QPoint treeNodeCell(c, row);
        if (const auto item = itemAtCell(treeNodeCell))
            d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false);
    }

    emit collapsed(row, false);
}

void QQuickTreeView::collapseRecursively(int row)
{
    Q_D(QQuickTreeView);
    if (row >= d->m_treeModelToTableModel.rowCount())
        return;
    if (row < 0 && row != -1)
        return;

    auto collapseRowRecursive = [this, d](int startRow) {
        // Always collapse descendants recursively,
        // even if the top row itself is already collapsed.
        d->m_treeModelToTableModel.collapseRecursively(startRow);
        // Update the expanded state of the (still visible) startRow
        for (int c = leftColumn(); c <= rightColumn(); ++c) {
            const QPoint treeNodeCell(c, startRow);
            if (const auto item = itemAtCell(treeNodeCell))
                d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false);
        }
    };

    if (row >= 0) {
        collapseRowRecursive(row);
    } else {
        // Collapse all root nodes recursively
        const auto model = d->m_treeModelToTableModel.model();
        for (int r = 0; r < model->rowCount(); ++r) {
            const int rootRow = d->m_treeModelToTableModel.itemIndex(model->index(r, 0));
            if (rootRow != -1)
                collapseRowRecursive(rootRow);
        }
    }

    emit collapsed(row, true);
}

void QQuickTreeView::toggleExpanded(int row)
{
    if (isExpanded(row))
        collapse(row);
    else
        expand(row);
}

QModelIndex QQuickTreeView::modelIndex(const QPoint &cell) const
{
    Q_D(const QQuickTreeView);
    const QModelIndex tableIndex = d->m_treeModelToTableModel.index(cell.y(), cell.x());
    return d->m_treeModelToTableModel.mapToModel(tableIndex);
}

QPoint QQuickTreeView::cellAtIndex(const QModelIndex &index) const
{
    const QModelIndex tableIndex = d_func()->m_treeModelToTableModel.mapFromModel(index);
    return QPoint(tableIndex.column(), tableIndex.row());
}

#if QT_DEPRECATED_SINCE(6, 4)
QModelIndex QQuickTreeView::modelIndex(int row, int column) const
{
    static const bool compat6_4 = qEnvironmentVariable("QT_QUICK_TABLEVIEW_COMPAT_VERSION") == QStringLiteral("6.4");
    if (compat6_4) {
        // XXX Qt 7: Remove this compatibility path here and in QQuickTableView.
        // In Qt 6.4.0 and 6.4.1, a source incompatible change led to row and column
        // being documented to be specified in the opposite order.
        // QT_QUICK_TABLEVIEW_COMPAT_VERSION can therefore be set to force tableview
        // to continue accepting calls to modelIndex(column, row).
        return modelIndex({row, column});
    } else {
        qmlWarning(this) << "modelIndex(row, column) is deprecated. "
                            "Use index(row, column) instead. For more information, see "
                            "https://doc.qt.io/qt-6/qml-qtquick-tableview-obsolete.html";
        return modelIndex({column, row});
    }
}
#endif

void QQuickTreeView::keyPressEvent(QKeyEvent *event)
{
    event->ignore();

    if (!keyNavigationEnabled())
        return;
    if (!selectionModel())
        return;

    const int row = cellAtIndex(selectionModel()->currentIndex()).y();
    switch (event->key()) {
    case Qt::Key_Left:
        collapse(row);
        event->accept();
        break;
    case Qt::Key_Right:
        expand(row);
        event->accept();
        break;
    default:
        break;
    }

    if (!event->isAccepted())
        QQuickTableView::keyPressEvent(event);
}

QT_END_NAMESPACE

#include "moc_qquicktreeview_p.cpp"