diff options
Diffstat (limited to 'src')
21 files changed, 1893 insertions, 124 deletions
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 Binary files differnew file mode 100644 index 000000000..b28431017 --- /dev/null +++ b/src/controls/doc/images/qtquickcontrols-example-filesystembrowser.png diff --git a/src/controls/doc/images/treeview.png b/src/controls/doc/images/treeview.png Binary files differnew file mode 100644 index 000000000..aac520a3e --- /dev/null +++ b/src/controls/doc/images/treeview.png 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); |