summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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;
+};