summaryrefslogtreecommitdiff
path: root/src/controls/Private
diff options
context:
space:
mode:
Diffstat (limited to 'src/controls/Private')
-rw-r--r--src/controls/Private/BasicTableView.qml62
-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
6 files changed, 1042 insertions, 9 deletions
diff --git a/src/controls/Private/BasicTableView.qml b/src/controls/Private/BasicTableView.qml
index bc8ebdb0..a42a406c 100644
--- a/src/controls/Private/BasicTableView.qml
+++ b/src/controls/Private/BasicTableView.qml
@@ -269,11 +269,14 @@ ScrollView {
Otherwise \c null is returned.
*/
function insertColumn(index, column) {
+ if (__isTreeView && index === 0 && columnCount > 0) {
+ console.warn(__viewTypeName + "::insertColumn(): Can't replace column 0")
+ return null
+ }
var object = column
- if (typeof column['createObject'] === 'function')
+ if (typeof column['createObject'] === 'function') {
object = column.createObject(root)
-
- else if (object.__view) {
+ } else if (object.__view) {
console.warn(__viewTypeName + "::insertColumn(): you cannot add a column to multiple views")
return null
}
@@ -299,6 +302,10 @@ ScrollView {
console.warn(__viewTypeName + "::removeColumn(): invalid argument")
return
}
+ if (__isTreeView && index === 0) {
+ console.warn(__viewTypeName + "::removeColumn(): Can't remove column 0")
+ return
+ }
var column = columnModel.get(index).columnItem
columnModel.remove(index, 1)
column.destroy()
@@ -314,6 +321,10 @@ ScrollView {
console.warn(__viewTypeName + "::moveColumn(): invalid argument")
return
}
+ if (__isTreeView && to === 0) {
+ console.warn(__viewTypeName + "::moveColumn(): Can't move column 0")
+ return
+ }
columnModel.move(from, to, 1)
}
@@ -371,6 +382,9 @@ ScrollView {
property string __viewTypeName
/*! \internal */
+ readonly property bool __isTreeView: __viewTypeName === "TreeView"
+
+ /*! \internal */
default property alias __columns: root.data
/*! \internal */
@@ -543,6 +557,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 +623,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
}
}
}
@@ -654,6 +700,8 @@ ScrollView {
visible: modelData.visible
height: headerStyle.height
+ readonly property bool treeViewMovable: !__isTreeView || index > 0
+
Loader {
id: headerStyle
sourceComponent: root.headerDelegate
@@ -673,7 +721,7 @@ ScrollView {
id: targetmark
width: parent.width
height:parent.height
- opacity: (index === repeater.targetIndex && repeater.targetIndex !== repeater.dragIndex) ? 0.5 : 0
+ opacity: (treeViewMovable && index === repeater.targetIndex && repeater.targetIndex !== repeater.dragIndex) ? 0.5 : 0
Behavior on opacity { NumberAnimation { duration: 160 } }
color: palette.highlight
visible: modelData.movable
@@ -693,7 +741,7 @@ ScrollView {
// NOTE: the direction is different from the master branch
// so this indicates that I am using an invalid assumption on item ordering
onPositionChanged: {
- if (modelData.movable && pressed && columnCount > 1) { // only do this while dragging
+ if (drag.active && modelData.movable && pressed && columnCount > 1) { // only do this while dragging
for (var h = columnCount-1 ; h >= 0 ; --h) {
if (drag.target.x + listView.contentX + headerRowDelegate.width/2 > headerrow.children[h].x) {
repeater.targetIndex = h
@@ -710,7 +758,7 @@ ScrollView {
onReleased: {
if (repeater.targetIndex >= 0 && repeater.targetIndex !== index ) {
var targetColumn = columnModel.get(repeater.targetIndex).columnItem
- if (targetColumn.movable) {
+ if (targetColumn.movable && (!__isTreeView || repeater.targetIndex > 0)) {
columnModel.move(index, repeater.targetIndex, 1)
if (sortIndicatorColumn === index)
sortIndicatorColumn = repeater.targetIndex
@@ -719,7 +767,7 @@ ScrollView {
repeater.targetIndex = -1
repeater.dragIndex = -1
}
- drag.target: modelData.movable && columnCount > 1 ? draghandle : null
+ drag.target: treeViewMovable && modelData.movable && columnCount > 1 ? draghandle : null
}
Loader {
diff --git a/src/controls/Private/private.pri b/src/controls/Private/private.pri
index 3d6a7162..7d23fc47 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 4cd4f52b..2ac692af 100644
--- a/src/controls/Private/qquickstyleitem.cpp
+++ b/src/controls/Private/qquickstyleitem.cpp
@@ -370,6 +370,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();
@@ -1223,6 +1236,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;
}
@@ -1309,6 +1324,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") {
@@ -1422,6 +1439,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;
}
@@ -1502,6 +1524,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 4ef86c73..74674454 100644
--- a/src/controls/Private/qquickstyleitem_p.h
+++ b/src/controls/Private/qquickstyleitem_p.h
@@ -122,6 +122,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 00000000..70e038fe
--- /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 = floorf(log10f(float(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 00000000..838ab805
--- /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