Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qquicktreeview.cpp
Go to the documentation of this file.
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
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
258// Hard-code the tree column to be 0 for now
259static const int kTreeColumn = 0;
260
262
265{
266}
267
269{
270}
271
273{
274 return m_assignedModel;
275}
276
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())
291 else if (const auto qaim = qvariant_cast<QAbstractItemModel*>(effectiveModel))
293 else
294 qmlWarning(q) << "TreeView only accepts a model of type QAbstractItemModel";
295
296
298 emit q->modelChanged();
299}
300
301void QQuickTreeViewPrivate::initItemCallback(int serializedModelIndex, QObject *object)
302{
303 updateRequiredProperties(serializedModelIndex, object, true);
304 QQuickTableViewPrivate::initItemCallback(serializedModelIndex, object);
305}
306
307void QQuickTreeViewPrivate::itemReusedCallback(int serializedModelIndex, QObject *object)
308{
309 updateRequiredProperties(serializedModelIndex, object, false);
310 QQuickTableViewPrivate::itemReusedCallback(serializedModelIndex, object);
311}
312
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(QPoint(column, row));
327 updateRequiredProperties(serializedModelIndex, item, false);
328 }
329 }
330}
331
332void QQuickTreeViewPrivate::updateRequiredProperties(int serializedModelIndex, QObject *object, bool init)
333{
334 Q_Q(QQuickTreeView);
335 const QPoint cell = cellAtModelIndex(serializedModelIndex);
336 const int row = cell.y();
337 const int column = cell.x();
338
339 setRequiredProperty("treeView", QVariant::fromValue(q), serializedModelIndex, object, init);
340 setRequiredProperty("isTreeNode", column == kTreeColumn, serializedModelIndex, object, init);
341 setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init);
342 setRequiredProperty("expanded", q->isExpanded(row), serializedModelIndex, object, init);
343 setRequiredProperty("depth", 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, newRect.x());
361 const QModelIndex endIndex = q->index(row, newRect.x() + newRect.width());
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)
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, oldRect.x());
381 const QModelIndex endIndex = q->index(row, oldRect.x() + oldRect.width());
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)
398 }
399 }
400}
401
404{
405 Q_D(QQuickTreeView);
406
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(std::addressof(d->m_treeModelToTableModel));
413 d->QQuickTableViewPrivate::setModelImpl(modelAsVariant);
417 this, &QQuickTreeView::rootIndexChanged);
418
419 auto tapHandler = new QQuickTapHandler(this);
420 tapHandler->setAcceptedModifiers(Qt::NoModifier);
421 connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, tapHandler]{
423 return;
425 return;
426
427 const int row = cellAtPosition(tapHandler->point().pressPosition()).y();
429 });
430}
431
433{
434}
435
437{
438 return d_func()->m_treeModelToTableModel.rootIndex();
439}
440
442{
443 Q_D(QQuickTreeView);
444 d->m_treeModelToTableModel.setRootIndex(index);
446}
447
449{
450 Q_D(QQuickTreeView);
451 d->m_treeModelToTableModel.resetRootIndex();
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
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
474{
475 if (row >= 0)
476 expandRecursively(row, 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(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(treeNodeCell))
496 d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, 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(model->index(r, 0));
511 if (rootRow != -1)
512 expandRowRecursively(rootRow);
513 }
514 }
515
517}
518
519void QQuickTreeView::expandToIndex(const QModelIndex &index)
520{
521 Q_D(QQuickTreeView);
522
523 if (!index.isValid()) {
524 qmlWarning(this) << "index is not valid: " << index;
525 return;
526 }
527
528 if (index.model() != d->m_treeModelToTableModel.model()) {
529 qmlWarning(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;
540 int row = rowAtIndex(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(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(treeNodeCell))
551 d->setRequiredProperty("expanded", true, d->modelIndexAtCell(treeNodeCell), item, 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(parent);
561 depth++;
562 }
563 }
564
566}
567
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(row);
578
579 for (int c = leftColumn(); c <= rightColumn(); ++c) {
580 const QPoint treeNodeCell(c, row);
581 if (const auto item = itemAtCell(treeNodeCell))
582 d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, false);
583 }
584
585 emit collapsed(row, 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(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(treeNodeCell))
604 d->setRequiredProperty("expanded", false, d->modelIndexAtCell(treeNodeCell), item, 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(model->index(r, 0));
615 if (rootRow != -1)
616 collapseRowRecursive(rootRow);
617 }
618 }
619
620 emit collapsed(row, true);
621}
622
624{
625 if (isExpanded(row))
626 collapse(row);
627 else
628 expand(row);
629}
630
632{
633 Q_D(const QQuickTreeView);
634 const QModelIndex tableIndex = d->m_treeModelToTableModel.index(cell.y(), cell.x());
635 return d->m_treeModelToTableModel.mapToModel(tableIndex);
636}
637
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)
646{
647 static const bool compat6_4 = qEnvironmentVariable("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({row, column});
655 } else {
656 qmlWarning(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({column, row});
660 }
661}
662#endif
663
665{
666 event->ignore();
667
669 return;
670 if (!selectionModel())
671 return;
672
673 const int row = cellAtIndex(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())
689}
690
692
693#include "moc_qquicktreeview_p.cpp"
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles=QList< int >())
This signal is emitted whenever the data in an existing item changes.
virtual void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
Selects the model item index using the specified command, and emits selectionChanged().
\inmodule QtCore
The QJSValue class acts as a container for Qt/JavaScript data types.
Definition qjsvalue.h:31
The QKeyEvent class describes a key event.
Definition qevent.h:423
Definition qlist.h:74
\inmodule QtCore
constexpr int row() const noexcept
Returns the row this model index refers to.
constexpr int column() const noexcept
Returns the column this model index refers to.
static QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer< Func1 >::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer< Func2 >::Object *receiverPrivate, Func2 slot, Qt::ConnectionType type=Qt::AutoConnection)
Definition qobject_p.h:298
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
\inmodule QtCore\reentrant
Definition qpoint.h:23
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:127
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:132
void setModel(QAbstractItemModel *model)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:64
QQuickItem * parent
\qmlproperty Item QtQuick::Item::parent This property holds the visual parent of the item.
Definition qquickitem.h:68
QPoint cellAtModelIndex(int modelIndex) const
virtual void itemReusedCallback(int modelIndex, QObject *object)
void scheduleRebuildTable(QQuickTableViewPrivate::RebuildOptions options)
virtual void initItemCallback(int modelIndex, QObject *item)
QAbstractItemModel * qaim(QVariant modelAsVariant) const
int modelIndexAtCell(const QPoint &cell) const
QPointer< QItemSelectionModel > selectionModel
void setRequiredProperty(const char *property, const QVariant &value, int serializedModelIndex, QObject *object, bool init)
void setSelectionBehavior(SelectionBehavior selectionBehavior)
FINALbool keyNavigationEnabled
void setEditTriggers(EditTriggers editTriggers)
FINALQItemSelectionModel * selectionModel
FINALbool pointerNavigationEnabled
FINALEditTriggers editTriggers
Q_INVOKABLE QQuickItem * itemAtCell(const QPoint &cell) const
Q_INVOKABLE void positionViewAtCell(const QPoint &cell, PositionMode mode, const QPointF &offset=QPointF(), const QRectF &subRect=QRectF())
void keyPressEvent(QKeyEvent *e) override
This event handler can be reimplemented in a subclass to receive key press events for an item.
void doubleTapped(QEventPoint eventPoint, Qt::MouseButton)
void updateSelection(const QRect &oldSelection, const QRect &newSelection) override
QVariant modelImpl() const override
void setModelImpl(const QVariant &newModel) override
QQmlTreeModelToTableModel m_treeModelToTableModel
void updateRequiredProperties(int serializedModelIndex, QObject *object, bool init)
void initItemCallback(int serializedModelIndex, QObject *object) override
void itemReusedCallback(int serializedModelIndex, QObject *object) override
void dataChangedCallback(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
Q_INVOKABLE void collapse(int row)
Q_INVOKABLE void expand(int row)
Q_INVOKABLE bool isExpanded(int row) const
void expanded(int row, int depth)
QQuickTreeView(QQuickItem *parent=nullptr)
QModelIndex rootIndex
Q_INVOKABLE void toggleExpanded(int row)
void keyPressEvent(QKeyEvent *event) override
This event handler can be reimplemented in a subclass to receive key press events for an item.
void setRootIndex(const QModelIndex &index)
void collapsed(int row, bool recursively)
Q_INVOKABLE QModelIndex modelIndex(const QPoint &cell) const override
~QQuickTreeView() override
Q_INVOKABLE QPoint cellAtIndex(const QModelIndex &index) const override
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:238
QRect normalized() const noexcept
Returns a normalized rectangle; i.e., a rectangle that has a non-negative width and height.
Definition qrect.cpp:273
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:184
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:235
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:187
\inmodule QtCore
Definition qvariant.h:64
T value() const &
Definition qvariant.h:511
int userType() const
Definition qvariant.h:336
bool isNull() const
Returns true if this is a null variant, false otherwise.
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:531
Combined button and popup list for selecting options.
@ Key_Right
Definition qnamespace.h:674
@ Key_Left
Definition qnamespace.h:672
@ NoModifier
GLint GLenum GLsizei GLsizei GLsizei depth
GLuint index
[2]
GLboolean r
[2]
GLenum GLenum GLsizei void GLsizei void * column
struct _cl_event * event
const GLubyte * c
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLenum GLenum GLsizei void * row
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
static const int kTreeColumn
\qmltype TreeView \inqmlmodule QtQuick
#define QStringLiteral(str)
QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
static QVariant toVariant(const QV4::Value &value, QMetaType typeHint, bool createJSValueForObjectsAndSymbols, V4ObjectSet *visitedObjects)
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
QGraphicsItem * item
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent