summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>2015-01-07 12:07:28 +0100
committerGabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>2015-02-05 19:52:01 +0100
commit6548969fe00eefc6b0029ce6eb8fc28d87820af6 (patch)
treee3b8784355125484211b25171f8b333cd6c00fc8
parentaf24d3754d807cfec1ea480eded2f32de5279f0e (diff)
Introducting TreeView
The TreeView, as currently implemented, extends the TableView by adding support for hierarchical models. In the broad sense, it remains a list view with columns, like TableView. The main architecture is based on TreeModelAdaptor, that wraps the hierarchical model. It keeps track of which items are expanded or collapsed, and also relays model changes to the view. (TreeModelAdaptor is a private type and should be considered as an implementation detail.) The TreeView only supports QAbstractItemModels for the time being, and, just like TableView, relies on roles to pass the data to the view. This also means that model columns are not supported. Selection is supported by ItemSelectionModel which exposes part of the API of QItemSelectionModel. For this, support has been added for QModelIndex and related classes. This requires importing QtQml.Models 2.2 should an actual usage of the TreeView use selection. In the same way, TreeViewStyle currently extends TableViewStyle with the relevant features, like branch indicator. [ChangeLog][QtQuick.Controls] Introducing TreeView Co-Authored-With: Caroline Chao <caroline.chao@theqtcompany.com> Change-Id: Id3dba240a732744571e4a646b7b98678ab522da6
-rw-r--r--examples/quick/controls/filesystembrowser/deployment.pri27
-rw-r--r--examples/quick/controls/filesystembrowser/filesystembrowser.pro13
-rw-r--r--examples/quick/controls/filesystembrowser/main.cpp58
-rw-r--r--examples/quick/controls/filesystembrowser/main.qml115
-rw-r--r--examples/quick/controls/filesystembrowser/qml.qrc5
-rw-r--r--src/controls/Private/BasicTableView.qml32
-rw-r--r--src/controls/Private/private.pri6
-rw-r--r--src/controls/Private/qquickstyleitem.cpp25
-rw-r--r--src/controls/Private/qquickstyleitem_p.h1
-rw-r--r--src/controls/Private/qquicktreemodeladaptor.cpp799
-rw-r--r--src/controls/Private/qquicktreemodeladaptor_p.h158
-rw-r--r--src/controls/Styles/Base/BasicTableViewStyle.qml150
-rw-r--r--src/controls/Styles/Base/TableViewStyle.qml89
-rw-r--r--src/controls/Styles/Base/TreeViewStyle.qml95
-rw-r--r--src/controls/Styles/Desktop/TableViewStyle.qml7
-rw-r--r--src/controls/Styles/Desktop/TreeViewStyle.qml67
-rw-r--r--src/controls/Styles/qmldir1
-rw-r--r--src/controls/Styles/styles.pri3
-rw-r--r--src/controls/TableView.qml52
-rw-r--r--src/controls/TableViewColumn.qml8
-rw-r--r--src/controls/TreeView.qml500
-rw-r--r--src/controls/controls.pro1
-rw-r--r--src/controls/doc/images/qtquickcontrols-example-filesystembrowser.pngbin0 -> 39132 bytes
-rw-r--r--src/controls/doc/images/treeview.pngbin0 -> 14784 bytes
-rw-r--r--src/controls/doc/src/qtquickcontrols-examples.qdoc14
-rw-r--r--src/controls/plugin.cpp9
-rw-r--r--tests/auto/auto.pro2
-rw-r--r--tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro12
-rw-r--r--tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp1135
-rw-r--r--tests/auto/shared/qtestmodel.h319
30 files changed, 3578 insertions, 125 deletions
diff --git a/examples/quick/controls/filesystembrowser/deployment.pri b/examples/quick/controls/filesystembrowser/deployment.pri
new file mode 100644
index 000000000..5441b63dc
--- /dev/null
+++ b/examples/quick/controls/filesystembrowser/deployment.pri
@@ -0,0 +1,27 @@
+android-no-sdk {
+ target.path = /data/user/qt
+ export(target.path)
+ INSTALLS += target
+} else:android {
+ x86 {
+ target.path = /libs/x86
+ } else: armeabi-v7a {
+ target.path = /libs/armeabi-v7a
+ } else {
+ target.path = /libs/armeabi
+ }
+ export(target.path)
+ INSTALLS += target
+} else:unix {
+ isEmpty(target.path) {
+ qnx {
+ target.path = /tmp/$${TARGET}/bin
+ } else {
+ target.path = /opt/$${TARGET}/bin
+ }
+ export(target.path)
+ }
+ INSTALLS += target
+}
+
+export(INSTALLS)
diff --git a/examples/quick/controls/filesystembrowser/filesystembrowser.pro b/examples/quick/controls/filesystembrowser/filesystembrowser.pro
new file mode 100644
index 000000000..1cdc565af
--- /dev/null
+++ b/examples/quick/controls/filesystembrowser/filesystembrowser.pro
@@ -0,0 +1,13 @@
+TEMPLATE = app
+
+QT += qml quick widgets
+
+SOURCES += main.cpp
+
+RESOURCES += qml.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
+
+# Default rules for deployment.
+include(deployment.pri)
diff --git a/examples/quick/controls/filesystembrowser/main.cpp b/examples/quick/controls/filesystembrowser/main.cpp
new file mode 100644
index 000000000..1941d129e
--- /dev/null
+++ b/examples/quick/controls/filesystembrowser/main.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd. nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QApplication>
+#include <QQmlApplicationEngine>
+#include <QtQml>
+#include <QFileSystemModel>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ QFileSystemModel *fsm = new QFileSystemModel(&engine);
+ fsm->setRootPath(QDir::homePath());
+ fsm->setResolveSymlinks(true);
+ engine.rootContext()->setContextProperty("fileSystemModel", fsm);
+ engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
+
+ return app.exec();
+}
diff --git a/examples/quick/controls/filesystembrowser/main.qml b/examples/quick/controls/filesystembrowser/main.qml
new file mode 100644
index 000000000..4874f7d3e
--- /dev/null
+++ b/examples/quick/controls/filesystembrowser/main.qml
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd. nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import QtQml.Models 2.2
+
+ApplicationWindow {
+ visible: true
+ width: 640
+ height: 480
+ title: qsTr("File System")
+
+ menuBar: MenuBar {
+ Menu {
+ title: qsTr("File")
+ MenuItem {
+ text: qsTr("Exit")
+ onTriggered: Qt.quit();
+ }
+ }
+ }
+
+ Row {
+ id: row
+ anchors.top: parent.top
+ anchors.topMargin: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ ExclusiveGroup {
+ id: eg
+ }
+
+ Repeater {
+ model: [ "None", "Single", "Extended", "Multi", "Contig."]
+ Button {
+ text: modelData
+ exclusiveGroup: eg
+ checkable: true
+ checked: index === 1
+ onClicked: view.selectionMode = index
+ }
+ }
+ }
+
+ TreeView {
+ id: view
+ anchors.fill: parent
+ anchors.margins: 2 * 12 + row.height
+ model: fileSystemModel
+
+ selection: ItemSelectionModel {
+ id: s
+ model: fileSystemModel
+ onSelectionChanged: {
+ console.log("selected", selected)
+ console.log("deselected", deselected)
+ console.log("selection", s.selection())
+ }
+ onCurrentChanged: console.log("current", current)
+ }
+
+ onCurrentIndexChanged: console.log("current index", currentIndex)
+
+ TableViewColumn {
+ title: "Name"
+ role: "fileName"
+ }
+
+// TableViewColumn {
+// title: "Permissions"
+// role: "filePermissions"
+// }
+
+ onClicked: console.log("clicked", index)
+ onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
+ }
+}
diff --git a/examples/quick/controls/filesystembrowser/qml.qrc b/examples/quick/controls/filesystembrowser/qml.qrc
new file mode 100644
index 000000000..5f6483ac3
--- /dev/null
+++ b/examples/quick/controls/filesystembrowser/qml.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>
diff --git a/src/controls/Private/BasicTableView.qml b/src/controls/Private/BasicTableView.qml
index cac673eb9..f5fa3238b 100644
--- a/src/controls/Private/BasicTableView.qml
+++ b/src/controls/Private/BasicTableView.qml
@@ -543,6 +543,7 @@ ScrollView {
property bool itemSelected: __mouseArea.selected(rowIndex)
property bool alternate: alternatingRowColors && rowIndex % 2 === 1
readonly property color itemTextColor: itemSelected ? __style.highlightedTextColor : __style.textColor
+ property Item branchDecoration: null
width: itemrow.width
height: rowstyle.height
@@ -608,6 +609,37 @@ ScrollView {
: modelData && modelData.hasOwnProperty(role)
? modelData[role] // QObjectList / QObject
: modelData != undefined ? modelData : "" // Models without role
+ readonly property int depth: itemModel && column === 0 && itemModel["_q_TreeView_ItemDepth"] || 0
+ readonly property bool hasChildren: itemModel && itemModel["_q_TreeView_HasChildren"] || false
+ readonly property bool hasSibling: itemModel && itemModel["_q_TreeView_HasSibling"] || false
+ readonly property bool isExpanded: itemModel && itemModel["_q_TreeView_ItemExpanded"] || false
+ }
+
+ readonly property int __itemIndentation: __style.__indentation * (styleData.depth + 1)
+
+ Binding {
+ target: item
+ property: "x"
+ value: __itemIndentation
+ }
+
+ Binding {
+ target: item
+ property: "width"
+ value: itemDelegateLoader.width - __itemIndentation
+ }
+
+ Loader {
+ id: branchDelegateLoader
+ active: rowitem.itemModel !== undefined
+ && index === 0
+ && itemDelegateLoader.width > __itemIndentation
+ && styleData.hasChildren
+ sourceComponent: __style ? __style.__branchDelegate : null
+ anchors.right: parent.item.left
+ anchors.verticalCenter: parent.verticalCenter
+ property QtObject styleData: itemDelegateLoader.styleData
+ onLoaded: rowitem.branchDecoration = item
}
}
}
diff --git a/src/controls/Private/private.pri b/src/controls/Private/private.pri
index 3d6a71624..7d23fc479 100644
--- a/src/controls/Private/private.pri
+++ b/src/controls/Private/private.pri
@@ -9,7 +9,8 @@ HEADERS += \
$$PWD/qquickwheelarea_p.h \
$$PWD/qquickabstractstyle_p.h \
$$PWD/qquickpadding_p.h \
- $$PWD/qquickcontrolsprivate_p.h
+ $$PWD/qquickcontrolsprivate_p.h \
+ $$PWD/qquicktreemodeladaptor_p.h
SOURCES += \
$$PWD/qquickcalendarmodel.cpp \
@@ -19,7 +20,8 @@ SOURCES += \
$$PWD/qquickrangeddate.cpp \
$$PWD/qquickcontrolsettings.cpp \
$$PWD/qquickwheelarea.cpp \
- $$PWD/qquickabstractstyle.cpp
+ $$PWD/qquickabstractstyle.cpp \
+ $$PWD/qquicktreemodeladaptor.cpp
!no_desktop {
diff --git a/src/controls/Private/qquickstyleitem.cpp b/src/controls/Private/qquickstyleitem.cpp
index fb49e6e3f..1e10176b2 100644
--- a/src/controls/Private/qquickstyleitem.cpp
+++ b/src/controls/Private/qquickstyleitem.cpp
@@ -367,6 +367,19 @@ void QQuickStyleItem::initStyleOption()
}
}
break;
+ case ItemBranchIndicator: {
+ if (!m_styleoption)
+ m_styleoption = new QStyleOption;
+
+ m_styleoption->state = QStyle::State_Item; // We don't want to fully support Win 95
+ if (m_properties.value("hasChildren").toBool())
+ m_styleoption->state |= QStyle::State_Children;
+ if (m_properties.value("hasSibling").toBool()) // Even this one could go away
+ m_styleoption->state |= QStyle::State_Sibling;
+ if (m_on)
+ m_styleoption->state |= QStyle::State_Open;
+ }
+ break;
case Header: {
if (!m_styleoption)
m_styleoption = new QStyleOptionHeader();
@@ -1220,6 +1233,8 @@ int QQuickStyleItem::pixelMetric(const QString &metric)
return qApp->style()->pixelMetric(QStyle::PM_SplitterWidth, 0 );
else if (metric == "scrollbarspacing")
return abs(qApp->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, 0 ));
+ else if (metric == "treeviewindentation")
+ return qApp->style()->pixelMetric(QStyle::PM_TreeViewIndentation, 0 );
return 0;
}
@@ -1306,6 +1321,8 @@ void QQuickStyleItem::setElementType(const QString &str)
} else {
m_itemType = (str == "item") ? Item : ItemRow;
}
+ } else if (str == "itembranchindicator") {
+ m_itemType = ItemBranchIndicator;
} else if (str == "groupbox") {
m_itemType = GroupBox;
} else if (str == "tab") {
@@ -1419,6 +1436,11 @@ QRectF QQuickStyleItem::subControlRect(const QString &subcontrolString)
subcontrol);
}
break;
+ case ItemBranchIndicator: {
+ QStyleOption opt;
+ opt.rect = QRect(0, 0, implicitWidth(), implicitHeight());
+ return qApp->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, 0);
+ }
default:
break;
}
@@ -1499,6 +1521,9 @@ void QQuickStyleItem::paint(QPainter *painter)
case Item:
qApp->style()->drawControl(QStyle::CE_ItemViewItem, m_styleoption, painter);
break;
+ case ItemBranchIndicator:
+ qApp->style()->drawPrimitive(QStyle::PE_IndicatorBranch, m_styleoption, painter);
+ break;
case Header:
qApp->style()->drawControl(QStyle::CE_Header, m_styleoption, painter);
break;
diff --git a/src/controls/Private/qquickstyleitem_p.h b/src/controls/Private/qquickstyleitem_p.h
index 10a4e5702..c52157fd5 100644
--- a/src/controls/Private/qquickstyleitem_p.h
+++ b/src/controls/Private/qquickstyleitem_p.h
@@ -119,6 +119,7 @@ public:
Header,
Item,
ItemRow,
+ ItemBranchIndicator,
Splitter,
Menu,
MenuItem,
diff --git a/src/controls/Private/qquicktreemodeladaptor.cpp b/src/controls/Private/qquicktreemodeladaptor.cpp
new file mode 100644
index 000000000..6ea2e4591
--- /dev/null
+++ b/src/controls/Private/qquicktreemodeladaptor.cpp
@@ -0,0 +1,799 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <math.h>
+#include "qquicktreemodeladaptor_p.h"
+#include <QtCore/qstack.h>
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+//#define QQUICKTREEMODELADAPTOR_DEBUG
+#ifndef QQUICKTREEMODELADAPTOR_DEBUG
+# undef qDebug
+# define qDebug QT_NO_QDEBUG_MACRO
+#elif !defined(QT_TESTLIB_LIB)
+# define ASSERT_CONSISTENCY() Q_ASSERT_X(testConsistency(true /* dumpOnFail */), Q_FUNC_INFO, "Consistency test failed")
+#endif
+#ifndef ASSERT_CONSISTENCY
+# define ASSERT_CONSISTENCY qt_noop
+#endif
+
+QQuickTreeModelAdaptor::QQuickTreeModelAdaptor(QObject *parent) :
+ QAbstractListModel(parent), m_model(0), m_lastItemIndex(0)
+{
+}
+
+QAbstractItemModel *QQuickTreeModelAdaptor::model() const
+{
+ return m_model;
+}
+
+void QQuickTreeModelAdaptor::setModel(QAbstractItemModel *arg)
+{
+ struct Cx {
+ const char *signal;
+ const char *slot;
+ };
+ static const Cx connections[] = {
+ { SIGNAL(modelReset()),
+ SLOT(modelHasBeenReset()) },
+ { SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
+ SLOT(modelDataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)) },
+
+ { SIGNAL(layoutAboutToBeChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
+ SLOT(modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)) },
+ { SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
+ SLOT(modelLayoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)) },
+
+ { SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)),
+ SLOT(modelRowsAboutToBeInserted(const QModelIndex &, int, int)) },
+ { SIGNAL(rowsInserted(const QModelIndex&, int, int)),
+ SLOT(modelRowsInserted(const QModelIndex&, int, int)) },
+ { SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
+ SLOT(modelRowsAboutToBeRemoved(const QModelIndex&, int, int)) },
+ { SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
+ SLOT(modelRowsRemoved(const QModelIndex&, int, int)) },
+ { SIGNAL(rowsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int)),
+ SLOT(modelRowsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int)) },
+ { SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)),
+ SLOT(modelRowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)) },
+ { 0, 0 }
+ };
+
+ if (m_model != arg) {
+ if (m_model) {
+ for (const Cx *c = &connections[0]; c->signal; c++)
+ disconnect(m_model, c->signal, this, c->slot);
+ }
+
+ clearModelData();
+ m_model = arg;
+
+ if (m_model) {
+ for (const Cx *c = &connections[0]; c->signal; c++)
+ connect(m_model, c->signal, this, c->slot);
+
+ showModelTopLevelItems();
+ }
+
+ emit modelChanged(arg);
+ }
+}
+
+void QQuickTreeModelAdaptor::clearModelData()
+{
+ beginResetModel();
+ m_items.clear();
+ m_expandedItems.clear();
+ endResetModel();
+}
+
+QHash<int, QByteArray> QQuickTreeModelAdaptor::roleNames() const
+{
+ if (!m_model)
+ return QHash<int, QByteArray>();
+
+ QHash<int, QByteArray> modelRoleNames = m_model->roleNames();
+ modelRoleNames.insert(DepthRole, "_q_TreeView_ItemDepth");
+ modelRoleNames.insert(ExpandedRole, "_q_TreeView_ItemExpanded");
+ modelRoleNames.insert(HasChildrenRole, "_q_TreeView_HasChildren");
+ modelRoleNames.insert(HasSiblingRole, "_q_TreeView_HasSibling");
+ return modelRoleNames;
+}
+
+int QQuickTreeModelAdaptor::rowCount(const QModelIndex &) const
+{
+ return m_items.count();
+}
+
+QVariant QQuickTreeModelAdaptor::data(const QModelIndex &index, int role) const
+{
+ if (!m_model)
+ return QVariant();
+
+ const QModelIndex &modelIndex = mapToModel(index);
+
+ switch (role) {
+ case DepthRole:
+ return m_items.at(index.row()).depth;
+ case ExpandedRole:
+ return isExpanded(index.row());
+ case HasChildrenRole:
+ return !(modelIndex.flags() & Qt::ItemNeverHasChildren) && m_model->hasChildren(modelIndex);
+ case HasSiblingRole:
+ return modelIndex.row() != m_model->rowCount(modelIndex.parent()) - 1;
+ default:
+ return m_model->data(modelIndex, role);
+ }
+}
+
+bool QQuickTreeModelAdaptor::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!m_model)
+ return false;
+
+ switch (role) {
+ case DepthRole:
+ case ExpandedRole:
+ case HasChildrenRole:
+ case HasSiblingRole:
+ return false;
+ default: {
+ const QModelIndex &pmi = mapToModel(index);
+ qDebug() << "setData" << pmi << role;
+ return m_model->setData(pmi, value, role);
+ }
+ }
+}
+
+int QQuickTreeModelAdaptor::itemIndex(const QModelIndex &index)
+{
+ // This is basically a plagiarism of QTreeViewPrivate::viewIndex()
+ if (!index.isValid() || m_items.isEmpty())
+ return -1;
+
+ const int totalCount = m_items.count();
+
+ // We start nearest to the lastViewedItem
+ int localCount = qMin(m_lastItemIndex - 1, totalCount - m_lastItemIndex);
+ for (int i = 0; i < localCount; ++i) {
+ const TreeItem &item1 = m_items.at(m_lastItemIndex + i);
+ if (item1.index == index) {
+ m_lastItemIndex = m_lastItemIndex + i;
+ return m_lastItemIndex;
+ }
+ const TreeItem &item2 = m_items.at(m_lastItemIndex - i - 1);
+ if (item2.index == index) {
+ m_lastItemIndex = m_lastItemIndex - i - 1;
+ return m_lastItemIndex;
+ }
+ }
+
+ for (int j = qMax(0, m_lastItemIndex + localCount); j < totalCount; ++j) {
+ const TreeItem &item = m_items.at(j);
+ if (item.index == index) {
+ m_lastItemIndex = j;
+ return j;
+ }
+ }
+ for (int j = qMin(totalCount, m_lastItemIndex - localCount) - 1; j >= 0; --j) {
+ const TreeItem &item = m_items.at(j);
+ if (item.index == index) {
+ m_lastItemIndex = j;
+ return j;
+ }
+ }
+
+ // nothing found
+ return -1;
+}
+
+bool QQuickTreeModelAdaptor::isVisible(const QModelIndex &index)
+{
+ return itemIndex(index) != -1;
+}
+
+bool QQuickTreeModelAdaptor::childrenVisible(const QModelIndex &index)
+{
+ return (!index.isValid() && !m_items.isEmpty())
+ || (m_expandedItems.contains(index) && isVisible(index));
+}
+
+const QModelIndex &QQuickTreeModelAdaptor::mapToModel(const QModelIndex &index) const
+{
+ return m_items.at(index.row()).index;
+}
+
+QModelIndex QQuickTreeModelAdaptor::mapRowToModelIndex(int row) const
+{
+ if (row < 0 || row >= m_items.count())
+ return QModelIndex();
+ return m_items.at(row).index;
+}
+
+QItemSelection QQuickTreeModelAdaptor::selectionForRowRange(int from, int to) const
+{
+ Q_ASSERT(0 <= from && from < m_items.count());
+ Q_ASSERT(0 <= to && to < m_items.count());
+
+ if (from > to)
+ qSwap(from, to);
+
+ typedef QPair<QModelIndex, QModelIndex> MIPair;
+ typedef QHash<QModelIndex, MIPair> MI2MIPairHash;
+ MI2MIPairHash ranges;
+ QModelIndex firstIndex = m_items.at(from).index;
+ QModelIndex lastIndex = firstIndex;
+ QModelIndex previousParent = firstIndex.parent();
+ bool selectLastRow = false;
+ for (int i = from + 1; i <= to || (selectLastRow = true); i++) {
+ // We run an extra iteration to make sure the last row is
+ // added to the selection. (And also to avoid duplicating
+ // the insertion code.)
+ QModelIndex index;
+ QModelIndex parent;
+ if (!selectLastRow) {
+ index = m_items.at(i).index;
+ parent = index.parent();
+ }
+ if (selectLastRow || previousParent != parent) {
+ const MI2MIPairHash::iterator &it = ranges.find(previousParent);
+ if (it == ranges.end())
+ ranges.insert(previousParent, MIPair(firstIndex, lastIndex));
+ else
+ it->second = lastIndex;
+
+ if (selectLastRow)
+ break;
+
+ firstIndex = index;
+ previousParent = parent;
+ }
+ lastIndex = index;
+ }
+
+ QItemSelection sel;
+ sel.reserve(ranges.count());
+ foreach (const MIPair &pair, ranges)
+ sel.append(QItemSelectionRange(pair.first, pair.second));
+
+ return sel;
+}
+
+void QQuickTreeModelAdaptor::showModelTopLevelItems(bool doInsertRows)
+{
+ if (!m_model)
+ return;
+
+ if (m_model->hasChildren(QModelIndex()) && m_model->canFetchMore(QModelIndex()))
+ m_model->fetchMore(QModelIndex());
+ const long topLevelRowCount = m_model->rowCount();
+ if (topLevelRowCount == 0) {
+ qDebug() << "no toplevel items";
+ return;
+ }
+
+ showModelChildItems(TreeItem(), 0, topLevelRowCount - 1, doInsertRows);
+}
+
+void QQuickTreeModelAdaptor::showModelChildItems(const TreeItem &parentItem, int start, int end, bool doInsertRows, bool doExpandPendingRows)
+{
+ const QModelIndex &parentIndex = parentItem.index;
+ int rowIdx = parentIndex.isValid() ? itemIndex(parentIndex) + 1 : 0;
+ Q_ASSERT(rowIdx == 0 || parentItem.expanded);
+ if (parentIndex.isValid() && (rowIdx == 0 || !parentItem.expanded)) {
+ if (rowIdx == 0)
+ qDebug() << "not found" << parentIndex;
+ else
+ qDebug() << "not expanded" << rowIdx - 1;
+ return;
+ }
+
+ if (m_model->rowCount(parentIndex) == 0) {
+ if (m_model->hasChildren(parentIndex) && m_model->canFetchMore(parentIndex))
+ m_model->fetchMore(parentIndex);
+ qDebug() << "no children" << parentIndex;
+ return;
+ }
+
+ int insertCount = end - start + 1;
+ int startIdx;
+ if (start == 0) {
+ startIdx = rowIdx;
+ } else {
+ const QModelIndex &prevSiblingIdx = m_model->index(start - 1, 0, parentIndex);
+ startIdx = lastChildIndex(prevSiblingIdx) + 1;
+ }
+
+ int rowDepth = rowIdx == 0 ? 0 : parentItem.depth + 1;
+ qDebug() << "inserting from" << startIdx << "to" << startIdx + insertCount - 1 << "depth" << rowDepth;
+ if (doInsertRows)
+ beginInsertRows(QModelIndex(), startIdx, startIdx + insertCount - 1);
+ m_items.reserve(m_items.count() + insertCount);
+ for (int i = 0; i < insertCount; i++) {
+ const QModelIndex &cmi = m_model->index(start + i, 0, parentIndex);
+ bool expanded = m_expandedItems.contains(cmi);
+ m_items.insert(startIdx + i, TreeItem(cmi, rowDepth, expanded));
+ if (expanded) {
+ qDebug() << "will expand" << startIdx + i;
+ m_itemsToExpand.append(&m_items[startIdx + i]);
+ }
+ }
+ if (doInsertRows)
+ endInsertRows();
+ qDebug() << "insertion done";
+
+ if (doExpandPendingRows)
+ expandPendingRows(doInsertRows);
+}
+
+
+void QQuickTreeModelAdaptor::expand(QModelIndex idx)
+{
+ ASSERT_CONSISTENCY();
+ if (!idx.isValid() || !m_model->hasChildren(idx))
+ return;
+ if (m_expandedItems.contains(idx))
+ return;
+
+ int row = itemIndex(idx);
+ if (row != -1)
+ expandRow(row);
+ else
+ m_expandedItems.insert(idx);
+ ASSERT_CONSISTENCY();
+
+ emit expanded(idx);
+}
+
+void QQuickTreeModelAdaptor::collapse(QModelIndex idx)
+{
+ ASSERT_CONSISTENCY();
+ if (!idx.isValid() || !m_model->hasChildren(idx))
+ return;
+ if (!m_expandedItems.contains(idx))
+ return;
+
+ int row = itemIndex(idx);
+ if (row != -1)
+ collapseRow(row);
+ else
+ m_expandedItems.remove(idx);
+ ASSERT_CONSISTENCY();
+
+ emit collapsed(idx);
+}
+
+bool QQuickTreeModelAdaptor::isExpanded(QModelIndex index) const
+{
+ ASSERT_CONSISTENCY();
+ return !index.isValid() || m_expandedItems.contains(index);
+}
+
+bool QQuickTreeModelAdaptor::isExpanded(int row) const
+{
+ return m_items.at(row).expanded;
+}
+
+void QQuickTreeModelAdaptor::expandRow(int n)
+{
+ if (!m_model || isExpanded(n)) {
+ qDebug() << "already expanded" << n;
+ return;
+ }
+
+ TreeItem &item = m_items[n];
+ if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(item.index)) {
+ qDebug() << "no children" << n;
+ return;
+ }
+ item.expanded = true;
+ m_expandedItems.insert(item.index);
+ QVector<int> changedRole(1, ExpandedRole);
+ emit dataChanged(index(n), index(n), changedRole);
+
+ qDebug() << "expanding" << n << m_model->rowCount(item.index) << m_items[n].expanded;
+ m_itemsToExpand.append(&item);
+ expandPendingRows();
+}
+
+void QQuickTreeModelAdaptor::expandPendingRows(bool doInsertRows)
+{
+ while (!m_itemsToExpand.isEmpty()) {
+ TreeItem *item = m_itemsToExpand.takeFirst();
+ Q_ASSERT(item->expanded);
+ const QModelIndex &index = item->index;
+ int childrenCount = m_model->rowCount(index);
+ if (childrenCount == 0) {
+ if (m_model->hasChildren(index) && m_model->canFetchMore(index))
+ m_model->fetchMore(index);
+ qDebug() << "no children for row" << itemIndex(index);
+ continue;
+ }
+
+ qDebug() << "expanding pending row" << itemIndex(index) << "children"<< childrenCount;
+
+ // TODO Pre-compute the total number of items made visible
+ // so that we only call a single beginInsertRows()/endInsertRows()
+ // pair per expansion (same as we do for collapsing).
+ showModelChildItems(*item, 0, childrenCount - 1, doInsertRows, false);
+ }
+}
+
+void QQuickTreeModelAdaptor::collapseRow(int n)
+{
+ if (!m_model || !isExpanded(n)) {
+ qDebug() << "not expanded" << n;
+ return;
+ }
+
+ TreeItem &item = m_items[n];
+ item.expanded = false;
+ m_expandedItems.remove(item.index);
+ QVector<int> changedRole(1, ExpandedRole);
+ emit dataChanged(index(n), index(n), changedRole);
+ int childrenCount = m_model->rowCount(item.index);
+ if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(item.index) || childrenCount == 0) {
+ qDebug() << "no children" << n;
+ return;
+ }
+
+ qDebug() << "collapsing" << n << childrenCount;
+ const QModelIndex &emi = m_model->index(m_model->rowCount(item.index) - 1, 0, item.index);
+ int lastIndex = lastChildIndex(emi);
+ removeVisibleRows(n + 1, lastIndex);
+}
+
+int QQuickTreeModelAdaptor::lastChildIndex(const QModelIndex &index)
+{
+// qDebug() << "last child of" << itemIndex(index.parent());
+
+ if (!m_expandedItems.contains(index)) {
+// qDebug() << "not expanded" << itemIndex(index);
+ return itemIndex(index);
+ }
+
+ QModelIndex parent = index.parent();
+ QModelIndex nextSiblingIndex;
+ while (parent.isValid()) {
+ nextSiblingIndex = parent.sibling(parent.row() + 1, 0);
+ if (nextSiblingIndex.isValid())
+ break;
+ parent = parent.parent();
+ }
+
+ int firstIndex = nextSiblingIndex.isValid() ? itemIndex(nextSiblingIndex) : m_items.count();
+ qDebug() << "first index" << firstIndex - 1;
+ return firstIndex - 1;
+}
+
+void QQuickTreeModelAdaptor::removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows)
+{
+ if (startIndex < 0 || endIndex < 0 || startIndex > endIndex)
+ return;
+
+ qDebug() << "removing" << startIndex << endIndex;
+ if (doRemoveRows)
+ beginRemoveRows(QModelIndex(), startIndex, endIndex);
+ m_items.erase(m_items.begin() + startIndex, m_items.begin() + endIndex + 1);
+ if (doRemoveRows)
+ endRemoveRows();
+}
+
+void QQuickTreeModelAdaptor::modelHasBeenReset()
+{
+ qDebug() << "modelHasBeenReset";
+ clearModelData();
+
+ showModelTopLevelItems();
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRigth, const QVector<int> &roles)
+{
+ qDebug() << "modelDataChanged" << topLeft << bottomRigth;
+ Q_ASSERT(topLeft.parent() == bottomRigth.parent());
+ const QModelIndex &parent = topLeft.parent();
+ if (parent.isValid() && !childrenVisible(parent)) {
+ qDebug() << "not visible" << parent;
+ ASSERT_CONSISTENCY();
+ return;
+ }
+
+ int topIndex = itemIndex(topLeft);
+ if (topIndex == -1) // 'parent' is not visible anymore, though it's been expanded previously
+ return;
+ for (int i = topLeft.row(); i <= bottomRigth.row(); i++) {
+ // Group items with same parent to minize the number of 'dataChanged()' emits
+ int bottomIndex = topIndex;
+ while (bottomIndex < m_items.count()) {
+ const QModelIndex &idx = m_items.at(bottomIndex).index;
+ if (idx.parent() != parent) {
+ --bottomIndex;
+ break;
+ }
+ if (idx.row() == bottomRigth.row())
+ break;
+ ++bottomIndex;
+ }
+ emit dataChanged(index(topIndex), index(bottomIndex), roles);
+
+ i += bottomIndex - topIndex;
+ if (i == bottomRigth.row())
+ break;
+ topIndex = bottomIndex + 1;
+ while (topIndex < m_items.count()
+ && m_items.at(topIndex).index.parent() != parent)
+ topIndex++;
+ }
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
+{
+ qDebug() << "modelLayoutAboutToBeChanged" << parents << hint << m_items.count();
+ ASSERT_CONSISTENCY();
+ Q_UNUSED(parents);
+ Q_UNUSED(hint);
+}
+
+void QQuickTreeModelAdaptor::modelLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
+{
+ Q_UNUSED(hint);
+ qDebug() << "modelLayoutChanged" << parents << hint << m_items.count();
+ if (parents.isEmpty()) {
+ m_items.clear();
+ showModelTopLevelItems(false /*doInsertRows*/);
+ emit dataChanged(index(0), index(m_items.count() - 1));
+ }
+
+ Q_FOREACH (const QPersistentModelIndex &pmi, parents) {
+ if (m_expandedItems.contains(pmi) && m_model->hasChildren(pmi)) {
+ int row = itemIndex(pmi);
+ if (row != -1) {
+ const QModelIndex &lmi = m_model->index(m_model->rowCount(pmi) - 1, 0, pmi);
+ int lastRow = lastChildIndex(lmi);
+ removeVisibleRows(row + 1, lastRow, false /*doRemoveRows*/);
+ showModelChildItems(m_items.at(row), 0, m_model->rowCount(pmi) - 1, false /*doInsertRows*/);
+ emit dataChanged(index(row + 1), index(lastRow));
+ }
+ }
+ }
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::modelRowsAboutToBeInserted(const QModelIndex & parent, int start, int end)
+{
+ qDebug() << "modelRowsAboutToBeInserted" << parent << "start" << start << "end" << end;
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::modelRowsInserted(const QModelIndex & parent, int start, int end)
+{
+ qDebug() << "modelRowsInserted" << parent << "start" << start << "end" << end;
+ TreeItem item;
+ int parentRow = itemIndex(parent);
+ if (parentRow >= 0) {
+ item = m_items.at(parentRow);
+ if (!item.expanded) {
+ ASSERT_CONSISTENCY();
+ return;
+ }
+ } else if (parent.isValid()) {
+ item = TreeItem(parent);
+ }
+ showModelChildItems(item, start, end);
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::modelRowsAboutToBeRemoved(const QModelIndex & parent, int start, int end)
+{
+ qDebug() << "modelRowsAboutToBeRemoved" << parent << "start" << start << "end" << end;
+ ASSERT_CONSISTENCY();
+ if (!parent.isValid() || childrenVisible(parent)) {
+ const QModelIndex &smi = m_model->index(start, 0, parent);
+ int startIndex = itemIndex(smi);
+ const QModelIndex &emi = m_model->index(end, 0, parent);
+ int endIndex = itemIndex(emi);
+ if (isExpanded(emi)) {
+ const QModelIndex &idx = m_model->index(m_model->rowCount(emi) - 1, 0, emi);
+ endIndex = lastChildIndex(idx);
+ }
+ removeVisibleRows(startIndex, endIndex);
+ }
+
+ for (int r = start; r <= end; r++) {
+ const QModelIndex &cmi = m_model->index(r, 0, parent);
+ m_expandedItems.remove(cmi);
+ }
+}
+
+void QQuickTreeModelAdaptor::modelRowsRemoved(const QModelIndex & parent, int start, int end)
+{
+ qDebug() << "modelRowsRemoved" << parent << "start" << start << "end" << end;
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::modelRowsAboutToBeMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
+{
+ qDebug() << "modelRowsAboutToBeMoved" << sourceParent << "source start" << sourceStart << "end" << sourceEnd;
+ qDebug() << " destination" << destinationParent << "row" << destinationRow;
+ ASSERT_CONSISTENCY();
+ if (!childrenVisible(sourceParent))
+ return; // Do nothing now. See modelRowsMoved() below.
+
+ if (!childrenVisible(destinationParent)) {
+ modelRowsAboutToBeRemoved(sourceParent, sourceStart, sourceEnd);
+ } else {
+ int depthDifference = -1;
+ if (destinationParent.isValid()) {
+ int destParentIndex = itemIndex(destinationParent);
+ depthDifference = m_items.at(destParentIndex).depth;
+ }
+ if (sourceParent.isValid()) {
+ int sourceParentIndex = itemIndex(sourceParent);
+ depthDifference -= m_items.at(sourceParentIndex).depth;
+ } else {
+ depthDifference++;
+ }
+ qDebug() << "depth difference" << depthDifference;
+
+ int startIndex = itemIndex(m_model->index(sourceStart, 0, sourceParent));
+ const QModelIndex &emi = m_model->index(sourceEnd, 0, sourceParent);
+ int endIndex;
+ if (isExpanded(emi))
+ endIndex = lastChildIndex(m_model->index(m_model->rowCount(emi) - 1, 0, emi));
+ else
+ endIndex = itemIndex(emi);
+
+ int destIndex = -1;
+ if (destinationRow == m_model->rowCount(destinationParent)) {
+ const QModelIndex &emi = m_model->index(destinationRow - 1, 0, destinationParent);
+ destIndex = lastChildIndex(emi) + 1;
+ } else {
+ destIndex = itemIndex(m_model->index(destinationRow, 0, destinationParent));
+ }
+
+ qDebug() << "moving" << (destIndex > endIndex ? "forward" : "backward") << startIndex << endIndex << destIndex << m_items.count();
+ beginMoveRows(QModelIndex(), startIndex, endIndex, QModelIndex(), destIndex);
+ int totalMovedCount = endIndex - startIndex + 1;
+ const QList<TreeItem> &buffer = m_items.mid(startIndex, totalMovedCount);
+ qDebug() << "copied" << startIndex << totalMovedCount;
+ int bufferCopyOffset;
+ if (destIndex > endIndex) {
+ for (int i = endIndex + 1; i < destIndex; i++) {
+ m_items.swap(i, i - totalMovedCount); // Fast move from 1st to 2nd position
+ }
+ bufferCopyOffset = destIndex - totalMovedCount;
+ } else {
+ for (int i = startIndex - 1; i >= destIndex; i--) {
+ m_items.swap(i, i + totalMovedCount); // Fast move from 1st to 2nd position
+ }
+ bufferCopyOffset = destIndex;
+ }
+ qDebug() << "copying back" << bufferCopyOffset << buffer.length();
+ for (int i = 0; i < buffer.length(); i++) {
+ TreeItem item = buffer.at(i);
+ item.depth += depthDifference;
+ m_items.replace(bufferCopyOffset + i, item);
+ }
+ endMoveRows();
+ }
+}
+
+void QQuickTreeModelAdaptor::modelRowsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
+{
+ qDebug() << "modelRowsMoved" << sourceParent << "source start" << sourceStart << "end" << sourceEnd;
+ qDebug() << " destination" << destinationParent << "row" << destinationRow;
+ if (!childrenVisible(sourceParent) && childrenVisible(destinationParent))
+ modelRowsInserted(destinationParent, destinationRow, destinationRow + sourceEnd - sourceStart);
+ ASSERT_CONSISTENCY();
+}
+
+void QQuickTreeModelAdaptor::dump() const
+{
+ int count = m_items.count();
+ if (count == 0)
+ return;
+ int countWidth = floor(log10(count)) + 1;
+ qInfo() << "Dumping" << this;
+ for (int i = 0; i < count; i++) {
+ const TreeItem &item = m_items.at(i);
+ bool hasChildren = m_model->hasChildren(item.index);
+ int children = m_model->rowCount(item.index);
+ qInfo().noquote().nospace()
+ << QString("%1 ").arg(i, countWidth) << QString(4 * item.depth, QChar::fromLatin1('.'))
+ << QLatin1String(!hasChildren ? ".. " : item.expanded ? " v " : " > ")
+ << item.index << children;
+ }
+}
+
+bool QQuickTreeModelAdaptor::testConsistency(bool dumpOnFail) const
+{
+ QModelIndex parent;
+ QStack<QModelIndex> ancestors;
+ QModelIndex idx = m_model->index(0, 0);
+ for (int i = 0; i < m_items.count(); i++) {
+ bool isConsistent = true;
+ const TreeItem &item = m_items.at(i);
+ if (item.index != idx) {
+ qWarning() << "QModelIndex inconsistency" << i << item.index;
+ qWarning() << " expected" << idx;
+ isConsistent = false;
+ }
+ if (item.index.parent() != parent) {
+ qWarning() << "Parent inconsistency" << i << item.index;
+ qWarning() << " stored index parent" << item.index.parent() << "model parent" << parent;
+ isConsistent = false;
+ }
+ if (item.depth != ancestors.count()) {
+ qWarning() << "Depth inconsistency" << i << item.index;
+ qWarning() << " item depth" << item.depth << "ancestors stack" << ancestors.count();
+ isConsistent = false;
+ }
+ if (item.expanded && !m_expandedItems.contains(item.index)) {
+ qWarning() << "Expanded inconsistency" << i << item.index;
+ qWarning() << " set" << m_expandedItems.contains(item.index) << "item" << item.expanded;
+ isConsistent = false;
+ }
+ if (!isConsistent) {
+ if (dumpOnFail)
+ dump();
+ return false;
+ }
+ QModelIndex firstChildIndex;
+ if (item.expanded)
+ firstChildIndex = m_model->index(0, 0, idx);
+ if (firstChildIndex.isValid()) {
+ ancestors.push(parent);
+ parent = idx;
+ idx = m_model->index(0, 0, parent);
+ } else {
+ while (idx.row() == m_model->rowCount(parent) - 1) {
+ if (ancestors.isEmpty())
+ break;
+ idx = parent;
+ parent = ancestors.pop();
+ }
+ idx = m_model->index(idx.row() + 1, 0, parent);
+ }
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/controls/Private/qquicktreemodeladaptor_p.h b/src/controls/Private/qquicktreemodeladaptor_p.h
new file mode 100644
index 000000000..838ab8055
--- /dev/null
+++ b/src/controls/Private/qquicktreemodeladaptor_p.h
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKTREEMODELADAPTOR_H
+#define QQUICKTREEMODELADAPTOR_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/qset.h>
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qitemselectionmodel.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractItemModel;
+
+class QQuickTreeModelAdaptor : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
+
+ struct TreeItem;
+
+public:
+ explicit QQuickTreeModelAdaptor(QObject *parent = 0);
+
+ QAbstractItemModel *model() const;
+
+ enum {
+ DepthRole = Qt::UserRole - 4,
+ ExpandedRole,
+ HasChildrenRole,
+ HasSiblingRole
+ };
+
+ QHash<int, QByteArray> roleNames() const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data(const QModelIndex &, int role) const;
+ bool setData(const QModelIndex &index, const QVariant &value, int role);
+
+ void clearModelData();
+
+ bool isVisible(const QModelIndex &index);
+ bool childrenVisible(const QModelIndex &index);
+
+ const QModelIndex &mapToModel(const QModelIndex &index) const;
+ Q_INVOKABLE QModelIndex mapRowToModelIndex(int row) const;
+
+ Q_INVOKABLE QItemSelection selectionForRowRange(int form, int to) 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);
+ void expandPendingRows(bool doInsertRows = true);
+ int lastChildIndex(const QModelIndex &index);
+ void removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows = true);
+
+ void expandRow(int n);
+ void collapseRow(int n);
+ bool isExpanded(int row) const;
+
+ Q_INVOKABLE bool isExpanded(QModelIndex) const;
+
+ void dump() const;
+ bool testConsistency(bool dumpOnFail = false) const;
+
+signals:
+ void modelChanged(QAbstractItemModel *model);
+ void expanded(QModelIndex index);
+ void collapsed(QModelIndex index);
+
+public slots:
+ void expand(QModelIndex);
+ void collapse(QModelIndex);
+
+ void setModel(QAbstractItemModel *model);
+
+private slots:
+ void modelHasBeenReset();
+ void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRigth, 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;
+ }
+ };
+
+ QAbstractItemModel *m_model;
+ QList<TreeItem> m_items;
+ QSet<QPersistentModelIndex> m_expandedItems;
+ QList<TreeItem *> m_itemsToExpand;
+ int m_lastItemIndex;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKTREEMODELADAPTOR_H
diff --git a/src/controls/Styles/Base/BasicTableViewStyle.qml b/src/controls/Styles/Base/BasicTableViewStyle.qml
new file mode 100644
index 000000000..24a528f52
--- /dev/null
+++ b/src/controls/Styles/Base/BasicTableViewStyle.qml
@@ -0,0 +1,150 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Private 1.0
+
+/*!
+ \qmltype BasicTableViewStyle
+ \inqmlmodule QtQuick.Controls.Styles
+ \since 5.1
+ \internal
+ \qmlabstract
+ \ingroup viewsstyling
+ \brief Provides custom styling for TableView
+
+ \note This class derives from \l {QtQuick.Controls.Styles::}{ScrollViewStyle}
+ and supports all of the properties defined there.
+*/
+ScrollViewStyle {
+ id: root
+
+ /*! The \l TableView this style is attached to. */
+ readonly property BasicTableView control: __control
+
+ /*! The text color. */
+ property color textColor: SystemPaletteSingleton.text(control.enabled)
+
+ /*! The background color. */
+ property color backgroundColor: control.backgroundVisible ? SystemPaletteSingleton.base(control.enabled) : "transparent"
+
+ /*! The alternate background color. */
+ property color alternateBackgroundColor: "#f5f5f5"
+
+ /*! The text highlight color, used within selections. */
+ property color highlightedTextColor: "white"
+
+ /*! Activates items on single click. */
+ property bool activateItemOnSingleClick: false
+
+ padding.top: control.headerVisible ? 0 : 1
+
+ /*! \qmlproperty Component BasicTableViewStyle::headerDelegate
+ Delegate for header. This delegate is described in \l {TableView::headerDelegate}
+ */
+ property Component headerDelegate: BorderImage {
+ height: textItem.implicitHeight * 1.2
+ source: "images/header.png"
+ border.left: 4
+ border.bottom: 2
+ border.top: 2
+ Text {
+ id: textItem
+ anchors.fill: parent
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: styleData.textAlignment
+ anchors.leftMargin: 12
+ text: styleData.value
+ elide: Text.ElideRight
+ color: textColor
+ renderType: Settings.isMobile ? Text.QtRendering : Text.NativeRendering
+ }
+ Rectangle {
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 1
+ anchors.topMargin: 1
+ width: 1
+ color: "#ccc"
+ }
+ }
+
+ /*! \qmlproperty Component BasicTableViewStyle::rowDelegate
+ Delegate for row. This delegate is described in \l {TableView::rowDelegate}
+ */
+ property Component rowDelegate: Rectangle {
+ height: Math.round(TextSingleton.implicitHeight * 1.2)
+ property color selectedColor: control.activeFocus ? "#07c" : "#999"
+ color: styleData.selected ? selectedColor :
+ !styleData.alternate ? alternateBackgroundColor : backgroundColor
+ }
+
+ /*! \qmlproperty Component BasicTableViewStyle::itemDelegate
+ Delegate for item. This delegate is described in \l {TableView::itemDelegate}
+ */
+ property Component itemDelegate: Item {
+ height: Math.max(16, label.implicitHeight)
+ property int implicitWidth: label.implicitWidth + 20
+
+ Text {
+ id: label
+ objectName: "label"
+ width: parent.width
+// anchors.leftMargin: 12
+ anchors.left: parent.left
+ anchors.right: parent.right
+ horizontalAlignment: styleData.textAlignment
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.verticalCenterOffset: 1
+ elide: styleData.elideMode
+ text: styleData.value !== undefined ? styleData.value : ""
+ color: styleData.textColor
+ renderType: Settings.isMobile ? Text.QtRendering : Text.NativeRendering
+ }
+ }
+
+ /*! \internal
+ Part of TreeViewStyle
+ */
+ property Component __branchDelegate: null
+
+ /*! \internal
+ Part of TreeViewStyle
+ */
+ property int __indentation: 12
+}
diff --git a/src/controls/Styles/Base/TableViewStyle.qml b/src/controls/Styles/Base/TableViewStyle.qml
index e5e6db2fd..3715eb266 100644
--- a/src/controls/Styles/Base/TableViewStyle.qml
+++ b/src/controls/Styles/Base/TableViewStyle.qml
@@ -37,8 +37,8 @@
** $QT_END_LICENSE$
**
****************************************************************************/
-import QtQuick 2.2
-import QtQuick.Controls 1.2
+import QtQuick 2.5
+import QtQuick.Controls 1.4
import QtQuick.Controls.Private 1.0
/*!
@@ -51,92 +51,9 @@ import QtQuick.Controls.Private 1.0
\note This class derives from \l {QtQuick.Controls.Styles::}{ScrollViewStyle}
and supports all of the properties defined there.
*/
-ScrollViewStyle {
+BasicTableViewStyle {
id: root
/*! The \l TableView this style is attached to. */
readonly property TableView control: __control
-
- /*! The text color. */
- property color textColor: SystemPaletteSingleton.text(control.enabled)
-
- /*! The background color. */
- property color backgroundColor: control.backgroundVisible ? SystemPaletteSingleton.base(control.enabled) : "transparent"
-
- /*! The alternate background color. */
- property color alternateBackgroundColor: "#f5f5f5"
-
- /*! The text highlight color, used within selections. */
- property color highlightedTextColor: "white"
-
- /*! Activates items on single click. */
- property bool activateItemOnSingleClick: false
-
- padding.top: control.headerVisible ? 0 : 1
-
- /*! \qmlproperty Component TableViewStyle::headerDelegate
- Delegate for header. This delegate is described in \l {TableView::headerDelegate}
- */
- property Component headerDelegate: BorderImage {
- height: textItem.implicitHeight * 1.2
- source: "images/header.png"
- border.left: 4
- border.bottom: 2
- border.top: 2
- Text {
- id: textItem
- anchors.fill: parent
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: styleData.textAlignment
- anchors.leftMargin: 12
- text: styleData.value
- elide: Text.ElideRight
- color: textColor
- renderType: Settings.isMobile ? Text.QtRendering : Text.NativeRendering
- }
- Rectangle {
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.bottomMargin: 1
- anchors.topMargin: 1
- width: 1
- color: "#ccc"
- }
- }
-
- /*! \qmlproperty Component TableViewStyle::rowDelegate
- Delegate for row. This delegate is described in \l {TableView::rowDelegate}
- */
- property Component rowDelegate: Rectangle {
- height: Math.round(TextSingleton.implicitHeight * 1.2)
- property color selectedColor: control.activeFocus ? "#07c" : "#999"
- color: styleData.selected ? selectedColor :
- !styleData.alternate ? alternateBackgroundColor : backgroundColor
- }
-
- /*! \qmlproperty Component TableViewStyle::itemDelegate
- Delegate for item. This delegate is described in \l {TableView::itemDelegate}
- */
- property Component itemDelegate: Item {
- height: Math.max(16, label.implicitHeight)
- property int implicitWidth: label.implicitWidth + 20
-
- Text {
- id: label
- objectName: "label"
- width: parent.width
- anchors.leftMargin: 12
- anchors.left: parent.left
- anchors.right: parent.right
- horizontalAlignment: styleData.textAlignment
- anchors.verticalCenter: parent.verticalCenter
- anchors.verticalCenterOffset: 1
- elide: styleData.elideMode
- text: styleData.value !== undefined ? styleData.value : ""
- color: styleData.textColor
- renderType: Settings.isMobile ? Text.QtRendering : Text.NativeRendering
- }
- }
}
-
diff --git a/src/controls/Styles/Base/TreeViewStyle.qml b/src/controls/Styles/Base/TreeViewStyle.qml
new file mode 100644
index 000000000..2e36c3166
--- /dev/null
+++ b/src/controls/Styles/Base/TreeViewStyle.qml
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Private 1.0
+
+/*!
+ \qmltype TreeViewStyle
+ \inqmlmodule QtQuick.Controls.Styles
+ \since 5.5
+ \ingroup viewsstyling
+ \inherits TableViewStyle
+ \brief Provides custom styling for TreeView
+
+ \note This class derives from \l {QtQuick.Controls.Styles::}{TableViewStyle}
+ and supports all of the properties defined there.
+*/
+BasicTableViewStyle {
+ id: root
+
+ /*! The \l TreeView this style is attached to. */
+ readonly property TreeView control: __control
+
+ /*!
+ The amount each level is indented relatively to its parent level.
+ */
+ property int indentation: 12
+
+ // TODO - to update
+ /*! \qmlproperty Component TreeViewStyle::branchDelegate
+
+ This property defines a delegate to draw the branch indicator.
+
+ In the branch delegate you have access to the following special properties:
+ \list
+ \li styleData.column - the index of the column
+ \li styleData.selected - if the item is currently selected
+ \li styleData.textColor - the default text color for an item
+ \li styleData.isExpanded - true when the item is expanded
+ \li styleData.hasChildren - true if the model index of the current item has children
+ \li styleData.hasSibling - true if the model index of the current item has sibling
+ \endlist
+ */
+
+ property Component branchDelegate: Item {
+ width: 16
+ height: 16
+ Text {
+ visible: styleData.column === 0 && styleData.hasChildren
+ text: styleData.isExpanded ? "\u25bc" : "\u25b6"
+ color: !control.activeFocus || styleData.selected ? styleData.textColor : "#666"
+ font.pointSize: 10
+ renderType: Text.NativeRendering
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: styleData.isExpanded ? 2 : 0
+ }
+ }
+
+ __branchDelegate: branchDelegate
+ __indentation: indentation
+}
diff --git a/src/controls/Styles/Desktop/TableViewStyle.qml b/src/controls/Styles/Desktop/TableViewStyle.qml
index 11f8ed54c..377b3a750 100644
--- a/src/controls/Styles/Desktop/TableViewStyle.qml
+++ b/src/controls/Styles/Desktop/TableViewStyle.qml
@@ -45,7 +45,8 @@ import "."
ScrollViewStyle {
id: root
- readonly property TableView control: __control
+ readonly property BasicTableView control: __control
+ property int __indentation: 8
property bool activateItemOnSingleClick: __styleitem.styleHint("activateItemOnSingleClick")
property color textColor: __styleitem.textColor
property color backgroundColor: SystemPaletteSingleton.base(control.enabled)
@@ -95,7 +96,6 @@ ScrollViewStyle {
id: label
objectName: "label"
width: parent.width
- anchors.leftMargin: 8
font: __styleitem.font
anchors.left: parent.left
anchors.right: parent.right
@@ -107,5 +107,6 @@ ScrollViewStyle {
renderType: Text.NativeRendering
}
}
-}
+ property Component __branchDelegate: null
+}
diff --git a/src/controls/Styles/Desktop/TreeViewStyle.qml b/src/controls/Styles/Desktop/TreeViewStyle.qml
new file mode 100644
index 000000000..1901c40cc
--- /dev/null
+++ b/src/controls/Styles/Desktop/TreeViewStyle.qml
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.2
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Private 1.0
+import "." as Desktop
+
+Desktop.TableViewStyle {
+ id: root
+
+ __indentation: 12
+
+ __branchDelegate: StyleItem {
+ id: si
+ elementType: "itembranchindicator"
+ properties: {
+ "hasChildren": styleData.hasChildren,
+ "hasSibling": styleData.hasSibling && !styleData.isExpanded
+ }
+ on: styleData.isExpanded
+ selected: styleData.selected
+ hasFocus: __styleitem.active
+
+ Component.onCompleted: {
+ implicitWidth = si.pixelMetric("treeviewindentation")
+ implicitHeight = implicitWidth
+ var rect = si.subControlRect("dummy");
+ width = rect.width
+ height = rect.height
+ root.__indentation = width
+ }
+ }
+}
diff --git a/src/controls/Styles/qmldir b/src/controls/Styles/qmldir
index 7e21858e1..0be8756d5 100644
--- a/src/controls/Styles/qmldir
+++ b/src/controls/Styles/qmldir
@@ -15,6 +15,7 @@ SpinBoxStyle 1.1 Base/SpinBoxStyle.qml
SwitchStyle 1.1 Base/SwitchStyle.qml
TabViewStyle 1.0 Base/TabViewStyle.qml
TableViewStyle 1.0 Base/TableViewStyle.qml
+TreeViewStyle 1.0 Base/TreeViewStyle.qml
TextAreaStyle 1.1 Base/TextAreaStyle.qml
TextFieldStyle 1.0 Base/TextFieldStyle.qml
ToolBarStyle 1.0 Base/ToolBarStyle.qml
diff --git a/src/controls/Styles/styles.pri b/src/controls/Styles/styles.pri
index 1fcf41476..f91a9d1a2 100644
--- a/src/controls/Styles/styles.pri
+++ b/src/controls/Styles/styles.pri
@@ -18,7 +18,9 @@ STYLES_QML_FILES = \
$$PWD/Base/SpinBoxStyle.qml \
$$PWD/Base/SwitchStyle.qml \
$$PWD/Base/StatusBarStyle.qml \
+ $$PWD/Base/BasicTableViewStyle.qml \
$$PWD/Base/TableViewStyle.qml \
+ $$PWD/Base/TreeViewStyle.qml \
$$PWD/Base/TabViewStyle.qml \
$$PWD/Base/TextAreaStyle.qml \
$$PWD/Base/TextFieldStyle.qml \
@@ -49,6 +51,7 @@ STYLES_QML_FILES = \
$$PWD/Desktop/StatusBarStyle.qml\
$$PWD/Desktop/TabViewStyle.qml \
$$PWD/Desktop/TableViewStyle.qml \
+ $$PWD/Desktop/TreeViewStyle.qml \
$$PWD/Desktop/TextAreaStyle.qml \
$$PWD/Desktop/TextFieldStyle.qml \
$$PWD/Desktop/ToolBarStyle.qml \
diff --git a/src/controls/TableView.qml b/src/controls/TableView.qml
index 0bec3de6d..629c95dd0 100644
--- a/src/controls/TableView.qml
+++ b/src/controls/TableView.qml
@@ -1,38 +1,34 @@
/****************************************************************************
**
-** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
-** Contact: http://www.qt-project.org/legal
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
-** $QT_BEGIN_LICENSE:BSD$
-** You may use this file under the terms of the BSD license as follows:
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
**
-** "Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions are
-** met:
-** * Redistributions of source code must retain the above copyright
-** notice, this list of conditions and the following disclaimer.
-** * Redistributions in binary form must reproduce the above copyright
-** notice, this list of conditions and the following disclaimer in
-** the documentation and/or other materials provided with the
-** distribution.
-** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
-** of its contributors may be used to endorse or promote products derived
-** from this software without specific prior written permission.
+** 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.LGPLv3 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.html.
**
-**
-** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
diff --git a/src/controls/TableViewColumn.qml b/src/controls/TableViewColumn.qml
index 2d911a1ec..3cea1a2bc 100644
--- a/src/controls/TableViewColumn.qml
+++ b/src/controls/TableViewColumn.qml
@@ -45,11 +45,11 @@ import QtQuick 2.2
\inqmlmodule QtQuick.Controls
\since 5.1
\ingroup viewitems
- \brief Used to define columns in a \l TableView.
+ \brief Used to define columns in a \l TableView or in a \l TreeView.
\image tableview.png
- TableViewColumn represents a column within a TableView. It provides
+ TableViewColumn represents a column within a TableView or a TreeView. It provides
properties to decide how the data in that column is presented.
\qml
@@ -60,7 +60,7 @@ import QtQuick 2.2
}
\endqml
- \sa TableView
+ \sa TableView, TreeView
*/
QtObject {
@@ -121,7 +121,7 @@ QtObject {
property int horizontalAlignment: Text.AlignLeft
/*! The delegate of the column. This can be used to set the
- \l TableView::itemDelegate for a specific column.
+ \l TableView::itemDelegate or TreeView::itemDelegate for a specific column.
In the delegate you have access to the following special properties:
\list
diff --git a/src/controls/TreeView.qml b/src/controls/TreeView.qml
new file mode 100644
index 000000000..7e1520da9
--- /dev/null
+++ b/src/controls/TreeView.qml
@@ -0,0 +1,500 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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.LGPLv3 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.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.4
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Private 1.0
+import QtQuick.Controls.Styles 1.2
+import QtQml.Models 2.2
+
+/*!
+ \qmltype TreeView
+ \inqmlmodule QtQuick.Controls
+ \since 5.5
+ \ingroup views
+ \brief Provides a tree view with scroll bars, styling and header sections.
+
+ \image treeview.png
+
+ A TreeView implements a tree representation of items from a model.
+
+ Data for each row in the TreeView
+ is provided by the model. TreeView accepts models derived from the QAbstractItemModel class.
+
+ You provide title and size of a column header
+ by adding a \l TableViewColumn as demonstrated below.
+
+ \code
+ TreeView {
+ TableViewColumn {
+ title: "Name"
+ role: "fileName"
+ width: 300
+ }
+ TableViewColumn {
+ title: "Permissions"
+ role: "filePermissions"
+ width: 100
+ }
+ model: fileSystemModel
+ }
+ \endcode
+
+ The header sections are attached to values in the \l model by defining
+ the model role they attach to. Each property in the model will
+ then be shown in their corresponding column.
+
+ You can customize the look by overriding the \l itemDelegate,
+ \l rowDelegate, or \l headerDelegate properties.
+
+ The view itself does not provide sorting. This has to
+ be done on the model itself. However you can provide sorting
+ on the model, and enable sort indicators on headers.
+
+\list
+ \li int sortIndicatorColumn - The index of the current sort column
+ \li bool sortIndicatorVisible - Whether the sort indicator should be enabled
+ \li enum sortIndicatorOrder - Qt.AscendingOrder or Qt.DescendingOrder depending on state
+\endlist
+
+ You can create a custom appearance for a TreeView by
+ assigning a \l {QtQuick.Controls.Styles::TreeViewStyle}{TreeViewStyle}.
+*/
+
+BasicTableView {
+ id: root
+
+ /*!
+ \qmlproperty QAbstractItemModel TreeView::model
+ This property holds the model providing data for the tree view.
+
+ The model provides the set of data that is displayed by the view.
+ The TreeView accept models derived from the QAbstractItemModel class.
+ */
+ property var model: null
+
+ /*!
+ \qmlproperty QModelIndex TreeView::currentIndex
+ The model index of the current row in the tree view.
+ */
+ readonly property var currentIndex: modelAdaptor.mapRowToModelIndex(__currentRow)
+
+ /*!
+ \qmlproperty QItemSelectionModel TreeView::selection
+ */
+ property ItemSelectionModel selection: null
+
+ /*!
+ \qmlsignal TreeView::activated(QModelIndex index)
+
+ Emitted when the user activates a row in the tree by mouse or keyboard interaction.
+ Mouse activation is triggered by single- or double-clicking, depending on the platform.
+
+ \a index is the model index of the activated row in the tree.
+
+ \note This signal is only emitted for mouse interaction that is not blocked in the row or item delegate.
+
+ The corresponding handler is \c onActivated.
+ */
+ signal activated(var index)
+
+ /*!
+ \qmlsignal TreeView::clicked(QModelIndex index)
+
+ Emitted when the user clicks a valid row in the tree by single clicking
+
+ \a index is the model index of the clicked row in the tree.
+
+ \note This signal is only emitted if the row or item delegate does not accept mouse events.
+
+ The corresponding handler is \c onClicked.
+ */
+ signal clicked(var index)
+
+ /*!
+ \qmlsignal TreeView::doubleClicked(QModelIndex index)
+
+ Emitted when the user presses and holds a valid row in the tree.
+
+ \a index is the model index of the double clicked row in the tree.
+
+ \note This signal is only emitted if the row or item delegate does not accept mouse events.
+
+ The corresponding handler is \c onPressAndHold.
+ */
+ signal doubleClicked(var index)
+
+ /*!
+ \qmlsignal TreeView::pressAndHold(QModelIndex index)
+
+ Emitted when the user presses and holds a valid row in the tree.
+
+ \a index is the model index of the pressed row in the tree.
+
+ \note This signal is only emitted if the row or item delegate does not accept mouse events.
+
+ The corresponding handler is \c onPressAndHold.
+ */
+ signal pressAndHold(var index)
+
+ /*!
+ \qmlsignal TreeView::expanded(QModelIndex index)
+
+ Emitted when a valid row in the tree is expanded, displaying its children.
+
+ \a index is the model index of the expanded row in the tree.
+
+ \note This signal is only emitted if the row or item delegate does not accept mouse events.
+
+ The corresponding handler is \c onExpanded.
+ */
+ signal expanded(var index)
+
+ /*!
+ \qmlsignal TreeView::collapsed(QModelIndex index)
+
+ Emitted when a valid row in the tree is collapsed, hiding its children.
+
+ \a index is the model index of the collapsed row in the tree.
+
+ \note This signal is only emitted if the row or item delegate does not accept mouse events.
+
+ The corresponding handler is \c onCollapsed.
+ */
+ signal collapsed(var index)
+
+ /*!
+ \qmlmethod bool TreeView::isExpanded(QModelIndex index)
+
+ Returns true if the model item index is expanded; otherwise returns false.
+
+ \sa {expanded}, {expand}
+ */
+ function isExpanded(index) {
+ return modelAdaptor.isExpanded(index)
+ }
+
+ /*!
+ \qmlmethod void TreeView::collapse(QModelIndex index)
+
+ Collapses the model item specified by the index.
+
+ \sa {collapsed}, {isExpanded}
+ */
+ function collapse(index) {
+ modelAdaptor.collapse(index)
+ }
+
+ /*!
+ \qmlmethod void TreeView::expand(QModelIndex index)
+
+ Expands the model item specified by the index.
+
+ \sa {expanded}, {isExpanded}
+ */
+ function expand(index) {
+ modelAdaptor.expand(index)
+ }
+
+ style: Qt.createComponent(Settings.style + "/TreeViewStyle.qml", root)
+
+ // Internal stuff. Do not look
+
+ __viewTypeName: "TreeView"
+
+ __model: TreeModelAdaptor {
+ id: modelAdaptor
+ model: root.model
+
+ onExpanded: root.expanded(index)
+ onCollapsed: root.collapsed(index)
+ }
+
+ onSelectionModeChanged: if (!!selection) selection.clear()
+
+ __mouseArea: MouseArea {
+ id: mouseArea
+
+ parent: __listView
+ width: __listView.width
+ height: __listView.height
+ z: -1
+ propagateComposedEvents: true
+ focus: true
+ // Note: with boolean preventStealing we are keeping
+ // the flickable from eating our mouse press events
+ preventStealing: !Settings.hasTouchScreen
+
+ property int clickedRow: -1
+ property int pressedRow: -1
+ property int pressedColumn: -1
+ readonly property alias currentRow: root.__currentRow
+
+ // Handle vertical scrolling whem dragging mouse outside boundaries
+ property int autoScroll: 0 // 0 -> do nothing; 1 -> increment; 2 -> decrement
+ property bool shiftPressed: false // forward shift key state to the autoscroll timer
+
+ Timer {
+ running: mouseArea.autoScroll !== 0 && __verticalScrollBar.visible
+ interval: 20
+ repeat: true
+ onTriggered: {
+ var oldPressedRow = mouseArea.pressedRow
+ var row
+ if (mouseArea.autoScroll === 1) {
+ __listView.incrementCurrentIndexBlocking();
+ row = __listView.indexAt(0, __listView.height + __listView.contentY)
+ if (row === -1)
+ row = __listView.count - 1
+ } else {
+ __listView.decrementCurrentIndexBlocking();
+ row = __listView.indexAt(0, __listView.contentY)
+ }
+
+ if (row !== oldPressedRow) {
+ mouseArea.pressedRow = row
+ var modifiers = mouseArea.shiftPressed ? Qt.ShiftModifier : Qt.NoModifier
+ mouseArea.mouseSelect(row, modifiers, true /* drag */)
+ }
+ }
+ }
+
+ function mouseSelect(row, modifiers, drag) {
+ if (!selection) {
+ maybeWarnAboutSelectionMode()
+ return
+ }
+
+ if (selectionMode) {
+ var modelIndex = modelAdaptor.mapRowToModelIndex(row)
+ if (selectionMode === SelectionMode.SingleSelection) {
+ selection.setCurrentIndex(modelIndex, ItemSelectionModel.NoUpdate)
+ } else {
+ var itemSelection = clickedRow === row ? modelIndex
+ : modelAdaptor.selectionForRowRange(clickedRow, row)
+ if (selectionMode === SelectionMode.MultiSelection
+ || modifiers & Qt.ControlModifier) {
+ if (drag)
+ selection.select(itemSelection, ItemSelectionModel.ToggleCurrent)
+ else
+ selection.select(modelIndex, ItemSelectionModel.Toggle)
+ } else if (modifiers & Qt.ShiftModifier) {
+ selection.select(itemSelection, ItemSelectionModel.SelectCurrent)
+ } else {
+ clickedRow = row // Needed only when drag is true
+ selection.select(modelIndex, ItemSelectionModel.ClearAndSelect)
+ }
+ }
+ }
+ }
+
+ function keySelect(keyModifiers) {
+ if (selectionMode) {
+ if (!keyModifiers)
+ clickedRow = currentRow
+ if (!(keyModifiers & Qt.ControlModifier))
+ mouseSelect(currentRow, keyModifiers, keyModifiers & Qt.ShiftModifier)
+ }
+ }
+
+ function selected(row) {
+ if (selectionMode === SelectionMode.NoSelection)
+ return false
+
+ var modelIndex = null
+ if (!!selection) {
+ modelIndex = modelAdaptor.mapRowToModelIndex(row)
+ if (modelIndex.valid) {
+ if (selectionMode === SelectionMode.SingleSelection)
+ return selection.currentIndex === modelIndex
+ return selection.hasSelection && selection.isSelected(modelIndex)
+ }
+ }
+
+ return row === currentRow
+ && (selectionMode === SelectionMode.SingleSelection
+ || (selectionMode > SelectionMode.SingleSelection && !selection))
+ }
+
+ function branchDecorationContains(x, y) {
+ var clickedItem = __listView.itemAt(0, y + __listView.contentY)
+ if (!(clickedItem && clickedItem.rowItem))
+ return false
+ var branchDecoration = clickedItem.rowItem.branchDecoration
+ if (!branchDecoration)
+ return false
+ var pos = mapToItem(branchDecoration, x, y)
+ return branchDecoration.contains(Qt.point(pos.x, pos.y))
+ }
+
+ function maybeWarnAboutSelectionMode() {
+ if (selectionMode > SelectionMode.SingleSelection)
+ console.warn("TreeView: Non-single selection is not supported without an ItemSelectionModel.")
+ }
+
+ onPressed: {
+ pressedRow = __listView.indexAt(0, mouseY + __listView.contentY)
+ pressedColumn = __listView.columnAt(mouseX)
+ __listView.forceActiveFocus()
+ if (pressedRow > -1 && !Settings.hasTouchScreen
+ && !branchDecorationContains(mouse.x, mouse.y)) {
+ __listView.currentIndex = pressedRow
+ if (clickedRow === -1)
+ clickedRow = pressedRow
+ mouseSelect(pressedRow, mouse.modifiers, false)
+ if (!mouse.modifiers)
+ clickedRow = pressedRow
+ }
+ }
+
+ onReleased: {
+ pressedRow = -1
+ pressedColumn = -1
+ autoScroll = 0
+ }
+
+ onPositionChanged: {
+ // NOTE: Testing for pressed is not technically needed, at least
+ // until we decide to support tooltips or some other hover feature
+ if (mouseY > __listView.height && pressed) {
+ if (autoScroll === 1) return;
+ autoScroll = 1
+ } else if (mouseY < 0 && pressed) {
+ if (autoScroll === 2) return;
+ autoScroll = 2
+ } else {
+ autoScroll = 0
+ }
+
+ if (pressed && containsMouse) {
+ var oldPressedRow = pressedRow
+ pressedRow = __listView.indexAt(0, mouseY + __listView.contentY)
+ pressedColumn = __listView.columnAt(mouseX)
+ if (pressedRow > -1 && oldPressedRow !== pressedRow) {
+ __listView.currentIndex = pressedRow
+ mouseSelect(pressedRow, mouse.modifiers, true /* drag */)
+ }
+ }
+ }
+
+ onExited: {
+ pressedRow = -1
+ pressedColumn = -1
+ }
+
+ onCanceled: {
+ pressedRow = -1
+ pressedColumn = -1
+ autoScroll = 0
+ }
+
+ onClicked: {
+ var clickIndex = __listView.indexAt(0, mouseY + __listView.contentY)
+ if (clickIndex > -1) {
+ var modelIndex = modelAdaptor.mapRowToModelIndex(clickIndex)
+ if (branchDecorationContains(mouse.x, mouse.y)) {
+ if (modelAdaptor.isExpanded(modelIndex))
+ modelAdaptor.collapse(modelIndex)
+ else
+ modelAdaptor.expand(modelIndex)
+ } else if (root.__activateItemOnSingleClick) {
+ root.activated(modelIndex)
+ }
+ root.clicked(modelIndex)
+ }
+ }
+
+ onDoubleClicked: {
+ var clickIndex = __listView.indexAt(0, mouseY + __listView.contentY)
+ if (clickIndex > -1) {
+ var modelIndex = modelAdaptor.mapRowToModelIndex(clickIndex)
+ if (!root.__activateItemOnSingleClick)
+ root.activated(modelIndex)
+ root.doubleClicked(modelIndex)
+ }
+ }
+
+ onPressAndHold: {
+ var pressIndex = __listView.indexAt(0, mouseY + __listView.contentY)
+ if (pressIndex > -1) {
+ var modelIndex = modelAdaptor.mapRowToModelIndex(pressIndex)
+ root.pressAndHold(modelIndex)
+ }
+ }
+
+ Keys.forwardTo: [root]
+
+ Keys.onUpPressed: {
+ event.accepted = __listView.decrementCurrentIndexBlocking()
+ keySelect(event.modifiers)
+ }
+
+ Keys.onDownPressed: {
+ event.accepted = __listView.incrementCurrentIndexBlocking()
+ keySelect(event.modifiers)
+ }
+
+ Keys.onRightPressed: {
+ if (root.currentIndex.valid)
+ root.expand(currentIndex)
+ else
+ event.accepted = false
+ }
+
+ Keys.onLeftPressed: {
+ if (root.currentIndex.valid)
+ root.collapse(currentIndex)
+ else
+ event.accepted = false
+ }
+
+ Keys.onReturnPressed: {
+ if (root.currentIndex.valid)
+ root.activated(currentIndex)
+ else
+ event.accepted = false
+ }
+
+ Keys.onPressed: {
+ __listView.scrollIfNeeded(event.key)
+
+ if (event.key === Qt.Key_A && event.modifiers & Qt.ControlModifier
+ && !!selection && selectionMode > SelectionMode.SingleSelection) {
+ var sel = modelAdaptor.selectionForRowRange(0, __listView.count - 1)
+ selection.select(sel, ItemSelectionModel.SelectCurrent)
+ } else if (event.key === Qt.Key_Shift) {
+ shiftPressed = true
+ }
+ }
+
+ Keys.onReleased: {
+ if (event.key === Qt.Key_Shift)
+ shiftPressed = false
+ }
+ }
+}
diff --git a/src/controls/controls.pro b/src/controls/controls.pro
index 78edc913c..6b54028fb 100644
--- a/src/controls/controls.pro
+++ b/src/controls/controls.pro
@@ -32,6 +32,7 @@ CONTROLS_QML_FILES = \
TabView.qml \
TableView.qml \
TableViewColumn.qml \
+ TreeView.qml \
TextArea.qml \
TextField.qml \
ToolBar.qml \
diff --git a/src/controls/doc/images/qtquickcontrols-example-filesystembrowser.png b/src/controls/doc/images/qtquickcontrols-example-filesystembrowser.png
new file mode 100644
index 000000000..b28431017
--- /dev/null
+++ b/src/controls/doc/images/qtquickcontrols-example-filesystembrowser.png
Binary files differ
diff --git a/src/controls/doc/images/treeview.png b/src/controls/doc/images/treeview.png
new file mode 100644
index 000000000..aac520a3e
--- /dev/null
+++ b/src/controls/doc/images/treeview.png
Binary files differ
diff --git a/src/controls/doc/src/qtquickcontrols-examples.qdoc b/src/controls/doc/src/qtquickcontrols-examples.qdoc
index 29c8d2b55..36b59bbe7 100644
--- a/src/controls/doc/src/qtquickcontrols-examples.qdoc
+++ b/src/controls/doc/src/qtquickcontrols-examples.qdoc
@@ -502,3 +502,17 @@
\include examples-run.qdocinc
*/
+
+/*!
+ \example filesystembrowser
+ \title Qt Quick Controls - File System Browser Example
+ \ingroup qtquickcontrols_examples
+ \brief An example for the TreeView control.
+ \image qtquickcontrols-example-filesystembrowser.png
+
+ This example project demonstrates the usage of \l {TreeView} from
+ \l{Qt Quick Controls} - a control to display a tree representation of items
+ from a model derived from the QAbstractItemModel class.
+
+ The example displays the home path data given by the QFileSystemModel model.
+*/
diff --git a/src/controls/plugin.cpp b/src/controls/plugin.cpp
index 0a652d7f0..27cdebd65 100644
--- a/src/controls/plugin.cpp
+++ b/src/controls/plugin.cpp
@@ -51,6 +51,7 @@
#include "Private/qquickspinboxvalidator_p.h"
#include "Private/qquickabstractstyle_p.h"
#include "Private/qquickcontrolsprivate_p.h"
+#include "Private/qquicktreemodeladaptor_p.h"
#ifdef QT_WIDGETS_LIB
#include <QtQuick/qquickimageprovider.h>
@@ -104,7 +105,9 @@ static const struct {
{ "BusyIndicator", 1, 1 },
- { "TextArea", 1, 3 }
+ { "TextArea", 1, 3 },
+
+ { "TreeView", 1, 4 }
};
void QtQuickControlsPlugin::registerTypes(const char *uri)
@@ -143,11 +146,15 @@ void QtQuickControlsPlugin::initializeEngine(QQmlEngine *engine, const char *uri
qmlRegisterType<QQuickSpinBoxValidator>(private_uri, 1, 0, "SpinBoxValidator");
qmlRegisterSingletonType<QQuickTooltip>(private_uri, 1, 0, "Tooltip", QQuickControlsPrivate::registerTooltipModule);
qmlRegisterSingletonType<QQuickControlSettings>(private_uri, 1, 0, "Settings", QQuickControlsPrivate::registerSettingsModule);
+ qmlRegisterType<QQuickTreeModelAdaptor>(private_uri, 1, 0, "TreeModelAdaptor");
qmlRegisterType<QQuickMenu>(private_uri, 1, 0, "MenuPrivate");
qmlRegisterType<QQuickMenuBar>(private_uri, 1, 0, "MenuBarPrivate");
qmlRegisterType<QQuickPopupWindow>(private_uri, 1, 0, "PopupWindow");
+ qmlRegisterUncreatableType<QAbstractItemModel>(private_uri, 1, 0, "AbstractItemModel",
+ QLatin1String("AbstractItemModel is an abstract type."));
+
#ifdef QT_WIDGETS_LIB
qmlRegisterType<QQuickStyleItem>(private_uri, 1, 0, "StyleItem");
engine->addImageProvider("__tablerow", new QQuickTableRowImageProvider);
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index 810b46787..97cfd32f4 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -1,3 +1,3 @@
TEMPLATE = subdirs
-SUBDIRS += testplugin controls activeFocusOnTab applicationwindow dialogs
+SUBDIRS += testplugin controls activeFocusOnTab applicationwindow dialogs qquicktreemodeladaptor
controls.depends = testplugin
diff --git a/tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro b/tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro
new file mode 100644
index 000000000..61b27acb2
--- /dev/null
+++ b/tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro
@@ -0,0 +1,12 @@
+TEMPLATE = app
+TARGET = tst_qquicktreemodeladaptor
+
+CONFIG += testcase
+CONFIG -= app_bundle
+QT = core testlib
+
+INCLUDEPATH += $$PWD/../../../src/controls/Private
+HEADERS += $$PWD/../../../src/controls/Private/qquicktreemodeladaptor_p.h \
+ $$PWD/../shared/qtestmodel.h
+SOURCES += $$PWD/tst_qquicktreemodeladaptor.cpp \
+ $$PWD/../../../src/controls/Private/qquicktreemodeladaptor.cpp
diff --git a/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp b/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp
new file mode 100644
index 000000000..aae0e5eac
--- /dev/null
+++ b/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp
@@ -0,0 +1,1135 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtCore>
+#include <qquicktreemodeladaptor_p.h>
+#include "../shared/qtestmodel.h"
+
+class tst_QQuickTreeModelAdaptor : public QObject
+{
+ Q_OBJECT
+
+public:
+ void compareData(int row, QQuickTreeModelAdaptor &tma, const QModelIndex &idx, QtTestModel &model, bool expanded = false);
+ void compareModels(QQuickTreeModelAdaptor &tma, QtTestModel &model);
+ void expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma, bool expandable, int expectedRowCountDifference);
+ void collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma, bool expandable, int expectedRowCountDifference);
+
+private slots:
+ void initTestCase();
+ void cleanup();
+
+ void setModel();
+ void modelReset();
+
+ void dataAccess();
+ void dataChange();
+ void groupedDataChange();
+
+ void expandAndCollapse_data();
+ void expandAndCollapse();
+ void expandAndCollapse2ndLevel();
+
+ void layoutChange();
+
+ void removeRows_data();
+ void removeRows();
+
+ void insertRows_data();
+ void insertRows();
+
+ void moveRows_data();
+ void moveRows();
+
+ void selectionForRowRange();
+};
+
+void tst_QQuickTreeModelAdaptor::initTestCase()
+{
+}
+
+void tst_QQuickTreeModelAdaptor::cleanup()
+{
+}
+
+void tst_QQuickTreeModelAdaptor::compareData(int row, QQuickTreeModelAdaptor &tma, const QModelIndex &modelIdx, QtTestModel &model, bool expanded)
+{
+ const QModelIndex &tmaIdx = tma.index(row);
+ QCOMPARE(tma.mapToModel(tmaIdx), modelIdx);
+ QCOMPARE(tma.data(tmaIdx, Qt::DisplayRole).toString(), model.displayData(modelIdx));
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::DepthRole).toInt(), model.level(modelIdx));
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::ExpandedRole).toBool(), expanded);
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::HasChildrenRole).toBool(), model.hasChildren(modelIdx));
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma, bool expandable,
+ int expectedRowCountDifference)
+{
+ QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
+ QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(QModelIndex,int,int)));
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+
+ int oldRowCount = tma.rowCount();
+ tma.expand(idx);
+ QCOMPARE(tma.isExpanded(idx), expandable);
+
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(idx));
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::ExpandedRole).toBool(), expandable);
+
+ if (expandable) {
+ // Rows were added below the parent
+ QCOMPARE(tma.rowCount(), oldRowCount + expectedRowCountDifference);
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), rowsInsertedSpy.count());
+ QVERIFY(rowsInsertedSpy.count() > 0);
+ if (rowsInsertedSpy.count() == 1) {
+ const QVariantList &rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.takeFirst();
+ const QVariantList &rowsInsertedArgs = rowsInsertedSpy.takeFirst();
+ for (int i = 0; i < rowsInsertedArgs.count(); i++)
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(i), rowsInsertedArgs.at(i));
+ QCOMPARE(rowsInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsInsertedArgs.at(1).toInt(), tma.itemIndex(idx) + 1);
+ QCOMPARE(rowsInsertedArgs.at(2).toInt(), tma.itemIndex(idx) + expectedRowCountDifference);
+ }
+
+ // Data changed for the parent's ExpandedRole (value checked above)
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, QQuickTreeModelAdaptor::ExpandedRole));
+ } else {
+ QCOMPARE(tma.rowCount(), oldRowCount);
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma,
+ bool expandable, int expectedRowCountDifference)
+{
+ QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
+ QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(QModelIndex,int,int)));
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+
+ int oldRowCount = tma.rowCount();
+ tma.collapse(idx);
+ QVERIFY(!tma.isExpanded(idx));
+
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(idx));
+ if (tmaIdx.isValid())
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::ExpandedRole).toBool(), false);
+
+ if (expandable) {
+ // Rows were removed below the parent
+ QCOMPARE(tma.rowCount(), oldRowCount - expectedRowCountDifference);
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ const QVariantList &rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.takeFirst();
+ const QVariantList &rowsRemovedArgs = rowsRemovedSpy.takeFirst();
+ for (int i = 0; i < rowsRemovedArgs.count(); i++)
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(i), rowsRemovedArgs.at(i));
+ QCOMPARE(rowsRemovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsRemovedArgs.at(1).toInt(), tma.itemIndex(idx) + 1);
+ QCOMPARE(rowsRemovedArgs.at(2).toInt(), tma.itemIndex(idx) + expectedRowCountDifference);
+
+ // Data changed for the parent's ExpandedRole (value checked above)
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, QQuickTreeModelAdaptor::ExpandedRole));
+ } else {
+ QCOMPARE(tma.rowCount(), oldRowCount);
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::compareModels(QQuickTreeModelAdaptor &tma, QtTestModel &model)
+{
+ QModelIndex parent;
+ QStack<QModelIndex> parents;
+ QModelIndex idx = model.index(0, 0);
+ int modelVisibleRows = model.rowCount(parent);
+ for (int i = 0; i < tma.rowCount(); i++) {
+ bool expanded = tma.isExpanded(i);
+ compareData(i, tma, idx, model, expanded);
+ if (expanded) {
+ parents.push(parent);
+ parent = idx;
+ modelVisibleRows += model.rowCount(parent);
+ idx = model.index(0, 0, parent);
+ } else {
+ while (idx.row() == model.rowCount(parent) - 1) {
+ if (parents.isEmpty())
+ break;
+ idx = parent;
+ parent = parents.pop();
+ }
+ idx = model.index(idx.row() + 1, 0, parent);
+ }
+ }
+ QCOMPARE(tma.rowCount(), modelVisibleRows);
+
+ // Duplicates the model inspection above, but provides extra tests
+ QVERIFY(tma.testConsistency());
+}
+
+void tst_QQuickTreeModelAdaptor::setModel()
+{
+ QtTestModel model(5, 1);
+ QQuickTreeModelAdaptor tma;
+
+ QSignalSpy modelChangedSpy(&tma, SIGNAL(modelChanged(QAbstractItemModel*)));
+ tma.setModel(&model);
+ QCOMPARE(modelChangedSpy.count(), 1);
+ QCOMPARE(tma.model(), &model);
+
+ // Set same model twice
+ tma.setModel(&model);
+ QCOMPARE(modelChangedSpy.count(), 1);
+
+ modelChangedSpy.clear();
+ tma.setModel(0);
+ QCOMPARE(modelChangedSpy.count(), 1);
+ QCOMPARE(tma.model(), static_cast<QAbstractItemModel *>(0));
+}
+
+void tst_QQuickTreeModelAdaptor::modelReset()
+{
+ QtTestModel model(5, 1);
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QSignalSpy modelAboutToBeResetSpy(&tma, SIGNAL(modelAboutToBeReset()));
+ QSignalSpy modelResetSpy(&tma, SIGNAL(modelReset()));
+
+ // Nothing expanded
+ model.resetModel();
+ QCOMPARE(modelAboutToBeResetSpy.count(), 1);
+ QCOMPARE(modelResetSpy.count(), 1);
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+
+ // Expanded items should not be anymore
+ tma.expand(model.index(0, 0));
+ tma.expand(model.index(2, 0));
+ tma.expand(model.index(2, 0, model.index(2, 0)));
+ modelAboutToBeResetSpy.clear();
+ modelResetSpy.clear();
+ model.resetModel();
+ QCOMPARE(modelAboutToBeResetSpy.count(), 1);
+ QCOMPARE(modelResetSpy.count(), 1);
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::dataAccess()
+{
+ QtTestModel model(5, 1);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+
+ QModelIndex parentIdx = model.index(2, 0);
+ QVERIFY(model.hasChildren(parentIdx));
+ tma.expand(parentIdx);
+ QVERIFY(tma.isExpanded(parentIdx));
+ QCOMPARE(tma.rowCount(), model.rowCount() + model.rowCount(parentIdx));
+ compareModels(tma, model);
+
+ tma.collapse(parentIdx);
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::dataChange()
+{
+ QtTestModel model(5, 1);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ const QModelIndex &idx = model.index(2, 0);
+ model.setData(idx, QVariant(), Qt::DisplayRole);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(idx));
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, Qt::DisplayRole));
+ compareModels(tma, model);
+
+ {
+ // Non expanded children shouldn't emit any signal
+ dataChangedSpy.clear();
+ const QModelIndex &childIdx = model.index(4, 0, idx);
+ model.setData(childIdx, QVariant(), Qt::DisplayRole);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ compareModels(tma, model);
+
+ // But expanded children should
+ tma.expand(idx);
+ QVERIFY(tma.isExpanded(idx));
+ dataChangedSpy.clear(); // expand() emits dataChanged() with ExpandedRole
+ model.setData(childIdx, QVariant(), Qt::DisplayRole);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(childIdx));
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, Qt::DisplayRole));
+ compareModels(tma, model);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::groupedDataChange()
+{
+ QtTestModel model(10, 1);
+ const QModelIndex &topLeftIdx = model.index(1, 0);
+ const QModelIndex &bottomRightIdx = model.index(7, 0);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ const QVector<int> roles(1, Qt::DisplayRole);
+
+ {
+ // No expanded items
+ model.groupedSetData(topLeftIdx, bottomRightIdx, roles);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ compareModels(tma, model);
+
+ const QModelIndex &tmaTLIdx = tma.index(tma.itemIndex(topLeftIdx));
+ const QModelIndex &tmaBRIdx = tma.index(tma.itemIndex(bottomRightIdx));
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+ }
+
+ // One item expanded in the group range
+ const QModelIndex &expandedIdx = model.index(4, 0);
+ tma.expand(expandedIdx);
+ QVERIFY(tma.isExpanded(expandedIdx));
+
+ for (int i = 0; i < 2; i++) {
+ const QModelIndex &tmaTLIdx = tma.index(tma.itemIndex(topLeftIdx));
+ const QModelIndex &tmaExpandedIdx = tma.index(tma.itemIndex(expandedIdx));
+ const QModelIndex &tmaExpandedSiblingIdx = tma.index(tma.itemIndex(expandedIdx.sibling(expandedIdx.row() + 1, 0)));
+ const QModelIndex &tmaBRIdx = tma.index(tma.itemIndex(bottomRightIdx));
+
+ dataChangedSpy.clear(); // expand() sends a dataChaned() signal
+ model.groupedSetData(topLeftIdx, bottomRightIdx, roles);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ compareModels(tma, model);
+
+ QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaExpandedIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaExpandedSiblingIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ // Further expanded descendants should not change grouping
+ tma.expand(model.index(0, 0, expandedIdx));
+ QVERIFY(tma.isExpanded(expandedIdx));
+ }
+ tma.collapse(model.index(0, 0, expandedIdx));
+
+ // Let's expand one more and see what happens...
+ const QModelIndex &otherExpandedIdx = model.index(6, 0);
+ tma.expand(otherExpandedIdx);
+ QVERIFY(tma.isExpanded(otherExpandedIdx));
+
+ for (int i = 0; i < 3; i++) {
+ const QModelIndex &tmaTLIdx = tma.index(tma.itemIndex(topLeftIdx));
+ const QModelIndex &tmaExpandedIdx = tma.index(tma.itemIndex(expandedIdx));
+ const QModelIndex &tmaExpandedSiblingIdx = tma.index(tma.itemIndex(expandedIdx.sibling(expandedIdx.row() + 1, 0)));
+ const QModelIndex &tmaOtherExpandedIdx = tma.index(tma.itemIndex(otherExpandedIdx));
+ const QModelIndex &tmaOtherExpandedSiblingIdx = tma.index(tma.itemIndex(otherExpandedIdx.sibling(otherExpandedIdx.row() + 1, 0)));
+ const QModelIndex &tmaBRIdx = tma.index(tma.itemIndex(bottomRightIdx));
+
+ dataChangedSpy.clear(); // expand() sends a dataChaned() signal
+ model.groupedSetData(topLeftIdx, bottomRightIdx, roles);
+ QCOMPARE(dataChangedSpy.count(), 3);
+ compareModels(tma, model);
+
+ QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaExpandedIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaExpandedSiblingIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaOtherExpandedIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaOtherExpandedSiblingIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ // Further expanded descendants should not change grouping
+ if (i == 0) {
+ tma.expand(model.index(0, 0, expandedIdx));
+ QVERIFY(tma.isExpanded(expandedIdx));
+ } else {
+ tma.expand(model.index(0, 0, otherExpandedIdx));
+ QVERIFY(tma.isExpanded(expandedIdx));
+ }
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndCollapse_data()
+{
+ QTest::addColumn<int>("parentRow");
+ QTest::newRow("First") << 0;
+ QTest::newRow("Middle") << 2;
+ QTest::newRow("Last") << 4;
+ QTest::newRow("Non expandable") << 3;
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndCollapse()
+{
+ QFETCH(int, parentRow);
+ QtTestModel model(5, 1);
+ const QModelIndex &parentIdx = model.index(parentRow, 0);
+ bool expandable = model.hasChildren(parentIdx);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ expandAndTest(parentIdx, tma, expandable, model.rowCount(parentIdx));
+ compareModels(tma, model);
+
+ collapseAndTest(parentIdx, tma, expandable, model.rowCount(parentIdx));
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndCollapse2ndLevel()
+{
+ const int expandRows[] = { 0, 2, 4, 3 };
+ const int expandRowsCount = sizeof(expandRows) / sizeof(expandRows[0]);
+ for (int i = 0; i < expandRowsCount - 1; i++) { // Skip last non-expandable row
+ QtTestModel model(5, 1);
+ const QModelIndex &parentIdx = model.index(expandRows[i], 0);
+ QVERIFY(model.hasChildren(parentIdx));
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ tma.expand(parentIdx);
+ QVERIFY(tma.isExpanded(parentIdx));
+ QCOMPARE(tma.rowCount(), model.rowCount() + model.rowCount(parentIdx));
+
+ for (int j = 0; j < expandRowsCount; j++) {
+ const QModelIndex &childIdx = model.index(expandRows[j], 0, parentIdx);
+ bool expandable = model.hasChildren(childIdx);
+
+ // Expand child
+ expandAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ // Collapse child
+ collapseAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+
+ // Expand child again
+ expandAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ // Collapse parent -> child node invisible, but expanded
+ collapseAndTest(parentIdx, tma, true, model.rowCount(parentIdx) + model.rowCount(childIdx));
+ compareModels(tma, model);
+ QCOMPARE(tma.isExpanded(childIdx), expandable);
+ // Expand parent again
+ expandAndTest(parentIdx, tma, true, model.rowCount(parentIdx) + model.rowCount(childIdx));
+ compareModels(tma, model);
+
+ // Collapse parent -> child node invisible, but expanded
+ collapseAndTest(parentIdx, tma, true, model.rowCount(parentIdx) + model.rowCount(childIdx));
+ compareModels(tma, model);
+ QCOMPARE(tma.isExpanded(childIdx), expandable);
+ // Collapse child -> nothing should change
+ collapseAndTest(childIdx, tma, false, 0);
+ compareModels(tma, model);
+ // Expand parent again
+ expandAndTest(parentIdx, tma, true, model.rowCount(parentIdx));
+ compareModels(tma, model);
+
+ // Expand child, one last time
+ expandAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ // Collapse child, and done
+ collapseAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ }
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::layoutChange()
+{
+ QtTestModel model(5, 1);
+ const QModelIndex &idx = model.index(0, 0);
+ const QModelIndex &idx2 = model.index(2, 0);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ // Nothing expanded
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ model.changeLayout();
+ QCOMPARE(dataChangedSpy.count(), 1);
+ QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(0));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.rowCount() - 1));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // One item expanded
+ tma.expand(idx);
+ QVERIFY(tma.isExpanded(idx));
+ dataChangedSpy.clear();
+ model.changeLayout();
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(0));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.rowCount() - 1));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // One parent layout change, expanded
+ dataChangedSpy.clear();
+ QList<QPersistentModelIndex> parents;
+ parents << idx;
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx) - 1, 0, idx))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // One parent layout change, collapsed
+ tma.collapse(idx);
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ compareModels(tma, model);
+
+ // Two-parent layout change, both collapsed
+ parents << idx2;
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ compareModels(tma, model);
+
+ // Two-parent layout change, only one expanded
+ tma.expand(idx2);
+ QVERIFY(tma.isExpanded(idx2));
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx2))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx2) - 1, 0, idx2))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // Two-parent layout change, both expanded
+ tma.expand(idx);
+ QVERIFY(tma.isExpanded(idx));
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx) - 1, 0, idx))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx2))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx2) - 1, 0, idx2))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+}
+
+static const int ModelRowCount = 9;
+
+void tst_QQuickTreeModelAdaptor::removeRows_data()
+{
+ QTest::addColumn<int>("removeFromRow");
+ QTest::addColumn<int>("removeCount");
+ QTest::addColumn<int>("removeParentRow");
+ QTest::addColumn<int>("expandRow");
+ QTest::addColumn<int>("expandParentRow");
+ QTest::addColumn<int>("expectedRemovedCount");
+
+ QTest::newRow("Nothing expanded, remove 1st row") << 0 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, remove 1st row") << 0 << 1 << -1 << 0 << -1 << 1 + ModelRowCount;
+ QTest::newRow("Expand last row, remove 1st row") << 0 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Nothing expanded, remove last row") << ModelRowCount - 1 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, remove last row") << ModelRowCount - 1 << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand last row, remove last row") << ModelRowCount - 1 << 1 << -1 << ModelRowCount - 1 << -1 << 1 + ModelRowCount;
+ QTest::newRow("Remove child row, parent collapsed") << 2 << 1 << 0 << -1 << -1 << 0;
+ QTest::newRow("Remove child row, parent expanded") << 2 << 1 << 0 << 0 << -1 << 1;
+ QTest::newRow("Remove several rows, nothing expanded") << 2 << 5 << -1 << -1 << -1 << 5;
+ QTest::newRow("Remove several rows, 1st row expanded") << 2 << 5 << -1 << 0 << -1 << 5;
+ QTest::newRow("Remove several rows, last row expanded") << 2 << 5 << -1 << ModelRowCount - 1 << -1 << 5;
+ QTest::newRow("Remove several rows, one of them expanded") << 2 << 5 << -1 << 4 << -1 << 5 + ModelRowCount;
+ QTest::newRow("Remove all rows, nothing expanded") << 0 << ModelRowCount << -1 << -1 << -1 << ModelRowCount;
+ QTest::newRow("Remove all rows, 1st row expanded") << 0 << ModelRowCount << -1 << 0 << -1 << ModelRowCount * 2;
+ QTest::newRow("Remove all rows, last row expanded") << 0 << ModelRowCount << -1 << ModelRowCount - 1 << -1 << ModelRowCount * 2;
+ QTest::newRow("Remove all rows, random one expanded") << 0 << ModelRowCount << -1 << 4 << -1 << ModelRowCount * 2;
+}
+
+void tst_QQuickTreeModelAdaptor::removeRows()
+{
+ QFETCH(int, removeFromRow);
+ QFETCH(int, removeCount);
+ QFETCH(int, removeParentRow);
+ QFETCH(int, expandRow);
+ QFETCH(int, expandParentRow);
+ QFETCH(int, expectedRemovedCount);
+
+ QtTestModel model(ModelRowCount, 1);
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(expandParentRow, 0);
+ if (expandParentIdx.isValid()) {
+ tma.expand(expandParentIdx);
+ QVERIFY(tma.isExpanded(expandParentIdx));
+ }
+ const QModelIndex &expandIdx = model.index(expandRow, 0, expandParentIdx);
+ if (expandIdx.isValid()) {
+ tma.expand(expandIdx);
+ QVERIFY(tma.isExpanded(expandIdx));
+ }
+
+ const QModelIndex &removeParentIdx = removeParentRow == -1 ? QModelIndex() : model.index(removeParentRow, 0);
+ const QModelIndex &removeIdx = model.index(removeFromRow, 0, removeParentIdx);
+ int tmaItemIdx = tma.itemIndex(removeIdx);
+
+ QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
+ QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
+ model.removeRows(removeFromRow, removeCount, removeParentIdx);
+ if (expectedRemovedCount == 0) {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ } else {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
+ QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
+ QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaItemIdx);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaItemIdx + expectedRemovedCount - 1);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::insertRows_data()
+{
+ QTest::addColumn<int>("insertFromRow");
+ QTest::addColumn<int>("insertCount");
+ QTest::addColumn<int>("insertParentRow");
+ QTest::addColumn<int>("expandRow");
+ QTest::addColumn<int>("expandParentRow");
+ QTest::addColumn<int>("expectedInsertedCount");
+
+ QTest::newRow("Nothing expanded, insert 1st row") << 0 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, insert 1st row") << 0 << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand last row, insert 1st row") << 0 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Nothing expanded, insert before the last row") << ModelRowCount - 1 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Nothing expanded, insert after the last row") << ModelRowCount << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, insert before the last row") << ModelRowCount - 1 << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand 1st row, insert after the last row") << ModelRowCount << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand last row, insert before the last row") << ModelRowCount - 1 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Expand last row, insert after the last row") << ModelRowCount << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Insert child row, parent collapsed") << 2 << 1 << 0 << -1 << -1 << 0;
+ QTest::newRow("Insert child row, parent expanded") << 2 << 1 << 0 << 0 << -1 << 1;
+ QTest::newRow("Insert several rows, nothing expanded") << 2 << 5 << -1 << -1 << -1 << 5;
+ QTest::newRow("Insert several rows, 1st row expanded") << 2 << 5 << -1 << 0 << -1 << 5;
+ QTest::newRow("Insert several rows, last row expanded") << 2 << 5 << -1 << ModelRowCount - 1 << -1 << 5;
+}
+
+void tst_QQuickTreeModelAdaptor::insertRows()
+{
+ QFETCH(int, insertFromRow);
+ QFETCH(int, insertCount);
+ QFETCH(int, insertParentRow);
+ QFETCH(int, expandRow);
+ QFETCH(int, expandParentRow);
+ QFETCH(int, expectedInsertedCount);
+
+ QtTestModel model(ModelRowCount, 1);
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(expandParentRow, 0);
+ if (expandParentIdx.isValid()) {
+ tma.expand(expandParentIdx);
+ QVERIFY(tma.isExpanded(expandParentIdx));
+ }
+ const QModelIndex &expandIdx = model.index(expandRow, 0, expandParentIdx);
+ if (expandIdx.isValid()) {
+ tma.expand(expandIdx);
+ QVERIFY(tma.isExpanded(expandIdx));
+ }
+
+ const QModelIndex &insertParentIdx = insertParentRow == -1 ? QModelIndex() : model.index(insertParentRow, 0);
+ const QModelIndex &insertIdx = model.index(insertFromRow, 0, insertParentIdx);
+ int tmaItemIdx = insertFromRow == model.rowCount(insertParentIdx) ? tma.rowCount() : tma.itemIndex(insertIdx);
+
+ QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)));
+ QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
+ model.insertRows(insertFromRow, insertCount, insertParentIdx);
+ if (expectedInsertedCount == 0) {
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ } else {
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QVariantList rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.first();
+ QVariantList rowsInsertedArgs = rowsInsertedSpy.first();
+ QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaItemIdx);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaItemIdx + expectedInsertedCount - 1);
+ QCOMPARE(tma.itemIndex(model.index(insertFromRow, 0, insertParentIdx)), tmaItemIdx);
+ }
+}
+
+enum MoveSignalType {
+ RowsMoved = 0, RowsInserted, RowsRemoved
+};
+
+void tst_QQuickTreeModelAdaptor::moveRows_data()
+{
+ QTest::addColumn<int>("sourceRow");
+ QTest::addColumn<bool>("expandSource");
+ QTest::addColumn<int>("moveCount");
+ QTest::addColumn<int>("sourceParentRow");
+ QTest::addColumn<bool>("expandSourceParent");
+ QTest::addColumn<int>("destRow");
+ QTest::addColumn<bool>("expandDest");
+ QTest::addColumn<int>("destParentRow");
+ QTest::addColumn<bool>("expandDestParent");
+ QTest::addColumn<int>("expandRow");
+ QTest::addColumn<int>("expandParentRow");
+ QTest::addColumn<int>("signalType");
+ QTest::addColumn<int>("expectedMovedCount");
+
+ QTest::newRow("From and to top-level parent")
+ << 0 << false << 1 << -1 << false
+ << 3 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to top-level parent, expanded")
+ << 0 << true << 1 << -1 << false
+ << 3 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to top-level parent, backwards")
+ << 4 << false << 1 << -1 << false
+ << 0 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to top-level parent, expanded, backwards")
+ << 4 << true << 1 << -1 << false
+ << 0 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("Moving between collapsed parents")
+ << 0 << false << 1 << 0 << false
+ << 0 << false << 2 << false
+ << -1 << -1 << (int)RowsMoved << 0;
+ QTest::newRow("From expanded parent to collapsed parent")
+ << 0 << false << 1 << 0 << true
+ << 0 << false << 2 << false
+ << -1 << -1 << (int)RowsRemoved << 1;
+ QTest::newRow("From collapsed parent to expanded parent")
+ << 0 << false << 1 << 0 << false
+ << 0 << false << 2 << true
+ << -1 << -1 << (int)RowsInserted << 1;
+ QTest::newRow("From and to same expanded parent")
+ << 0 << false << 1 << 0 << true
+ << 2 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From expanded parent to collapsed parent, expanded row")
+ << 0 << true << 1 << 0 << true
+ << 0 << false << 2 << false
+ << -1 << -1 << (int)RowsRemoved << ModelRowCount + 1;
+ QTest::newRow("From collapsed parent to expanded parent, expanded row")
+ << 0 << true << 1 << 0 << false
+ << 0 << false << 2 << true
+ << -1 << -1 << (int)RowsInserted << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, forward")
+ << 0 << true << 1 << 0 << true
+ << 5 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, last row")
+ << 0 << true << 1 << 0 << true
+ << ModelRowCount << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, several")
+ << 0 << true << 3 << 0 << true
+ << 5 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 3;
+ QTest::newRow("From and to same expanded parent, expanded row, backward")
+ << 6 << true << 1 << 0 << true
+ << 0 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, several, backward")
+ << 6 << true << 2 << 0 << true
+ << 0 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 2;
+ QTest::newRow("From and to different expanded parents")
+ << 0 << false << 1 << 0 << true
+ << 1 << false << 4 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, backward")
+ << 0 << false << 1 << 4 << true
+ << 2 << false << 0 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, up in level")
+ << 0 << false << 1 << 0 << true
+ << 5 << true << -1 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, up in level, backwards")
+ << 0 << false << 1 << 4 << true
+ << 1 << false << -1 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, up in level, as 1st item")
+ << 0 << false << 1 << 0 << true
+ << 0 << false << -1 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, backward, up in level")
+ << 0 << false << 1 << 4 << true
+ << 2 << false << 0 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+}
+
+void tst_QQuickTreeModelAdaptor::moveRows()
+{
+ QFETCH(int, sourceRow);
+ QFETCH(bool, expandSource);
+ QFETCH(int, moveCount);
+ QFETCH(int, sourceParentRow);
+ QFETCH(bool, expandSourceParent);
+ QFETCH(int, destRow);
+ QFETCH(bool, expandDest);
+ QFETCH(int, destParentRow);
+ QFETCH(bool, expandDestParent);
+ QFETCH(int, expandRow);
+ QFETCH(int, expandParentRow);
+ QFETCH(int, signalType);
+ QFETCH(int, expectedMovedCount);
+
+ QtTestModel model(ModelRowCount, 1);
+ model.alternateChildlessRows = false;
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(expandParentRow, 0);
+ if (expandParentIdx.isValid()) {
+ tma.expand(expandParentIdx);
+ QVERIFY(tma.isExpanded(expandParentIdx));
+ }
+ const QModelIndex &expandIdx = model.index(expandRow, 0, expandParentIdx);
+ if (expandIdx.isValid()) {
+ tma.expand(expandIdx);
+ QVERIFY(tma.isExpanded(expandIdx));
+ }
+
+ const QModelIndex &sourceParentIdx = sourceParentRow == -1 ? QModelIndex() : model.index(sourceParentRow, 0);
+ if (expandSourceParent && sourceParentIdx.isValid()) {
+ tma.expand(sourceParentIdx);
+ QVERIFY(tma.isExpanded(sourceParentIdx));
+ }
+ const QModelIndex &sourceIdx = model.index(sourceRow, 0, sourceParentIdx);
+ if (expandSource) {
+ tma.expand(sourceIdx);
+ QVERIFY(tma.isExpanded(sourceIdx));
+ }
+
+ const QModelIndex &destParentIdx = destParentRow == -1 ? QModelIndex() : model.index(destParentRow, 0);
+ if (expandDestParent && destParentIdx.isValid()) {
+ tma.expand(destParentIdx);
+ QVERIFY(tma.isExpanded(destParentIdx));
+ }
+ const QModelIndex &destIdx = model.index(destRow, 0, destParentIdx);
+ if (expandDest) {
+ tma.expand(destIdx);
+ QVERIFY(tma.isExpanded(destIdx));
+ }
+
+ int tmaSourceItemIdx = signalType == RowsInserted ? -1 // Not tested if RowsInserted
+ : tma.itemIndex(sourceIdx);
+ int tmaDestItemIdx = signalType == RowsRemoved ? -1 : // Not tested if RowsRemoved
+ destRow == model.rowCount(destParentIdx) ? -1 /* FIXME */ : tma.itemIndex(destIdx);
+
+ QSignalSpy rowsAboutToBeMovedSpy(&tma, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
+ QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
+ QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)));
+ QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
+ QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
+ QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
+
+ QVERIFY(model.moveRows(sourceParentIdx, sourceRow, moveCount, destParentIdx, destRow));
+
+ if (signalType != RowsMoved || expectedMovedCount == 0) {
+ QCOMPARE(rowsAboutToBeMovedSpy.count(), 0);
+ QCOMPARE(rowsMovedSpy.count(), 0);
+ }
+ if (signalType != RowsInserted || expectedMovedCount == 0) {
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ }
+ if (signalType != RowsRemoved || expectedMovedCount == 0) {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ }
+
+ if (expectedMovedCount != 0) {
+ if (signalType == RowsMoved) {
+ QCOMPARE(rowsAboutToBeMovedSpy.count(), 1);
+ QCOMPARE(rowsMovedSpy.count(), 1);
+ QVariantList rowsAboutToBeMovedArgs = rowsAboutToBeMovedSpy.first();
+ QVariantList rowsMovedArgs = rowsMovedSpy.first();
+ QCOMPARE(rowsAboutToBeMovedArgs, rowsMovedArgs);
+ QCOMPARE(rowsAboutToBeMovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeMovedArgs.at(1).toInt(), tmaSourceItemIdx);
+ QCOMPARE(rowsAboutToBeMovedArgs.at(2).toInt(), tmaSourceItemIdx + expectedMovedCount - 1);
+ QCOMPARE(rowsAboutToBeMovedArgs.at(3).toModelIndex(), QModelIndex());
+ if (tmaDestItemIdx != -1)
+ QCOMPARE(rowsAboutToBeMovedArgs.at(4).toInt(), tmaDestItemIdx);
+ } else if (signalType == RowsInserted) {
+ // We only test with one level of expanded children here, so we can do
+ // exhaustive testing depending on whether the moved row is expanded.
+ int signalCount = expandSource ? 2 : 1;
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), signalCount);
+ QCOMPARE(rowsInsertedSpy.count(), signalCount);
+ QVariantList rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.takeFirst();
+ QVariantList rowsInsertedArgs = rowsInsertedSpy.takeFirst();
+ QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaDestItemIdx);
+ if (expandSource) {
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaDestItemIdx);
+ rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.first();
+ rowsInsertedArgs = rowsInsertedSpy.first();
+ QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaDestItemIdx + 1);
+ }
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaDestItemIdx + expectedMovedCount - 1);
+ QCOMPARE(tma.itemIndex(model.index(destRow, 0, destParentIdx)), tmaDestItemIdx);
+ } else if (signalType == RowsRemoved) {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
+ QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
+ QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaSourceItemIdx);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaSourceItemIdx + expectedMovedCount - 1);
+ }
+ }
+ QVERIFY(tma.testConsistency());
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::selectionForRowRange()
+{
+ const int ModelRowCount = 9;
+ const int ModelRowCountLoopStep = 4;
+
+ QtTestModel model(ModelRowCount, 1);
+ model.alternateChildlessRows = false;
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ // NOTE: Some selections may look a bit cryptic. Insert a call to
+ // tma.dump() before each block if you need to see what's going on.
+
+ for (int i = 0; i < ModelRowCount; i += ModelRowCountLoopStep) {
+ // Single row selection
+ const QItemSelection &sel = tma.selectionForRowRange(i, i);
+ QCOMPARE(sel.count(), 1);
+ const QItemSelectionRange &range = sel.first();
+ QCOMPARE(QModelIndex(range.topLeft()), model.index(i, 0));
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(i, 0));
+ }
+
+ for (int i = 0; i < ModelRowCount - ModelRowCountLoopStep; i += ModelRowCountLoopStep) {
+ // Single range selection
+ const QItemSelection &sel = tma.selectionForRowRange(i, i + ModelRowCountLoopStep);
+ QCOMPARE(sel.count(), 1);
+ const QItemSelectionRange &range = sel.first();
+ QCOMPARE(QModelIndex(range.topLeft()), model.index(i, 0));
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(i + ModelRowCountLoopStep, 0));
+ }
+
+ { // Select all, no branch expanded
+ const QItemSelection &sel = tma.selectionForRowRange(0, ModelRowCount - 1);
+ QCOMPARE(sel.count(), 1);
+ const QItemSelectionRange &range = sel.first();
+ QCOMPARE(QModelIndex(range.topLeft()), model.index(0, 0));
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0));
+ }
+
+ // Expand 1st top-level item
+ const QModelIndex &parent = model.index(0, 0);
+ tma.expand(parent);
+
+ { // 1st item expanded, select first 5 rows
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4);
+ QCOMPARE(sel.count(), 2);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(3, 0, parent));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ { // 1st item expanded, select first 5 top-level items
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4 + ModelRowCount);
+ QCOMPARE(sel.count(), 2);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(4, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ // Expand 2nd top-level item
+ const QModelIndex &parent2 = model.index(1, 0);
+ tma.expand(parent2);
+
+ { // 1st two items expanded, select first 5 top-level items
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4 + 2 * ModelRowCount);
+ QCOMPARE(sel.count(), 3);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(4, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent2))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent2));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ // Expand 1st child of 1st top-level item
+ const QModelIndex &parent3 = model.index(0, 0, parent);
+ tma.expand(parent3);
+
+ { // 1st two items, and 1st child of 1st item expanded, select first 5 rows
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4);
+ QCOMPARE(sel.count(), 3);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent3))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(2, 0, parent3));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ { // 1st two items, and 1st child of 1st item expanded, select all
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4 * ModelRowCount - 1);
+ QCOMPARE(sel.count(), 4);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent2))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent2));
+ else if (range.topLeft() == model.index(0, 0, parent3))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent3));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ { // 1st two items, and 1st child of 1st item expanded, select rows across branches
+ const QItemSelection &sel = tma.selectionForRowRange(8, 23);
+ QCOMPARE(sel.count(), 4);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(1, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(1, 0));
+ else if (range.topLeft() == model.index(1, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent2))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(3, 0, parent2));
+ else if (range.topLeft() == model.index(6, 0, parent3))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent3));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+}
+
+QTEST_MAIN(tst_QQuickTreeModelAdaptor)
+#include "tst_qquicktreemodeladaptor.moc"
diff --git a/tests/auto/shared/qtestmodel.h b/tests/auto/shared/qtestmodel.h
new file mode 100644
index 000000000..e9472f84e
--- /dev/null
+++ b/tests/auto/shared/qtestmodel.h
@@ -0,0 +1,319 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+#include <QAbstractItemModel>
+
+class QtTestModel: public QAbstractItemModel
+{
+public:
+ QtTestModel(QObject *parent = 0): QAbstractItemModel(parent),
+ fetched(false), rows(10), cols(1), levels(INT_MAX), wrongIndex(false) { init(); }
+
+ QtTestModel(int _rows, int _cols, QObject *parent = 0): QAbstractItemModel(parent),
+ fetched(false), rows(_rows), cols(_cols), levels(INT_MAX), wrongIndex(false) { init(); }
+
+ void init() {
+ decorationsEnabled = false;
+ alternateChildlessRows = true;
+ tree = new Node(rows);
+ }
+
+ inline qint32 level(const QModelIndex &index) const {
+ Node *n = (Node *)index.internalPointer();
+ if (!n)
+ return -1;
+ int l = -1;
+ while (n != tree) {
+ n = n->parent;
+ ++l;
+ }
+ return l;
+ }
+
+ void resetModel()
+ {
+ beginResetModel();
+ fetched = false;
+ delete tree;
+ tree = new Node(rows);
+ endResetModel();
+ }
+
+ QString displayData(const QModelIndex &idx) const
+ {
+ return QString("[%1,%2,%3,%4]").arg(idx.row()).arg(idx.column()).arg(idx.internalId()).arg(hasChildren(idx));
+ }
+
+ bool canFetchMore(const QModelIndex &) const {
+ return !fetched;
+ }
+
+ void fetchMore(const QModelIndex &) {
+ fetched = true;
+ }
+
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const {
+ bool hasFetched = fetched;
+ fetched = true;
+ bool r = QAbstractItemModel::hasChildren(parent);
+ fetched = hasFetched;
+ return r;
+ }
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const {
+ if (!fetched)
+ qFatal("%s: rowCount should not be called before fetching", Q_FUNC_INFO);
+ if ((parent.column() > 0) || (level(parent) > levels)
+ || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1)))
+ return 0;
+ Node *n = (Node*)parent.internalPointer();
+ if (!n)
+ n = tree;
+ return n->children.count();
+ }
+
+ int columnCount(const QModelIndex& parent = QModelIndex()) const {
+ if ((parent.column() > 0) || (level(parent) > levels)
+ || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1)))
+ return 0;
+ return cols;
+ }
+
+ bool isEditable(const QModelIndex &index) const {
+ if (index.isValid())
+ return true;
+ return false;
+ }
+
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const
+ {
+ if (row < 0 || column < 0 || (level(parent) > levels) || column >= cols)
+ return QModelIndex();
+ Node *pn = (Node*)parent.internalPointer();
+ if (!pn)
+ pn = tree;
+ if (row >= pn->children.count())
+ return QModelIndex();
+
+ Node *n = pn->children.at(row);
+ if (!n) {
+ n = new Node(rows, pn);
+ pn->children[row] = n;
+ }
+ return createIndex(row, column, n);
+ }
+
+ QModelIndex parent(const QModelIndex &index) const
+ {
+ Node *n = (Node *)index.internalPointer();
+ if (!n || n->parent == tree)
+ return QModelIndex();
+ Q_ASSERT(n->parent->parent);
+ int parentRow = n->parent->parent->children.indexOf(n->parent);
+ Q_ASSERT(parentRow != -1);
+ return createIndex(parentRow, 0, n->parent);
+ }
+
+ QVariant data(const QModelIndex &idx, int role) const
+ {
+ if (!idx.isValid())
+ return QVariant();
+
+ Node *pn = (Node *)idx.internalPointer();
+ if (!pn)
+ pn = tree;
+ if (pn != tree)
+ pn = pn->parent;
+ if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols
+ || idx.row() >= pn->children.count()) {
+ wrongIndex = true;
+ qWarning("Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(),
+ idx.internalPointer());
+ return QVariant();
+ }
+
+ if (role == Qt::DisplayRole) {
+ return displayData(idx);
+ }
+
+ return QVariant();
+ }
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role)
+ {
+ Q_UNUSED(value);
+ QVector<int> changedRole(1, role);
+ emit dataChanged(index, index, changedRole);
+ return true;
+ }
+
+ void groupedSetData(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
+ {
+ emit dataChanged(topLeft, bottomRight, roles);
+ }
+
+ void changeLayout(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>())
+ {
+ emit layoutAboutToBeChanged(parents);
+ emit layoutChanged(parents);
+ }
+
+ bool removeRows(int row, int count, const QModelIndex &parent)
+ {
+ beginRemoveRows(parent, row, row + count - 1);
+ Node *n = (Node *)parent.internalPointer();
+ if (!n)
+ n = tree;
+ n->removeRows(row, count);
+ endRemoveRows();
+ return true;
+ }
+
+ void removeLastColumn()
+ {
+ beginRemoveColumns(QModelIndex(), cols - 1, cols - 1);
+ --cols;
+ endRemoveColumns();
+ }
+
+ void removeAllColumns()
+ {
+ beginRemoveColumns(QModelIndex(), 0, cols - 1);
+ cols = 0;
+ endRemoveColumns();
+ }
+
+ bool insertRows(int row, int count, const QModelIndex &parent)
+ {
+ beginInsertRows(parent, row, row + count - 1);
+ Node *n = (Node *)parent.internalPointer();
+ if (!n)
+ n = tree;
+ n->addRows(row, count);
+ endInsertRows();
+ return true;
+ }
+
+ bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
+ {
+ Q_ASSERT_X(sourceRow >= 0 && sourceRow < rowCount(sourceParent)
+ && count > 0 && sourceRow + count < rowCount(sourceParent)
+ && destinationChild >= 0 && destinationChild <= rowCount(destinationParent),
+ Q_FUNC_INFO, "Rows out of range.");
+ Q_ASSERT_X(!(sourceParent == destinationParent && destinationChild >= sourceRow && destinationChild < sourceRow + count),
+ Q_FUNC_INFO, "Moving rows onto themselves.");
+ if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild))
+ return false;
+ Node *src = (Node *)sourceParent.internalPointer();
+ if (!src)
+ src = tree;
+ Node *dest = (Node *)destinationParent.internalPointer();
+ if (!dest)
+ dest = tree;
+ QVector<Node *> buffer = src->children.mid(sourceRow, count);
+ if (src != dest) {
+ src->removeRows(sourceRow, count, true /* keep alive */);
+ dest->addRows(destinationChild, count);
+ } else {
+ QVector<Node *> &c = dest->children;
+ if (sourceRow < destinationChild) {
+ memmove(&c[sourceRow], &c[sourceRow + count], sizeof(Node *) * (destinationChild - sourceRow - count));
+ destinationChild -= count;
+ } else {
+ memmove(&c[destinationChild + count], &c[destinationChild], sizeof(Node *) * (sourceRow - destinationChild));
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ Node *n = buffer[i];
+ n->parent = dest;
+ dest->children[i + destinationChild] = n;
+ }
+
+ endMoveRows();
+ return true;
+ }
+
+ void setDecorationsEnabled(bool enable)
+ {
+ decorationsEnabled = enable;
+ }
+
+ mutable bool fetched;
+ bool decorationsEnabled;
+ bool alternateChildlessRows;
+ int rows, cols;
+ int levels;
+ mutable bool wrongIndex;
+
+ struct Node {
+ Node *parent;
+ QVector<Node *> children;
+
+ Node(int rows, Node *p = 0) : parent(p)
+ {
+ addRows(0, rows);
+ }
+
+ ~Node()
+ {
+ foreach (Node *n, children)
+ delete n;
+ }
+
+ void addRows(int row, int count)
+ {
+ if (count > 0) {
+ children.reserve(children.count() + count);
+ children.insert(row, count, (Node *)0);
+ }
+ }
+
+ void removeRows(int row, int count, bool keepAlive = false)
+ {
+ int newCount = qMax(children.count() - count, 0);
+ int effectiveCountDiff = children.count() - newCount;
+ if (effectiveCountDiff > 0) {
+ if (!keepAlive)
+ for (int i = 0; i < effectiveCountDiff; i++)
+ delete children[i + row];
+ children.remove(row, effectiveCountDiff);
+ }
+ }
+ };
+
+ Node *tree;
+};