aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conanfile.py1
-rw-r--r--src/quick/CMakeLists.txt6
-rw-r--r--src/quick/configure.cmake9
-rw-r--r--src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml90
-rw-r--r--src/quick/items/qquicktreeview.cpp496
-rw-r--r--src/quick/items/qquicktreeview_p.h96
-rw-r--r--src/quick/items/qquicktreeview_p_p.h90
-rw-r--r--src/quick/qt_cmdline.cmake1
-rw-r--r--tests/auto/quick/CMakeLists.txt1
-rw-r--r--tests/auto/quick/qquicktreeview/CMakeLists.txt43
-rw-r--r--tests/auto/quick/qquicktreeview/data/CustomDelegate.qml79
-rw-r--r--tests/auto/quick/qquicktreeview/data/normaltreeview.qml58
-rw-r--r--tests/auto/quick/qquicktreeview/testmodel.cpp157
-rw-r--r--tests/auto/quick/qquicktreeview/testmodel.h83
-rw-r--r--tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp364
-rw-r--r--tests/manual/treeview/sidebyside/CMakeLists.txt47
-rw-r--r--tests/manual/treeview/sidebyside/data/treeview.qml116
-rw-r--r--tests/manual/treeview/sidebyside/main.cpp80
-rw-r--r--tests/manual/treeview/sidebyside/testmodel.cpp200
-rw-r--r--tests/manual/treeview/sidebyside/testmodel.h87
20 files changed, 2103 insertions, 1 deletions
diff --git a/conanfile.py b/conanfile.py
index a2ca5cecc5..139f2e1cbc 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -64,6 +64,7 @@ _qtdeclarative_features = [
"quick-shadereffect",
"quick-sprite",
"quick-tableview",
+ "quick-treeview",
]
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index 0501726a86..e996cff237 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -456,6 +456,12 @@ qt_internal_extend_target(Quick CONDITION QT_FEATURE_quick_tableview
items/qquickselectable_p.h
)
+qt_internal_extend_target(Quick CONDITION QT_FEATURE_quick_treeview
+ SOURCES
+ items/qquicktreeview.cpp items/qquicktreeview_p.h
+ items/qquicktreeview_p_p.h
+)
+
qt_internal_extend_target(Quick CONDITION QT_FEATURE_quick_pathview
SOURCES
items/qquickpathview.cpp items/qquickpathview_p.h
diff --git a/src/quick/configure.cmake b/src/quick/configure.cmake
index b6e5ded40b..09154f3329 100644
--- a/src/quick/configure.cmake
+++ b/src/quick/configure.cmake
@@ -47,7 +47,7 @@ qt_feature("quick-gridview" PRIVATE
)
qt_feature("quick-itemview" PRIVATE
LABEL "ItemView item"
- CONDITION QT_FEATURE_quick_gridview OR QT_FEATURE_quick_listview OR QT_FEATURE_quick_tableview
+ CONDITION QT_FEATURE_quick_gridview OR QT_FEATURE_quick_listview OR QT_FEATURE_quick_tableview OR QT_FEATURE_quick_treeview
)
qt_feature("quick-viewtransitions" PRIVATE
LABEL "Transitions required for ItemViews and Positioners"
@@ -65,6 +65,12 @@ qt_feature("quick-tableview" PRIVATE
PURPOSE "Provides the TableView item."
CONDITION QT_FEATURE_qml_table_model
)
+qt_feature("quick-treeview" PRIVATE
+ SECTION "Qt Quick"
+ LABEL "TreeView item"
+ PURPOSE "Provides the TreeView item."
+ CONDITION QT_FEATURE_qml_table_model
+)
qt_feature("quick-particles" PRIVATE
SECTION "Qt Quick"
LABEL "Particle support"
@@ -118,6 +124,7 @@ qt_configure_add_summary_entry(ARGS "quick-flipable")
qt_configure_add_summary_entry(ARGS "quick-gridview")
qt_configure_add_summary_entry(ARGS "quick-listview")
qt_configure_add_summary_entry(ARGS "quick-tableview")
+qt_configure_add_summary_entry(ARGS "quick-treeview")
qt_configure_add_summary_entry(ARGS "quick-path")
qt_configure_add_summary_entry(ARGS "quick-pathview")
qt_configure_add_summary_entry(ARGS "quick-positioners")
diff --git a/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml b/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml
new file mode 100644
index 0000000000..756815cb76
--- /dev/null
+++ b/src/quick/doc/snippets/qml/treeview/qml-customdelegate.qml
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//![0]
+import QtQuick
+
+Window {
+ width: 600
+ height: 400
+ visible: true
+
+ TreeView {
+ anchors.fill: parent
+ // The model needs to be a QAbstractItemModel
+ // model: yourTreeModel
+
+ delegate: Item {
+ id: root
+
+ implicitWidth: padding + label.x + label.implicitWidth + padding
+ implicitHeight: label.implicitHeight * 1.5
+
+ readonly property real indent: 20
+ readonly property real padding: 5
+
+ // Assigned to by TreeView:
+ required property TreeView treeView
+ required property bool isTreeNode
+ required property bool expanded
+ required property int hasChildren
+ required property int depth
+
+ TapHandler {
+ onTapped: treeView.toggleExpanded(row)
+ }
+
+ Text {
+ id: indicator
+ visible: root.isTreeNode && root.hasChildren
+ x: padding + (root.depth * root.indent)
+ text: root.expanded ? "▼" : "▶"
+ }
+
+ Text {
+ id: label
+ x: padding + (root.isTreeNode ? (root.depth + 1) * root.indent : 0)
+ width: root.width - root.padding - x
+ clip: true
+ text: model.display
+ }
+ }
+ }
+}
+//![0]
diff --git a/src/quick/items/qquicktreeview.cpp b/src/quick/items/qquicktreeview.cpp
new file mode 100644
index 0000000000..2cf0d091d9
--- /dev/null
+++ b/src/quick/items/qquicktreeview.cpp
@@ -0,0 +1,496 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquicktreeview_p_p.h"
+
+#include <QtCore/qobject.h>
+#include <QtQml/qqmlcontext.h>
+
+#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>
+
+/*!
+ \qmltype TreeView
+ \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 \l 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 \l 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 mininal as possible, e.g 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 how a custom delegate could look like 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 \l 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 int 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}.
+
+ \note A TreeView only accept models that inherits \l QAbstractItemModel.
+*/
+
+/*!
+ \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 rows, the return value will be \c -1.
+
+ \sa 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 rows, the return value will be \c false.
+
+ \sa viewIndex
+*/
+
+/*!
+ \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()
+*/
+
+/*!
+ \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::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.
+*/
+
+/*!
+ \qmlmethod QModelIndex QtQuick::TreeView::modelIndex(row, column)
+
+ Returns the \l QModelIndex that maps to \a row and \a column in the view.
+
+ \a row and \a column should be the row and column in the view (table row and
+ table column), and not a row and column in the model.
+
+ The assigned model, which is a tree model, is converted to a flat table
+ model internally so that it can be shown in a TableView (which TreeView
+ inherits). This function can be used whenever you need to know which
+ index in the tree model maps to the given row and column in the view.
+
+ \sa rowAtIndex(), columnAtIndex()
+*/
+
+/*!
+ \qmlmethod QModelIndex QtQuick::TreeView::modelIndex(point cell)
+
+ Convenience function for doing:
+ \code
+ modelIndex(cell.y, cell.x)
+ \endcode
+
+ A cell is simply a \l point that combines row and column into
+ a single type. Note that \c point.x will map to the column, and
+ \c point.y will map to the row.
+*/
+
+/*!
+ \qmlmethod int QtQuick::TreeView::rowAtIndex(modelIndex)
+
+ Returns the row in the view that maps to \a modelIndex in the model.
+
+ The assigned model, which is a tree model, is converted to a flat table
+ model internally so that it can be shown in a TableView (which TreeView
+ inherits). This function can be used whenever you need to know which
+ row in the view the given model index maps to.
+
+ \note \a modelIndex must be a \l QModelIndex.
+
+ \sa columnAtIndex(), modelIndex()
+*/
+
+/*!
+ \qmlmethod int QtQuick::TreeView::columnAtIndex(modelIndex)
+
+ Returns the column in the view that maps to \a modelIndex in the model.
+
+ The assigned model, which is a tree model, is converted to a flat table
+ model internally so that it can be shown in a TableView (which TreeView
+ inherits). This function can be used whenever you need to know which
+ column in the view the given model index maps to.
+
+ \note \a modelIndex must be a \l QModelIndex.
+
+ \sa rowAtIndex(), modelIndex()
+*/
+
+/*!
+ \qmlmethod point QtQuick::TreeView::cellAtIndex(modelIndex)
+
+ Convenience function for doing:
+ \code
+ Qt.point(columnAtIndex(modelIndex), rowAtIndex(modelIndex))
+ \endcode
+
+ A cell is simply a \l point that combines row and column into
+ a single type. Note that \c point.x will map to the column, and
+ \c point.y will map to the row.
+*/
+
+/*!
+ \qmlsignal QtQuick::TreeView::expanded(row)
+
+ This signal is emitted when a row is expanded in the view.
+
+ \sa collapsed(), expand(), collapse(), toggleExpanded()
+*/
+
+/*!
+ \qmlsignal QtQuick::TreeView::collapsed(row)
+
+ This signal is emitted when a row is collapsed in the view.
+
+ \sa expanded(), expand(), collapse(), toggleExpanded()
+*/
+
+static const char* kRequiredProperties = "_qt_treeview_requiredpropertymask";
+// 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);
+
+ if (newModel == m_assignedModel)
+ return;
+
+ 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 accept models 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::setRequiredProperty(const char *property,
+ const QVariant &value, int serializedModelIndex, QObject *object, bool init)
+{
+ // Attaching a property list to the delegate item is just a
+ // work-around until QMetaProperty::isRequired() works!
+ const QString propertyName = QString::fromUtf8(property);
+
+ if (init) {
+ const bool wasRequired = model->setRequiredProperty(serializedModelIndex, propertyName, value);
+ if (wasRequired) {
+ QStringList propertyList = object->property(kRequiredProperties).toStringList();
+ object->setProperty(kRequiredProperties, propertyList << propertyName);
+ }
+ } else {
+ const QStringList propertyList = object->property(kRequiredProperties).toStringList();
+ if (!propertyList.contains(propertyName)) {
+ // We only write to properties that are required
+ return;
+ }
+ const auto metaObject = object->metaObject();
+ const int propertyIndex = metaObject->indexOfProperty(property);
+ const auto metaProperty = metaObject->property(propertyIndex);
+ metaProperty.write(object, value);
+ }
+}
+
+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);
+}
+
+QQuickTreeView::QQuickTreeView(QQuickItem *parent)
+ : QQuickTableView(*(new QQuickTreeViewPrivate), parent)
+{
+ Q_D(QQuickTreeView);
+
+ // 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);
+}
+
+QQuickTreeView::~QQuickTreeView()
+{
+}
+
+int QQuickTreeView::depth(int row) const
+{
+ if (row < 0 || row >= rows())
+ return -1;
+
+ return d_func()->m_treeModelToTableModel.depthAtRow(row);
+}
+
+bool QQuickTreeView::isExpanded(int row) const
+{
+ if (row < 0 || row >= rows())
+ return false;
+
+ return d_func()->m_treeModelToTableModel.isExpanded(row);
+}
+
+void QQuickTreeView::expand(int row)
+{
+ Q_D(QQuickTreeView);
+ if (row < 0 || row >= rows())
+ return;
+
+ if (d->m_treeModelToTableModel.isExpanded(row))
+ return;
+
+ d->m_treeModelToTableModel.expandRow(row);
+
+ 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);
+ }
+
+ emit expanded(row);
+}
+
+void QQuickTreeView::collapse(int row)
+{
+ Q_D(QQuickTreeView);
+ if (row < 0 || row >= rows())
+ 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);
+}
+
+void QQuickTreeView::toggleExpanded(int row)
+{
+ if (isExpanded(row))
+ collapse(row);
+ else
+ expand(row);
+}
+
+QModelIndex QQuickTreeView::modelIndex(int row, int column) const
+{
+ Q_D(const QQuickTreeView);
+ const QModelIndex tableIndex = d->m_treeModelToTableModel.index(row, column);
+ return d->m_treeModelToTableModel.mapToModel(tableIndex);
+}
+
+QModelIndex QQuickTreeView::modelIndex(const QPoint &cell) const
+{
+ return modelIndex(cell.y(), cell.x());
+}
+
+int QQuickTreeView::rowAtIndex(const QModelIndex &index) const
+{
+ return d_func()->m_treeModelToTableModel.mapFromModel(index).row();
+}
+
+int QQuickTreeView::columnAtIndex(const QModelIndex &index) const
+{
+ return d_func()->m_treeModelToTableModel.mapFromModel(index).column();
+}
+
+QPoint QQuickTreeView::cellAtIndex(const QModelIndex &index) const
+{
+ const QModelIndex tableIndex = d_func()->m_treeModelToTableModel.mapFromModel(index);
+ return QPoint(tableIndex.column(), tableIndex.row());
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktreeview_p.h b/src/quick/items/qquicktreeview_p.h
new file mode 100644
index 0000000000..9010030500
--- /dev/null
+++ b/src/quick/items/qquicktreeview_p.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKTREEVIEW_P_H
+#define QQUICKTREEVIEW_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qabstractitemmodel.h>
+#include "qquicktableview_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQuickTreeViewPrivate;
+
+class Q_QUICK_PRIVATE_EXPORT QQuickTreeView : public QQuickTableView
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(TreeView)
+
+public:
+ QQuickTreeView(QQuickItem *parent = nullptr);
+ ~QQuickTreeView() override;
+
+ Q_INVOKABLE int depth(int row) const;
+
+ Q_INVOKABLE bool isExpanded(int row) const;
+ Q_INVOKABLE void expand(int row);
+ Q_INVOKABLE void collapse(int row);
+ Q_INVOKABLE void toggleExpanded(int row);
+
+ Q_INVOKABLE QModelIndex modelIndex(int row, int column) const;
+ Q_INVOKABLE QModelIndex modelIndex(const QPoint &cell) const;
+ Q_INVOKABLE int rowAtIndex(const QModelIndex &index) const;
+ Q_INVOKABLE int columnAtIndex(const QModelIndex &index) const;
+ Q_INVOKABLE QPoint cellAtIndex(const QModelIndex &index) const;
+
+signals:
+ void expanded(int row);
+ void collapsed(int row);
+
+private:
+ Q_DISABLE_COPY(QQuickTreeView)
+ Q_DECLARE_PRIVATE(QQuickTreeView)
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickTreeView)
+
+#endif // QQUICKTREEVIEW_P_H
diff --git a/src/quick/items/qquicktreeview_p_p.h b/src/quick/items/qquicktreeview_p_p.h
new file mode 100644
index 0000000000..db974aefc9
--- /dev/null
+++ b/src/quick/items/qquicktreeview_p_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKTREEVIEW_P_P_H
+#define QQUICKTREEVIEW_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qquicktreeview_p.h"
+#include "qquicktableview_p_p.h"
+
+#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_PRIVATE_EXPORT QQuickTreeViewPrivate : public QQuickTableViewPrivate
+{
+public:
+ Q_DECLARE_PUBLIC(QQuickTreeView)
+
+ QQuickTreeViewPrivate();
+ ~QQuickTreeViewPrivate() override;
+
+ static inline QQuickTreeViewPrivate *get(QQuickTreeView *q) { return q->d_func(); }
+
+ QVariant modelImpl() const override;
+ void setModelImpl(const QVariant &newModel) override;
+
+ 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);
+
+ void setRequiredProperty(const char *property, const QVariant &value, int serializedModelIndex, QObject *object, bool init);
+ void updateRequiredProperties(int serializedModelIndex, QObject *object, bool init);
+
+public:
+ QQmlTreeModelToTableModel m_treeModelToTableModel;
+ QVariant m_assignedModel;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKTREEVIEW_P_P_H
diff --git a/src/quick/qt_cmdline.cmake b/src/quick/qt_cmdline.cmake
index 361c3e07e8..b328031a8c 100644
--- a/src/quick/qt_cmdline.cmake
+++ b/src/quick/qt_cmdline.cmake
@@ -5,6 +5,7 @@ qt_commandline_option(quick-flipable TYPE boolean)
qt_commandline_option(quick-gridview TYPE boolean)
qt_commandline_option(quick-listview TYPE boolean)
qt_commandline_option(quick-tableview TYPE boolean)
+qt_commandline_option(quick-treeview TYPE boolean)
qt_commandline_option(quick-path TYPE boolean)
qt_commandline_option(quick-pathview TYPE boolean)
qt_commandline_option(quick-positioners TYPE boolean)
diff --git a/tests/auto/quick/CMakeLists.txt b/tests/auto/quick/CMakeLists.txt
index 7f4fcc15f5..de701f909d 100644
--- a/tests/auto/quick/CMakeLists.txt
+++ b/tests/auto/quick/CMakeLists.txt
@@ -49,6 +49,7 @@ if(QT_FEATURE_private_tests)
add_subdirectory(qquicklistview)
add_subdirectory(qquicklistview2)
add_subdirectory(qquicktableview)
+ add_subdirectory(qquicktreeview)
add_subdirectory(qquickloader)
add_subdirectory(qquickmousearea)
add_subdirectory(qquickmultipointtoucharea)
diff --git a/tests/auto/quick/qquicktreeview/CMakeLists.txt b/tests/auto/quick/qquicktreeview/CMakeLists.txt
new file mode 100644
index 0000000000..504b18727b
--- /dev/null
+++ b/tests/auto/quick/qquicktreeview/CMakeLists.txt
@@ -0,0 +1,43 @@
+#####################################################################
+## tst_qquicktreeview Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qquicktreeview
+ SOURCES
+ testmodel.h testmodel.cpp
+ tst_qquicktreeview.cpp
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::QmlModelsPrivate
+ Qt::QmlPrivate
+ Qt::QuickPrivate
+ Qt::QuickTest
+ Qt::QuickTestUtilsPrivate
+ TESTDATA ${test_data}
+)
+
+#### Keys ignored in scope 1:.:.:qquicktableview.pro:<TRUE>:
+# DISTFILES = <EMPTY>
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qquicktreeview CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\":/data\\\"
+)
+
+qt_internal_extend_target(tst_qquicktreeview CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+)
diff --git a/tests/auto/quick/qquicktreeview/data/CustomDelegate.qml b/tests/auto/quick/qquicktreeview/data/CustomDelegate.qml
new file mode 100644
index 0000000000..47c36977df
--- /dev/null
+++ b/tests/auto/quick/qquicktreeview/data/CustomDelegate.qml
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import TestModel
+
+Item {
+ id: root
+
+ implicitWidth: padding + label.x + label.implicitWidth + padding
+ implicitHeight: label.implicitHeight * 1.5
+
+ property alias text: label.text
+
+ readonly property real indent: 20
+ readonly property real padding: 5
+
+ // Assigned to by TreeView:
+ required property TreeView treeView
+ required property bool isTreeNode
+ required property bool expanded
+ required property int hasChildren
+ required property int depth
+
+ TapHandler {
+ onTapped: treeView.toggleExpanded(row)
+ }
+
+ Text {
+ id: indicator
+ visible: root.isTreeNode && root.hasChildren
+ x: padding + (root.depth * root.indent)
+ text: root.expanded ? "▼" : "▶"
+ }
+
+ Text {
+ id: label
+ x: padding + (root.isTreeNode ? (root.depth + 1) * root.indent : 0)
+ width: root.width - root.padding - x
+ clip: true
+ text: model.display
+ }
+}
diff --git a/tests/auto/quick/qquicktreeview/data/normaltreeview.qml b/tests/auto/quick/qquicktreeview/data/normaltreeview.qml
new file mode 100644
index 0000000000..58f077fd6b
--- /dev/null
+++ b/tests/auto/quick/qquicktreeview/data/normaltreeview.qml
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import TestModel
+
+Item {
+ width: 800
+ height: 600
+
+ property alias treeView: treeView
+
+ TreeView {
+ id: treeView
+ anchors.fill:parent
+ anchors.margins: 10
+ model: TestModel {}
+ clip: true
+
+ delegate: CustomDelegate {}
+ }
+}
diff --git a/tests/auto/quick/qquicktreeview/testmodel.cpp b/tests/auto/quick/qquicktreeview/testmodel.cpp
new file mode 100644
index 0000000000..a562b3274a
--- /dev/null
+++ b/tests/auto/quick/qquicktreeview/testmodel.cpp
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "testmodel.h"
+
+TreeItem::TreeItem(TreeItem *parent)
+ : m_parentItem(parent)
+{}
+
+TreeItem::~TreeItem()
+{
+ qDeleteAll(m_childItems);
+}
+
+int TreeItem::row() const
+{
+ if (!m_parentItem)
+ return 0;
+ return m_parentItem->m_childItems.indexOf(const_cast<TreeItem *>(this));
+}
+
+TestModel::TestModel(QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ m_rootItem.reset(new TreeItem());
+ for (int col = 0; col < m_columnCount; ++col)
+ m_rootItem.data()->m_entries << QVariant(QString("0, %1").arg(col));
+ createTreeRecursive(m_rootItem.data(), 4, 0, 4);
+}
+
+void TestModel::createTreeRecursive(TreeItem *item, int childCount, int currentDepth, int maxDepth)
+{
+ for (int row = 0; row < childCount; ++row) {
+ auto childItem = new TreeItem(item);
+ for (int col = 0; col < m_columnCount; ++col)
+ childItem->m_entries << QVariant(QString("%1, %2").arg(row).arg(col));
+ item->m_childItems.append(childItem);
+ if (currentDepth < maxDepth && row == childCount - 1)
+ createTreeRecursive(childItem, childCount, currentDepth + 1, maxDepth);
+ }
+}
+
+TreeItem *TestModel::treeItem(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return m_rootItem.data();
+ return static_cast<TreeItem *>(index.internalPointer());
+}
+
+int TestModel::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return 1; // root of the tree
+ return treeItem(parent)->m_childItems.count();
+}
+
+int TestModel::columnCount(const QModelIndex &) const
+{
+ return m_columnCount;
+}
+
+QVariant TestModel::data(const QModelIndex &index, int role) const
+{
+ Q_UNUSED(role)
+ if (!index.isValid())
+ return QVariant();
+ TreeItem *item = treeItem(index);
+ return item->m_entries.at(index.column());
+}
+
+bool TestModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ Q_UNUSED(role)
+ if (!index.isValid())
+ return false;
+ TreeItem *item = treeItem(index);
+ if (!item)
+ return false;
+ item->m_entries[index.column()] = value;
+ emit dataChanged(index, index);
+ return true;
+}
+
+QModelIndex TestModel::index(int row, int column, const QModelIndex &parent) const
+{
+ Q_UNUSED(column)
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+ if (!parent.isValid())
+ return createIndex(0, 0, m_rootItem.data());
+ return createIndex(row, column, treeItem(parent)->m_childItems.at(row));
+}
+
+QModelIndex TestModel::parent(const QModelIndex &index) const
+{
+ TreeItem *parentItem = treeItem(index)->m_parentItem;
+ if (!parentItem)
+ return QModelIndex();
+ return createIndex(parentItem->row(), 0, parentItem);
+}
+
+bool TestModel::insertRows(int position, int rows, const QModelIndex &parent)
+{
+ if (!parent.isValid()) {
+ qWarning() << "Cannot insert rows on an invalid parent!";
+ return false;
+ }
+
+ beginInsertRows(parent, position, position + rows - 1);
+ TreeItem *parentItem = treeItem(parent);
+
+ for (int row = 0; row < rows; ++row) {
+ auto newChildItem = new TreeItem(parentItem);
+ for (int col = 0; col < m_columnCount; ++col)
+ newChildItem->m_entries << QVariant(QString("%1, %2 (inserted)").arg(position + row).arg(col));
+ parentItem->m_childItems.insert(position + row, newChildItem);
+ }
+
+ endInsertRows();
+ return true;
+}
diff --git a/tests/auto/quick/qquicktreeview/testmodel.h b/tests/auto/quick/qquicktreeview/testmodel.h
new file mode 100644
index 0000000000..4d9006d112
--- /dev/null
+++ b/tests/auto/quick/qquicktreeview/testmodel.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TESTMODEL_H
+#define TESTMODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtQuick/qquickview.h>
+
+class TreeItem
+{
+public:
+ explicit TreeItem(TreeItem *parent = nullptr);
+ ~TreeItem();
+
+ int row() const;
+ QVector<TreeItem *> m_childItems;
+ TreeItem *m_parentItem;
+ QVector<QVariant> m_entries;
+};
+
+// ########################################################
+
+class TestModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit TestModel(QObject *parent = nullptr);
+
+ void createTreeRecursive(TreeItem *item, int childCount, int currentDepth, int maxDepth);
+ TreeItem *treeItem(const QModelIndex &index) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex & = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+
+ bool insertRows(int position, int rows, const QModelIndex &parent) override;
+
+private:
+ QScopedPointer<TreeItem> m_rootItem;
+ int m_columnCount = 2;
+};
+
+#endif // TESTMODEL_H
diff --git a/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp b/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp
new file mode 100644
index 0000000000..c1ba0a62a5
--- /dev/null
+++ b/tests/auto/quick/qquicktreeview/tst_qquicktreeview.cpp
@@ -0,0 +1,364 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtQuickTest/quicktest.h>
+
+#include <QtQuick/qquickview.h>
+#include <QtQuick/private/qquicktreeview_p.h>
+#include <QtQuick/private/qquicktreeview_p_p.h>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQml/qqmlexpression.h>
+#include <QtQml/qqmlincubator.h>
+#include <QtQmlModels/private/qqmlobjectmodel_p.h>
+#include <QtQmlModels/private/qqmllistmodel_p.h>
+
+#include "testmodel.h"
+
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QtQuickTestUtils/private/viewtestutils_p.h>
+#include <QtQuickTestUtils/private/visualtestutils_p.h>
+
+using namespace QQuickViewTestUtils;
+using namespace QQuickVisualTestUtils;
+
+#define LOAD_TREEVIEW(fileName) \
+ view->setSource(testFileUrl(fileName)); \
+ view->show(); \
+ QVERIFY(QTest::qWaitForWindowActive(view)); \
+ auto treeView = view->rootObject()->property("treeView").value<QQuickTreeView *>(); \
+ QVERIFY(treeView); \
+ auto treeViewPrivate = QQuickTreeViewPrivate::get(treeView); \
+ Q_UNUSED(treeViewPrivate) \
+ auto model = treeView->model().value<TestModel *>(); \
+ Q_UNUSED(model)
+
+#define WAIT_UNTIL_POLISHED_ARG(item) \
+ QVERIFY(QQuickTest::qIsPolishScheduled(item)); \
+ QVERIFY(QQuickTest::qWaitForItemPolished(item))
+#define WAIT_UNTIL_POLISHED WAIT_UNTIL_POLISHED_ARG(treeView)
+
+// ########################################################
+
+class tst_qquicktreeview : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_qquicktreeview();
+
+private:
+ QQuickView *view = nullptr;
+
+private slots:
+ void initTestCase() override;
+ void showTreeView();
+ void expandAndCollapseRoot();
+ void toggleExpanded();
+ void expandAndCollapseChildren();
+ void requiredPropertiesRoot();
+ void requiredPropertiesChildren();
+ void emptyModel();
+ void updatedModifiedModel();
+ void insertRows();
+};
+
+tst_qquicktreeview::tst_qquicktreeview()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
+{
+}
+
+void tst_qquicktreeview::initTestCase()
+{
+ QQmlDataTest::initTestCase();
+ qmlRegisterType<TestModel>("TestModel", 1, 0, "TestModel");
+ view = createView();
+}
+
+void tst_qquicktreeview::showTreeView()
+{
+ LOAD_TREEVIEW("normaltreeview.qml");
+ // Check that the view is showing the root of the tree
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 1);
+}
+
+void tst_qquicktreeview::expandAndCollapseRoot()
+{
+ LOAD_TREEVIEW("normaltreeview.qml");
+ // Check that the view only has one row loaded so far (the root of the tree)
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 1);
+
+ // Expand the root
+ treeView->expand(0);
+ WAIT_UNTIL_POLISHED;
+ // We now expect 5 rows, the root pluss it's 4 children
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 5);
+
+ treeView->collapse(0);
+ WAIT_UNTIL_POLISHED;
+ // Check that the view only has one row loaded again (the root of the tree)
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 1);
+}
+
+void tst_qquicktreeview::toggleExpanded()
+{
+ LOAD_TREEVIEW("normaltreeview.qml");
+ // Check that the view only has one row loaded so far (the root of the tree)
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 1);
+
+ // Expand the root
+ treeView->toggleExpanded(0);
+ WAIT_UNTIL_POLISHED;
+ // We now expect 5 rows, the root pluss it's 4 children
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 5);
+
+ treeView->toggleExpanded(0);
+ WAIT_UNTIL_POLISHED;
+ // Check that the view only has one row loaded again (the root of the tree)
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 1);
+}
+
+void tst_qquicktreeview::expandAndCollapseChildren()
+{
+ // Check that we can expand and collapse children, and that
+ // the tree ends up with the expected rows
+ LOAD_TREEVIEW("normaltreeview.qml");
+
+ const int childCount = 4;
+
+ // Expand the last child of a parent recursively four times
+ for (int level = 0; level < 4; ++level) {
+ const int nodeToExpand = level * childCount;
+ const int firstChildRow = nodeToExpand + 1; // (+ 1 for the root)
+ const int lastChildRow = firstChildRow + 4;
+ treeView->expand(nodeToExpand);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(treeView->rows(), lastChildRow);
+
+ auto childItem1 = treeViewPrivate->loadedTableItem(QPoint(0, firstChildRow))->item;
+ QCOMPARE(childItem1->property("text").toString(), "0, 0");
+ auto childItem2 = treeViewPrivate->loadedTableItem(QPoint(0, firstChildRow + 1))->item;
+ QCOMPARE(childItem2->property("text").toString(), "1, 0");
+ auto childItem3 = treeViewPrivate->loadedTableItem(QPoint(0, firstChildRow + 2))->item;
+ QCOMPARE(childItem3->property("text").toString(), "2, 0");
+ }
+
+ // Collapse down from level 2 (deliberatly not mirroring the expansion by
+ // instead collapsing both level 3 and 4 in one go)
+ for (int level = 2; level > 0; --level) {
+ const int nodeToCollapse = level * childCount;
+ const int firstChildRow = nodeToCollapse - childCount + 1;
+ const int lastChildRow = nodeToCollapse + 1; // (+ 1 for the root)
+
+ treeView->collapse(nodeToCollapse);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(treeView->rows(), lastChildRow);
+
+ auto childItem1 = treeViewPrivate->loadedTableItem(QPoint(0, firstChildRow))->item;
+ QCOMPARE(childItem1->property("text").toString(), "0, 0");
+ auto childItem2 = treeViewPrivate->loadedTableItem(QPoint(0, firstChildRow + 1))->item;
+ QCOMPARE(childItem2->property("text").toString(), "1, 0");
+ auto childItem3 = treeViewPrivate->loadedTableItem(QPoint(0, firstChildRow + 2))->item;
+ QCOMPARE(childItem3->property("text").toString(), "2, 0");
+ }
+
+ // Collapse the root
+ treeView->collapse(0);
+ WAIT_UNTIL_POLISHED;
+ QCOMPARE(treeView->rows(), 1);
+}
+
+void tst_qquicktreeview::requiredPropertiesRoot()
+{
+ LOAD_TREEVIEW("normaltreeview.qml");
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 1);
+
+ const auto rootFxItem = treeViewPrivate->loadedTableItem(QPoint(0, 0));
+ QVERIFY(rootFxItem);
+ const auto rootItem = rootFxItem->item;
+ QVERIFY(rootItem);
+
+ const auto context = qmlContext(rootItem.data());
+ const auto viewProp = context->contextProperty("treeView").value<QQuickTreeView *>();
+ const auto isTreeNode = context->contextProperty("isTreeNode").toBool();
+ const auto expanded = context->contextProperty("expanded").toBool();
+ const auto hasChildren = context->contextProperty("hasChildren").toBool();
+ const auto depth = context->contextProperty("depth").toInt();
+
+ QCOMPARE(viewProp, treeView);
+ QCOMPARE(isTreeNode, true);
+ QCOMPARE(expanded, false);
+ QCOMPARE(hasChildren, true);
+ QCOMPARE(depth, 0);
+
+ treeView->expand(0);
+ WAIT_UNTIL_POLISHED;
+
+ const auto isExpandedAfterExpand = context->contextProperty("expanded").toBool();
+ QCOMPARE(isExpandedAfterExpand, true);
+}
+
+void tst_qquicktreeview::requiredPropertiesChildren()
+{
+ LOAD_TREEVIEW("normaltreeview.qml");
+ treeView->expand(0);
+ WAIT_UNTIL_POLISHED;
+ // We now expect 5 rows, the root pluss it's 4 children
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 5);
+
+ // The last child has it's own children, so expand that one as well
+ const auto rootIndex = model->index(0, 0);
+ const int childCount = model->rowCount(rootIndex);
+ QCOMPARE(childCount, 4);
+ const auto lastChildIndex = model->index(childCount - 1, 0, rootIndex);
+ QVERIFY(lastChildIndex.isValid());
+ QCOMPARE(model->hasChildren(lastChildIndex), true);
+
+ treeView->expand(4);
+ WAIT_UNTIL_POLISHED;
+ // We now expect root + 4 children + 4 children = 9 rows
+ const int rowCount = treeViewPrivate->loadedRows.count();
+ QCOMPARE(rowCount, 9);
+
+ // Go through all rows in the view, except the root
+ for (int row = 1; row < rowCount; ++row) {
+ const auto childFxItem = treeViewPrivate->loadedTableItem(QPoint(0, row));
+ QVERIFY(childFxItem);
+ const auto childItem = childFxItem->item;
+ QVERIFY(childItem);
+
+ const auto context = qmlContext(childItem.data());
+ const auto viewProp = context->contextProperty("treeView").value<QQuickTreeView *>();
+ const auto isTreeNode = context->contextProperty("isTreeNode").toBool();
+ const auto expanded = context->contextProperty("expanded").toBool();
+ const auto hasChildren = context->contextProperty("hasChildren").toBool();
+ const auto depth = context->contextProperty("depth").toInt();
+
+ QCOMPARE(viewProp, treeView);
+ QCOMPARE(isTreeNode, true);
+ QCOMPARE(expanded, row == 4);
+ QCOMPARE(hasChildren, row == 4 || row == 8);
+ QCOMPARE(depth, row <= 4 ? 1 : 2);
+ }
+}
+
+void tst_qquicktreeview::emptyModel()
+{
+ // Check that you can assign an empty model, and that
+ // calling functions will work as expected (and at least not crash)
+ LOAD_TREEVIEW("normaltreeview.qml");
+ treeView->setModel(QVariant());
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(treeViewPrivate->loadedItems.count(), 0);
+ QCOMPARE(treeView->rows(), 0);
+ QCOMPARE(treeView->columns(), 0);
+
+ // Check that we don't crash:
+ treeView->expand(10);
+ treeView->collapse(5);
+
+ QCOMPARE(treeView->depth(0), -1);
+ QCOMPARE(treeView->isExpanded(0), false);
+ QVERIFY(!treeView->modelIndex(10, 10).isValid());
+ QCOMPARE(treeView->rowAtIndex(QModelIndex()), -1);
+ QCOMPARE(treeView->columnAtIndex(QModelIndex()), -1);
+}
+
+void tst_qquicktreeview::updatedModifiedModel()
+{
+ // Check that if we change the data in the model, the
+ // delegate items get updated.
+ LOAD_TREEVIEW("normaltreeview.qml");
+ treeView->expand(0);
+ WAIT_UNTIL_POLISHED;
+ // We now expect 5 rows, the root plus it's 4 children
+ QCOMPARE(treeViewPrivate->loadedRows.count(), 5);
+
+ auto rootFxItem = treeViewPrivate->loadedTableItem(QPoint(0, 0));
+ QVERIFY(rootFxItem);
+ auto rootItem = rootFxItem->item;
+ QVERIFY(rootItem);
+ QCOMPARE(rootItem->property("text"), "0, 0");
+ model->setData(model->index(0, 0), QVariant(QString("Changed")));
+ QCOMPARE(rootItem->property("text"), "Changed");
+
+ auto rootFxItemCol1 = treeViewPrivate->loadedTableItem(QPoint(1, 2));
+ QVERIFY(rootFxItemCol1);
+ auto rootItemCol1 = rootFxItemCol1->item;
+ QVERIFY(rootItemCol1);
+ QCOMPARE(rootItemCol1->property("text"), "1, 1");
+ model->setData(model->index(1, 1, model->index(0,0)), QVariant(QString("Changed")));
+ QCOMPARE(rootItemCol1->property("text"), "Changed");
+}
+
+void tst_qquicktreeview::insertRows()
+{
+ // Check that if we add new rows to the model, TreeView gets updated
+ // to contain the new expected number of rows (flattened to a list)
+ LOAD_TREEVIEW("normaltreeview.qml");
+ treeView->expand(0);
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(treeView->rows(), 5);
+
+ const QModelIndex rootNode = model->index(0, 0, QModelIndex());
+ model->insertRows(0, 2, rootNode);
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(treeView->rows(), 7);
+ auto childItem1 = treeViewPrivate->loadedTableItem(QPoint(0, 1))->item;
+ QCOMPARE(childItem1->property("text").toString(), "0, 0 (inserted)");
+ auto childItem2 = treeViewPrivate->loadedTableItem(QPoint(0, 2))->item;
+ QCOMPARE(childItem2->property("text").toString(), "1, 0 (inserted)");
+ auto childItem3 = treeViewPrivate->loadedTableItem(QPoint(0, 3))->item;
+ QCOMPARE(childItem3->property("text").toString(), "0, 0");
+
+ const QModelIndex indexOfInsertedChild = model->index(1, 0, rootNode);
+ model->insertRows(0, 2, indexOfInsertedChild);
+ treeView->expand(2);
+ WAIT_UNTIL_POLISHED;
+
+ QCOMPARE(treeView->rows(), 9);
+}
+
+QTEST_MAIN(tst_qquicktreeview)
+
+#include "tst_qquicktreeview.moc"
diff --git a/tests/manual/treeview/sidebyside/CMakeLists.txt b/tests/manual/treeview/sidebyside/CMakeLists.txt
new file mode 100644
index 0000000000..874cff03bc
--- /dev/null
+++ b/tests/manual/treeview/sidebyside/CMakeLists.txt
@@ -0,0 +1,47 @@
+# Generated from auto.pro.
+
+if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(treeview_sidebyside LANGUAGES C CXX ASM)
+ find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
+endif()
+
+#####################################################################
+## treeview_sidebyside manual test
+#####################################################################
+
+qt_internal_add_manual_test(treeview_sidebyside
+ GUI
+ SOURCES
+ testmodel.cpp testmodel.h
+ main.cpp
+ PUBLIC_LIBRARIES
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickPrivate
+ Qt::QuickTest
+)
+
+qt_internal_extend_target(treeview_sidebyside CONDITION TARGET Qt::Widgets
+ PUBLIC_LIBRARIES
+ Qt::Widgets
+)
+
+qt6_add_qml_module(treeview_sidebyside
+ URI TestModel
+ VERSION 1.0
+)
+
+# Resources:
+set(qmake_immediate_resource_files
+ "data"
+)
+
+qt_internal_add_resource(treeview_sidebyside "qmake_immediate"
+ PREFIX
+ "/"
+ FILES
+ ${qmake_immediate_resource_files}
+)
+
diff --git a/tests/manual/treeview/sidebyside/data/treeview.qml b/tests/manual/treeview/sidebyside/data/treeview.qml
new file mode 100644
index 0000000000..b207f50960
--- /dev/null
+++ b/tests/manual/treeview/sidebyside/data/treeview.qml
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import TestModel
+
+ApplicationWindow {
+ width: 800
+ height: 600
+ visible: true
+
+ property alias treeView: treeView
+
+ Row {
+ id: topRow
+ width: parent.width
+ height: 50
+ Button {
+ text: "insert row"
+ onClicked: {
+ let index = treeView.modelIndex(1, 0)
+ treeView.model.insertRows(index.row, 1, index.parent);
+ }
+ }
+ Button {
+ text: "remove row"
+ onClicked: {
+ let index = treeView.modelIndex(1, 0)
+ treeView.model.removeRows(index.row, 1, index.parent);
+ }
+ }
+ }
+
+ TreeView {
+ id: treeView
+ width: parent.width
+ anchors.top: topRow.bottom
+ anchors.bottom: parent.bottom
+ model: TestModel {}
+ clip: true
+
+ delegate: Item {
+ id: root
+
+ implicitWidth: padding + label.x + label.implicitWidth + padding
+ implicitHeight: label.implicitHeight * 1.5
+
+ readonly property real indent: 20
+ readonly property real padding: 5
+
+ // Assigned to by TreeView:
+ required property TreeView treeView
+ required property bool isTreeNode
+ required property bool expanded
+ required property int hasChildren
+ required property int depth
+
+ TapHandler {
+ onTapped: treeView.toggleExpanded(row)
+ }
+
+ Text {
+ id: indicator
+ visible: root.isTreeNode && root.hasChildren
+ x: padding + (root.depth * root.indent)
+ text: root.expanded ? "▼" : "▶"
+ }
+
+ Text {
+ id: label
+ x: padding + (root.isTreeNode ? (root.depth + 1) * root.indent : 0)
+ width: root.width - root.padding - x
+ clip: true
+ text: model.display
+ }
+ }
+ }
+}
diff --git a/tests/manual/treeview/sidebyside/main.cpp b/tests/manual/treeview/sidebyside/main.cpp
new file mode 100644
index 0000000000..9829f61a80
--- /dev/null
+++ b/tests/manual/treeview/sidebyside/main.cpp
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQuick/qquickview.h>
+#include <QtQuick/qquickwindow.h>
+#include <QtQml/qqmlapplicationengine.h>
+#include <QtQml/qqmlcontext.h>
+
+#include <QtQuick/private/qquicktreeview_p.h>
+#include <QtQuick/private/qquicktreeview_p_p.h>
+
+#include <QtGui/qguiapplication.h>
+
+#ifdef QT_WIDGETS_LIB
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qtreeview.h>
+#endif
+
+#include "testmodel.h"
+
+int main(int c, char **args) {
+#ifdef QT_WIDGETS_LIB
+ QApplication app(c, args);
+#else
+ QGuiApplication app(c, args);
+#endif
+
+ QQmlApplicationEngine engine("qrc:data/treeview.qml");
+ QQuickWindow *window = static_cast<QQuickWindow *>(engine.rootObjects().at(0));
+ auto treeView = window->property("treeView").value<QQuickTreeView *>();
+ treeView->expand(0);
+
+#ifdef QT_WIDGETS_LIB
+ // Show widget version of the treeview as well
+ QTreeView treeViewWidget;
+ treeViewWidget.setModel(treeView->model().value<TestModel *>());
+ treeViewWidget.setExpanded(treeViewWidget.model()->index(0, 0), true);
+ treeViewWidget.resize(640, 480);
+ treeViewWidget.show();
+ treeViewWidget.raise();
+#endif
+
+ return app.exec();
+}
diff --git a/tests/manual/treeview/sidebyside/testmodel.cpp b/tests/manual/treeview/sidebyside/testmodel.cpp
new file mode 100644
index 0000000000..8afa7db4f4
--- /dev/null
+++ b/tests/manual/treeview/sidebyside/testmodel.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "testmodel.h"
+
+TreeItem::TreeItem(TreeItem *parent)
+ : m_parentItem(parent)
+{}
+
+TreeItem::TreeItem(int rootRow)
+ : m_parentItem(nullptr)
+ , rootRow(rootRow)
+{}
+
+TreeItem::~TreeItem()
+{
+ qDeleteAll(m_childItems);
+}
+
+int TreeItem::row() const
+{
+ if (!m_parentItem) {
+ Q_ASSERT(rootRow != -1);
+ return rootRow;
+ }
+ return m_parentItem->m_childItems.indexOf(const_cast<TreeItem *>(this));
+}
+
+TestModel::TestModel(QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ m_rootItems.append(new TreeItem(0));
+ m_rootItems.append(new TreeItem(1));
+ m_rootItems.append(new TreeItem(2));
+
+ for (int row = 0; row < m_rootItems.count(); ++row) {
+ const QString branchTag = QString::number(row);
+ for (int col = 0; col < m_columnCount; ++col)
+ m_rootItems[row]->m_entries << QVariant(QString("r:%1, c:%2, d:0, b:%3").arg(row).arg(col).arg(branchTag));
+ createTreeRecursive(m_rootItems[row], 4, 1, 5, branchTag);
+ }
+}
+
+void TestModel::createTreeRecursive(TreeItem *parentItem, int childCount, int currentDepth, int maxDepth, const QString &branchTag)
+{
+ for (int row = 0; row < childCount; ++row) {
+ auto childItem = new TreeItem(parentItem);
+
+ if (currentDepth < maxDepth && row == 0)
+ createTreeRecursive(childItem, childCount, currentDepth + 1, maxDepth, branchTag);
+
+ for (int col = 0; col < m_columnCount; ++col)
+ childItem->m_entries << QVariant(QString("r:%1, c:%2, d:%3, b:%4").arg(row).arg(col).arg(currentDepth).arg(branchTag));
+ parentItem->m_childItems.append(childItem);
+ }
+}
+
+TreeItem *TestModel::treeItem(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return nullptr;
+ return static_cast<TreeItem *>(index.internalPointer());
+}
+
+int TestModel::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return m_rootItems.count();
+ return treeItem(parent)->m_childItems.count();
+}
+
+int TestModel::columnCount(const QModelIndex &) const
+{
+ return m_columnCount;
+}
+
+QVariant TestModel::data(const QModelIndex &index, int role) const
+{
+ switch (role) {
+ case Qt::SizeHintRole:
+ return QSize(100, 20);
+ case Qt::DisplayRole: {
+ if (!index.isValid())
+ return QVariant("invalid index");
+ TreeItem *item = treeItem(index);
+ return item->m_entries.at(index.column());
+ break; }
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+bool TestModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ Q_UNUSED(role)
+ if (!index.isValid())
+ return false;
+ TreeItem *item = treeItem(index);
+ Q_ASSERT(item);
+ item->m_entries[index.column()] = value;
+ emit dataChanged(index, index);
+ return true;
+}
+
+QModelIndex TestModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+ if (!parent.isValid())
+ return createIndex(row, column, m_rootItems[row]);
+ return createIndex(row, column, treeItem(parent)->m_childItems.at(row));
+}
+
+QModelIndex TestModel::parent(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ TreeItem *item = treeItem(index);
+ TreeItem *parentItem = item->m_parentItem;
+ if (!parentItem)
+ return QModelIndex();
+
+ return createIndex(parentItem->row(), 0, parentItem);
+}
+
+bool TestModel::insertRows(int position, int count, const QModelIndex &parent)
+{
+ if (!parent.isValid()) {
+ qWarning() << "Cannot insert rows on an invalid parent!";
+ return false;
+ }
+
+ beginInsertRows(parent, position, position + count - 1);
+ TreeItem *parentItem = treeItem(parent);
+
+ for (int row = 0; row < count; ++row) {
+ auto newChildItem = new TreeItem(parentItem);
+ for (int col = 0; col < m_columnCount; ++col)
+ newChildItem->m_entries << QVariant(QString("(inserted at %1, %2)").arg(position + row).arg(col));
+ parentItem->m_childItems.insert(position + row, newChildItem);
+ }
+
+ endInsertRows();
+ return true;
+}
+
+bool TestModel::removeRows(int position, int count, const QModelIndex &parent)
+{
+ if (!parent.isValid()) {
+ qWarning() << "Cannot remove rows on an invalid parent!";
+ return false;
+ }
+
+ beginRemoveRows(parent, position, position + count - 1);
+
+ TreeItem *parentItem = treeItem(parent);
+ parentItem->m_childItems.remove(position, count);
+
+ endRemoveRows();
+ return true;
+}
+
diff --git a/tests/manual/treeview/sidebyside/testmodel.h b/tests/manual/treeview/sidebyside/testmodel.h
new file mode 100644
index 0000000000..a0af646628
--- /dev/null
+++ b/tests/manual/treeview/sidebyside/testmodel.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef TESTMODEL_H
+#define TESTMODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtQuick/qquickview.h>
+
+class TreeItem
+{
+public:
+ explicit TreeItem(TreeItem *parent);
+ explicit TreeItem(int rootRow);
+ ~TreeItem();
+
+ int row() const;
+ QVector<TreeItem *> m_childItems;
+ TreeItem *m_parentItem;
+ QVector<QVariant> m_entries;
+ int rootRow = -1;
+};
+
+// ########################################################
+
+class TestModel : public QAbstractItemModel
+{
+ Q_OBJECT
+ QML_ELEMENT
+
+public:
+ explicit TestModel(QObject *parent = nullptr);
+
+ void createTreeRecursive(TreeItem *parentItem, int childCount, int currentDepth, int maxDepth, const QString &branchTag);
+ TreeItem *treeItem(const QModelIndex &index) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex & = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+
+ Q_INVOKABLE bool insertRows(int position, int count, const QModelIndex &parent) override;
+ Q_INVOKABLE bool removeRows(int position, int count, const QModelIndex &parent) override;
+
+private:
+ QVector<TreeItem *> m_rootItems;
+ int m_columnCount = 2;
+};
+
+#endif // TESTMODEL_H