| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtCore> |
| #include <QAbstractItemModel> |
| |
| class TestModel: public QAbstractItemModel |
| { |
| Q_OBJECT |
| public: |
| TestModel(QObject *parent = 0): QAbstractItemModel(parent), |
| fetched(false), rows(10), cols(1), levels(INT_MAX), wrongIndex(false) { init(); } |
| |
| TestModel(int _rows, int _cols, QObject *parent = 0): QAbstractItemModel(parent), |
| fetched(false), rows(_rows), cols(_cols), levels(INT_MAX), wrongIndex(false) { init(); } |
| |
| enum { |
| NameRole = Qt::UserRole + 1 |
| }; |
| |
| void init() { |
| decorationsEnabled = false; |
| alternateChildlessRows = true; |
| tree = new Node(rows); |
| } |
| |
| inline qint32 level(const QModelIndex &index) const { |
| Node *n = (Node *)index.internalPointer(); |
| if (!n) |
| return -1; |
| int l = -1; |
| while (n != tree) { |
| n = n->parent; |
| ++l; |
| } |
| return l; |
| } |
| |
| void resetModel() |
| { |
| beginResetModel(); |
| fetched = false; |
| delete tree; |
| tree = new Node(rows); |
| endResetModel(); |
| } |
| |
| QString displayData(const QModelIndex &idx) const |
| { |
| return QString("[%1,%2,%3,%4]").arg(idx.row()).arg(idx.column()).arg(idx.internalId()).arg(hasChildren(idx)); |
| } |
| |
| bool canFetchMore(const QModelIndex &) const { |
| return !fetched; |
| } |
| |
| void fetchMore(const QModelIndex &) { |
| fetched = true; |
| } |
| |
| bool hasChildren(const QModelIndex &parent = QModelIndex()) const { |
| bool hasFetched = fetched; |
| fetched = true; |
| bool r = QAbstractItemModel::hasChildren(parent); |
| fetched = hasFetched; |
| return r; |
| } |
| |
| int rowCount(const QModelIndex& parent = QModelIndex()) const { |
| if (!fetched) |
| qFatal("%s: rowCount should not be called before fetching", Q_FUNC_INFO); |
| if ((parent.column() > 0) || (level(parent) > levels) |
| || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1))) |
| return 0; |
| Node *n = (Node*)parent.internalPointer(); |
| if (!n) |
| n = tree; |
| return n->children.count(); |
| } |
| |
| int columnCount(const QModelIndex& parent = QModelIndex()) const { |
| if ((parent.column() > 0) || (level(parent) > levels) |
| || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1))) |
| return 0; |
| return cols; |
| } |
| |
| bool isEditable(const QModelIndex &index) const { |
| if (index.isValid()) |
| return true; |
| return false; |
| } |
| |
| QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const |
| { |
| if (row < 0 || column < 0 || (level(parent) > levels) || column >= cols) |
| return QModelIndex(); |
| Node *pn = (Node*)parent.internalPointer(); |
| if (!pn) |
| pn = tree; |
| if (row >= pn->children.count()) |
| return QModelIndex(); |
| |
| Node *n = pn->children.at(row); |
| if (!n) { |
| n = new Node(rows, pn); |
| pn->children[row] = n; |
| } |
| return createIndex(row, column, n); |
| } |
| |
| QModelIndex parent(const QModelIndex &index) const |
| { |
| Node *n = (Node *)index.internalPointer(); |
| if (!n || n->parent == tree) |
| return QModelIndex(); |
| Q_ASSERT(n->parent->parent); |
| int parentRow = n->parent->parent->children.indexOf(n->parent); |
| Q_ASSERT(parentRow != -1); |
| return createIndex(parentRow, 0, n->parent); |
| } |
| |
| QVariant data(const QModelIndex &idx, int role) const |
| { |
| if (!idx.isValid()) |
| return QVariant(); |
| |
| Node *pn = (Node *)idx.internalPointer(); |
| if (!pn) |
| pn = tree; |
| if (pn != tree) |
| pn = pn->parent; |
| if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols |
| || idx.row() >= pn->children.count()) { |
| wrongIndex = true; |
| qWarning("Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(), |
| idx.internalPointer()); |
| return QVariant(); |
| } |
| |
| if (role == Qt::DisplayRole) |
| return displayData(idx); |
| |
| if (role == NameRole) |
| return QString("Name-%1-%2-%3-%4").arg(idx.row()).arg(idx.column()).arg(idx.internalId()).arg(hasChildren(idx)); |
| |
| return QVariant(); |
| } |
| |
| bool setData(const QModelIndex &index, const QVariant &value, int role) |
| { |
| Q_UNUSED(value); |
| QVector<int> changedRole(1, role); |
| emit dataChanged(index, index, changedRole); |
| return true; |
| } |
| |
| void groupedSetData(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) |
| { |
| emit dataChanged(topLeft, bottomRight, roles); |
| } |
| |
| void changeLayout(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>()) |
| { |
| emit layoutAboutToBeChanged(parents); |
| emit layoutChanged(parents); |
| } |
| |
| Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) |
| { |
| beginRemoveRows(parent, row, row + count - 1); |
| Node *n = (Node *)parent.internalPointer(); |
| if (!n) |
| n = tree; |
| n->removeRows(row, count); |
| endRemoveRows(); |
| return true; |
| } |
| |
| void removeLastColumn() |
| { |
| beginRemoveColumns(QModelIndex(), cols - 1, cols - 1); |
| --cols; |
| endRemoveColumns(); |
| } |
| |
| void removeAllColumns() |
| { |
| beginRemoveColumns(QModelIndex(), 0, cols - 1); |
| cols = 0; |
| endRemoveColumns(); |
| } |
| |
| bool insertRows(int row, int count, const QModelIndex &parent) |
| { |
| beginInsertRows(parent, row, row + count - 1); |
| Node *n = (Node *)parent.internalPointer(); |
| if (!n) |
| n = tree; |
| n->addRows(row, count); |
| endInsertRows(); |
| return true; |
| } |
| |
| bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) |
| { |
| Q_ASSERT_X(sourceRow >= 0 && sourceRow < rowCount(sourceParent) |
| && count > 0 && sourceRow + count - 1 < rowCount(sourceParent) |
| && destinationChild >= 0 && destinationChild <= rowCount(destinationParent), |
| Q_FUNC_INFO, "Rows out of range."); |
| Q_ASSERT_X(!(sourceParent == destinationParent && destinationChild >= sourceRow && destinationChild < sourceRow + count), |
| Q_FUNC_INFO, "Moving rows onto themselves."); |
| if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) |
| return false; |
| Node *src = (Node *)sourceParent.internalPointer(); |
| if (!src) |
| src = tree; |
| Node *dest = (Node *)destinationParent.internalPointer(); |
| if (!dest) |
| dest = tree; |
| QVector<Node *> buffer = src->children.mid(sourceRow, count); |
| if (src != dest) { |
| src->removeRows(sourceRow, count, true /* keep alive */); |
| dest->addRows(destinationChild, count); |
| } else { |
| QVector<Node *> &c = dest->children; |
| if (sourceRow < destinationChild) { |
| memmove(&c[sourceRow], &c[sourceRow + count], sizeof(Node *) * (destinationChild - sourceRow - count)); |
| destinationChild -= count; |
| } else { |
| memmove(&c[destinationChild + count], &c[destinationChild], sizeof(Node *) * (sourceRow - destinationChild)); |
| } |
| } |
| for (int i = 0; i < count; i++) { |
| Node *n = buffer[i]; |
| n->parent = dest; |
| dest->children[i + destinationChild] = n; |
| } |
| |
| endMoveRows(); |
| return true; |
| } |
| |
| void setDecorationsEnabled(bool enable) |
| { |
| decorationsEnabled = enable; |
| } |
| |
| mutable bool fetched; |
| bool decorationsEnabled; |
| bool alternateChildlessRows; |
| int rows, cols; |
| int levels; |
| mutable bool wrongIndex; |
| |
| struct Node { |
| Node *parent; |
| QVector<Node *> children; |
| |
| Node(int rows, Node *p = 0) : parent(p) |
| { |
| addRows(0, rows); |
| } |
| |
| ~Node() |
| { |
| qDeleteAll(children); |
| } |
| |
| void addRows(int row, int count) |
| { |
| if (count > 0) { |
| children.reserve(children.count() + count); |
| children.insert(row, count, (Node *)0); |
| } |
| } |
| |
| void removeRows(int row, int count, bool keepAlive = false) |
| { |
| int newCount = qMax(children.count() - count, 0); |
| int effectiveCountDiff = children.count() - newCount; |
| if (effectiveCountDiff > 0) { |
| if (!keepAlive) |
| for (int i = 0; i < effectiveCountDiff; i++) |
| delete children[i + row]; |
| children.remove(row, effectiveCountDiff); |
| } |
| } |
| }; |
| |
| QHash<int, QByteArray> roleNames() const |
| { |
| QHash<int, QByteArray> rn = QAbstractItemModel::roleNames(); |
| rn[NameRole] = "name"; |
| return rn; |
| } |
| |
| Node *tree; |
| }; |