qmlmodels: add qqmltreemodeltotablemodel

Add a proxy model to qmlmodels that takes a tree model
and converts it to a flat table model. This model is
used by TreeView to show a tree model inside a
TableView. It is mostly a raw copy of the model used
by TreeView in Controls 1 (and later by TreeView in
Marketplace), but with some modifications to target
TableView (with multiple columns) instead of
ListView.

Change-Id: I079937f35ae36659a6ad43375ac3d1d5840155d6
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
This commit is contained in:
Richard Moe Gustavsen 2021-11-19 16:56:43 +01:00
parent 1eb074a39d
commit fb954b07f2
8 changed files with 1619 additions and 0 deletions

View File

@ -39,6 +39,7 @@ qt_internal_extend_target(QmlModels CONDITION QT_FEATURE_qml_object_model
qt_internal_extend_target(QmlModels CONDITION QT_FEATURE_qml_table_model
SOURCES
qqmltableinstancemodel.cpp qqmltableinstancemodel_p.h
qqmltreemodeltotablemodel.cpp qqmltreemodeltotablemodel_p_p.h
)
qt_internal_extend_target(QmlModels CONDITION QT_FEATURE_qml_list_model

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,209 @@
/****************************************************************************
**
** 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 QQmlTreeModelToTableModel_H
#define QQmlTreeModelToTableModel_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 "qtqmlmodelsglobal_p.h"
#include <QtCore/qset.h>
#include <QtCore/qpointer.h>
#include <QtCore/qabstractitemmodel.h>
#include <QtCore/qitemselectionmodel.h>
QT_BEGIN_NAMESPACE
class QAbstractItemModel;
class Q_QMLMODELS_PRIVATE_EXPORT QQmlTreeModelToTableModel : public QAbstractItemModel
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(QModelIndex rootIndex READ rootIndex WRITE setRootIndex RESET resetRootIndex NOTIFY rootIndexChanged)
struct TreeItem;
public:
explicit QQmlTreeModelToTableModel(QObject *parent = nullptr);
QAbstractItemModel *model() const;
QModelIndex rootIndex() const;
void setRootIndex(const QModelIndex &idx);
void resetRootIndex();
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
enum {
DepthRole = Qt::UserRole - 5,
ExpandedRole,
HasChildrenRole,
HasSiblingRole,
ModelIndexRole
};
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
void clearModelData();
bool isVisible(const QModelIndex &index);
bool childrenVisible(const QModelIndex &index);
QModelIndex mapToModel(const QModelIndex &index) const;
QModelIndex mapFromModel(const QModelIndex &index) const;
QModelIndex mapToModel(int row) const;
Q_INVOKABLE QItemSelection selectionForRowRange(const QModelIndex &fromIndex, const QModelIndex &toIndex) const;
void showModelTopLevelItems(bool doInsertRows = true);
void showModelChildItems(const TreeItem &parent, int start, int end, bool doInsertRows = true, bool doExpandPendingRows = true);
int itemIndex(const QModelIndex &index) const;
void expandPendingRows(bool doInsertRows = true);
int lastChildIndex(const QModelIndex &index);
void removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows = true);
void dump() const;
bool testConsistency(bool dumpOnFail = false) const;
using QAbstractItemModel::hasChildren;
signals:
void modelChanged(QAbstractItemModel *model);
void rootIndexChanged();
void expanded(const QModelIndex &index);
void collapsed(const QModelIndex &index);
public slots:
void expand(const QModelIndex &);
void collapse(const QModelIndex &);
void setModel(QAbstractItemModel *model);
bool isExpanded(const QModelIndex &) const;
bool isExpanded(int row) const;
bool hasChildren(int row) const;
bool hasSiblings(int row) const;
int depthAtRow(int row) const;
void expandRow(int n);
void collapseRow(int n);
private slots:
void modelHasBeenDestroyed();
void modelHasBeenReset();
void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
void modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void modelLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void modelRowsAboutToBeInserted(const QModelIndex & parent, int start, int end);
void modelRowsAboutToBeMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow);
void modelRowsAboutToBeRemoved(const QModelIndex & parent, int start, int end);
void modelRowsInserted(const QModelIndex & parent, int start, int end);
void modelRowsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow);
void modelRowsRemoved(const QModelIndex & parent, int start, int end);
private:
struct TreeItem {
QPersistentModelIndex index;
int depth;
bool expanded;
explicit TreeItem(const QModelIndex &idx = QModelIndex(), int d = 0, int e = false)
: index(idx), depth(d), expanded(e)
{ }
inline bool operator== (const TreeItem &other) const
{
return this->index == other.index;
}
};
struct DataChangedParams {
QModelIndex topLeft;
QModelIndex bottomRight;
QVector<int> roles;
};
struct SignalFreezer {
SignalFreezer(QQmlTreeModelToTableModel *parent) : m_parent(parent) {
m_parent->enableSignalAggregation();
}
~SignalFreezer() { m_parent->disableSignalAggregation(); }
private:
QQmlTreeModelToTableModel *m_parent;
};
void enableSignalAggregation();
void disableSignalAggregation();
bool isAggregatingSignals() const { return m_signalAggregatorStack > 0; }
void queueDataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector<int> &roles);
void emitQueuedSignals();
QPointer<QAbstractItemModel> m_model = nullptr;
QPersistentModelIndex m_rootIndex;
QList<TreeItem> m_items;
QSet<QPersistentModelIndex> m_expandedItems;
QList<TreeItem> m_itemsToExpand;
mutable int m_lastItemIndex = 0;
bool m_visibleRowsMoved = false;
int m_signalAggregatorStack = 0;
QVector<DataChangedParams> m_queuedDataChanged;
int m_column = 0;
};
QT_END_NAMESPACE
#endif // QQmlTreeModelToTableModel_H

View File

@ -109,6 +109,7 @@ if(QT_FEATURE_private_tests)
add_subdirectory(qqmlimport)
add_subdirectory(qqmlobjectmodel)
add_subdirectory(qqmltablemodel)
add_subdirectory(qqmltreemodeltotablemodel)
add_subdirectory(qv4assembler)
add_subdirectory(qv4mm)
add_subdirectory(qv4identifiertable)

View File

@ -0,0 +1,36 @@
#####################################################################
## tst_qqmltreemodeltotablemodel 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_qqmltreemodeltotablemodel
SOURCES
tst_qqmltreemodeltotablemodel.cpp
testmodel.h testmodel.cpp
PUBLIC_LIBRARIES
Qt::Gui
Qt::Qml
Qt::QmlPrivate
Qt::Quick
Qt::QuickPrivate
Qt::QuickTestUtilsPrivate
TESTDATA ${test_data}
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qqmltreemodeltotablemodel CONDITION ANDROID OR IOS
DEFINES
QT_QMLTEST_DATADIR=\\\":/data\\\"
)
qt_internal_extend_target(tst_qqmltreemodeltotablemodel CONDITION NOT ANDROID AND NOT IOS
DEFINES
QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
)

View File

@ -0,0 +1,167 @@
/****************************************************************************
**
** 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
if (parent.column() == 0)
return treeItem(parent)->m_childItems.count();
return 0;
}
int TestModel::columnCount(const QModelIndex &parent) const
{
return parent.column() == 0 ? m_columnCount : 0;
}
QVariant TestModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role)
if (!index.isValid())
return QVariant();
TreeItem *item = treeItem(TestModel::index(index.row(), 0, index.parent()));
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(TestModel::index(index.row(), 0, index.parent()));
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 (column != 0)
return createIndex(row, column);
if (!parent.isValid())
return createIndex(0, 0, m_rootItem.data());
return createIndex(row, 0, treeItem(parent)->m_childItems.at(row));
}
QModelIndex TestModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
if (index.column() != 0)
return QModelIndex();
TreeItem *parentItem = treeItem(index)->m_parentItem;
if (!parentItem) {
qWarning() << "failed to resolve parent item for:" << index;
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;
}

View File

@ -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

View File

@ -0,0 +1,71 @@
/****************************************************************************
**
** 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/qtest.h>
#include <QAbstractItemModelTester>
#include <QtQmlModels/private/qqmltreemodeltotablemodel_p_p.h>
#include "testmodel.h"
class tst_QQmlTreeModelToTableModel : public QObject {
Q_OBJECT
private slots:
void testTestModel();
void testTreeModelToTableModel();
};
void tst_QQmlTreeModelToTableModel::testTestModel()
{
TestModel treeModel;
QAbstractItemModelTester tester(&treeModel, QAbstractItemModelTester::FailureReportingMode::QtTest);
}
void tst_QQmlTreeModelToTableModel::testTreeModelToTableModel()
{
QQmlTreeModelToTableModel model;
TestModel treeModel;
model.setModel(&treeModel);
QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
}
QTEST_MAIN(tst_QQmlTreeModelToTableModel)
#include "tst_qqmltreemodeltotablemodel.moc"