diff options
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 |