From fad842cebeab8cba78141edd10756a51885f448b Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Thu, 23 Jul 2009 11:04:25 +0100 Subject: Add move API to QAbstractItemModel. This adds the function beginMoveRows, endMoveRows, beginMoveColumns, endMoveColumns Reviewed-by: Olivier Goffart Acknowledged-by: Thierry Merge-request: 972 --- src/corelib/kernel/qabstractitemmodel.cpp | 327 ++++++++ src/corelib/kernel/qabstractitemmodel.h | 13 + src/corelib/kernel/qabstractitemmodel_p.h | 7 + tests/auto/qabstractitemmodel/dynamictreemodel.cpp | 245 ++++++ tests/auto/qabstractitemmodel/dynamictreemodel.h | 141 ++++ .../auto/qabstractitemmodel/qabstractitemmodel.pro | 5 +- .../qabstractitemmodel/tst_qabstractitemmodel.cpp | 841 ++++++++++++++++++++- 7 files changed, 1577 insertions(+), 2 deletions(-) create mode 100644 tests/auto/qabstractitemmodel/dynamictreemodel.cpp create mode 100644 tests/auto/qabstractitemmodel/dynamictreemodel.h diff --git a/src/corelib/kernel/qabstractitemmodel.cpp b/src/corelib/kernel/qabstractitemmodel.cpp index 3b7059b..fb0afcc 100644 --- a/src/corelib/kernel/qabstractitemmodel.cpp +++ b/src/corelib/kernel/qabstractitemmodel.cpp @@ -599,6 +599,118 @@ void QAbstractItemModelPrivate::rowsInserted(const QModelIndex &parent, } } +void QAbstractItemModelPrivate::itemsAboutToBeMoved(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation) +{ + Q_Q(QAbstractItemModel); + QVector persistent_moved_explicitly; + QVector persistent_moved_in_source; + QVector persistent_moved_in_destination; + + QHash::const_iterator it; + const QHash::const_iterator begin = persistent.indexes.constBegin(); + const QHash::const_iterator end = persistent.indexes.constEnd(); + + const bool sameParent = (srcParent == destinationParent); + const bool movingUp = (srcFirst > destinationChild); + + for ( it = begin; it != end; ++it) { + QPersistentModelIndexData *data = *it; + const QModelIndex &index = data->index; + const QModelIndex &parent = index.parent(); + const bool isSourceIndex = (parent == srcParent); + const bool isDestinationIndex = (parent == destinationParent); + + int childPosition; + if (orientation == Qt::Vertical) + childPosition = index.row(); + else + childPosition = index.column(); + + if (!index.isValid() || !(isSourceIndex || isDestinationIndex ) ) + continue; + + if (!sameParent && isDestinationIndex) { + if (childPosition >= destinationChild) + persistent_moved_in_destination.append(data); + continue; + } + + if (sameParent && movingUp && childPosition < destinationChild) + continue; + + if (sameParent && !movingUp && childPosition < srcFirst ) + continue; + + if (!sameParent && childPosition < srcFirst) + continue; + + if (sameParent && (childPosition > srcLast) && (childPosition >= destinationChild )) + continue; + + if ((childPosition <= srcLast) && (childPosition >= srcFirst)) { + persistent_moved_explicitly.append(data); + } else { + persistent_moved_in_source.append(data); + } + } + persistent.moved.push(persistent_moved_explicitly); + persistent.moved.push(persistent_moved_in_source); + persistent.moved.push(persistent_moved_in_destination); +} + +/*! + \internal + + Moves persistent indexes \a indexes by amount \a change. The change will be either a change in row value or a change in + column value depending on the value of \a orientation. The indexes may also be moved to a different parent if \a parent + differs from the existing parent for the index. +*/ +void QAbstractItemModelPrivate::movePersistentIndexes(QVector indexes, int change, const QModelIndex &parent, Qt::Orientation orientation) +{ + QVector::const_iterator it; + const QVector::const_iterator begin = indexes.constBegin(); + const QVector::const_iterator end = indexes.constEnd(); + + for (it = begin; it != end; ++it) + { + QPersistentModelIndexData *data = *it; + + int row = data->index.row(); + int column = data->index.column(); + + if (Qt::Vertical == orientation) + row += change; + else + column += change; + + persistent.indexes.erase(persistent.indexes.find(data->index)); + data->index = q_func()->index(row, column, parent); + if (data->index.isValid()) { + persistent.insertMultiAtEnd(data->index, data); + } else { + qWarning() << "QAbstractItemModel::endMoveRows: Invalid index (" << row << "," << column << ") in model" << q_func(); + } + } +} + +void QAbstractItemModelPrivate::itemsMoved(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation) +{ + QVector moved_in_destination = persistent.moved.pop(); + QVector moved_in_source = persistent.moved.pop(); + QVector moved_explicitly = persistent.moved.pop(); + + const bool sameParent = (sourceParent == destinationParent); + const bool movingUp = (sourceFirst > destinationChild); + + const int explicit_change = (!sameParent || movingUp) ? destinationChild - sourceFirst : destinationChild - sourceLast - 1 ; + const int source_change = (!sameParent || !movingUp) ? -1*(sourceLast - sourceFirst + 1) : sourceLast - sourceFirst + 1 ; + const int destination_change = sourceLast - sourceFirst + 1; + + movePersistentIndexes(moved_explicitly, explicit_change, destinationParent, orientation); + movePersistentIndexes(moved_in_source, source_change, sourceParent, orientation); + movePersistentIndexes(moved_in_destination, destination_change, destinationParent, orientation); +} + void QAbstractItemModelPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) { @@ -1370,6 +1482,36 @@ QAbstractItemModel::~QAbstractItemModel() */ /*! + \fn void QAbstractItemModel::rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) + + This signal is emitted after rows have been moved within the + model. The items between \a sourceStart and \a sourceEnd + inclusive, under the given \a sourceParent item have been moved to \a destinationParent + starting at the row \a destinationRow. + + \bold{Note:} Components connected to this signal use it to adapt to changes + in the model's dimensions. It can only be emitted by the QAbstractItemModel + implementation, and cannot be explicitly emitted in subclass code. + + \sa beginMoveRows() +*/ + +/*! + \fn void QAbstractItemModel::rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) + + This signal is emitted just before rows are moved within the + model. The items that will be moved are those between \a sourceStart and \a sourceEnd + inclusive, under the given \a sourceParent item. They will be moved to \a destinationParent + starting at the row \a destinationRow. + + \bold{Note:} Components connected to this signal use it to adapt to changes + in the model's dimensions. It can only be emitted by the QAbstractItemModel + implementation, and cannot be explicitly emitted in subclass code. + + \sa beginMoveRows() +*/ + +/*! \fn void QAbstractItemModel::columnsInserted(const QModelIndex &parent, int start, int end) This signal is emitted after columns have been inserted into the model. The @@ -2284,6 +2426,116 @@ void QAbstractItemModel::endRemoveRows() } /*! + Returns whether a move operation is valid. + + A move operation is not allowed if it moves a continuous range of rows to a destination within + itself, or if it attempts to move a row to one of its own descendants. + + \internal +*/ +bool QAbstractItemModelPrivate::allowMove(const QModelIndex &srcParent, int start, int end, const QModelIndex &destinationParent, int destinationStart, Qt::Orientation orientation) +{ + Q_Q(QAbstractItemModel); + // Don't move the range within itself. + if ( ( destinationParent == srcParent ) + && ( destinationStart >= start ) + && ( destinationStart <= end + 1) ) + return false; + + QModelIndex destinationAncestor = destinationParent; + int pos = (Qt::Vertical == orientation) ? destinationAncestor.row() : destinationAncestor.column(); + forever { + if (destinationAncestor == srcParent) { + if (pos >= start && pos <= end) + return false; + break; + } + + if (!destinationAncestor.isValid()) + break; + + pos = (Qt::Vertical == orientation) ? destinationAncestor.row() : destinationAncestor.column(); + destinationAncestor = destinationAncestor.parent(); + } + + return true; +} + +/*! + Begins a row move operation. + + When reimplementing a subclass, this method simplifies moving entities + in your model. This method is responsible for moving persistent indexes + in the model, which you would otherwise be required to do yourself. + + Using beginMoveRows and endMoveRows is an alternative to emitting + layoutAboutToBeChanged and layoutChanged directly along with changePersistentIndexes. + layoutAboutToBeChanged is emitted by this method for compatibility reasons. + + The \a sourceParent index corresponds to the parent from which the + rows are moved; \a sourceFirst and \a sourceLast are the row numbers of the + rows to be moved. The \a destinationParent index corresponds to the parent into which + the rows are moved. The \a destinationRow is the row to which the rows will be moved. + That is, the index at row \a sourceFirst in \a sourceParent will become row \a destinationRow + in \a destinationParent. Its siblings will be moved correspondingly. + + Note that \a sourceParent and \a destinationParent may be the same, in which case you must + ensure that the \a destinationRow is not within the range of \a sourceFirst and \a sourceLast. + You must also ensure that you do not attempt to move a row to one of its own chilren or ancestors. + This method returns false if either condition is true, in which case you should abort your move operation. + + \sa endMoveRows() + + \since 4.6 +*/ +bool QAbstractItemModel::beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationChild) +{ + Q_ASSERT(sourceFirst >= 0); + Q_ASSERT(sourceLast >= sourceFirst); + Q_ASSERT(destinationChild >= 0); + Q_D(QAbstractItemModel); + + if (!d->allowMove(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild, Qt::Vertical)) { + return false; + } + + d->changes.push(QAbstractItemModelPrivate::Change(sourceParent, sourceFirst, sourceLast)); + int destinationLast = destinationChild + (sourceLast - sourceFirst); + d->changes.push(QAbstractItemModelPrivate::Change(destinationParent, destinationChild, destinationLast)); + + d->itemsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild, Qt::Vertical); + emit rowsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild); + emit layoutAboutToBeChanged(); + return true; +} + +/*! + Ends a row move operation. + + When implementing a subclass, you must call this + function \e after moving data within the model's underlying data + store. + + layoutChanged is emitted by this method for compatibility reasons. + + \sa beginMoveRows() + + \since 4.6 +*/ +void QAbstractItemModel::endMoveRows() +{ + Q_D(QAbstractItemModel); + + QAbstractItemModelPrivate::Change insertChange = d->changes.pop(); + QAbstractItemModelPrivate::Change removeChange = d->changes.pop(); + + d->itemsMoved(removeChange.parent, removeChange.first, removeChange.last, insertChange.parent, insertChange.first, Qt::Vertical); + + emit rowsMoved(removeChange.parent, removeChange.first, removeChange.last, insertChange.parent, insertChange.first); + emit layoutChanged(); +} + +/*! Begins a column insertion operation. When reimplementing insertColumns() in a subclass, you must call this @@ -2406,6 +2658,81 @@ void QAbstractItemModel::endRemoveColumns() } /*! + Begins a column move operation. + + When reimplementing a subclass, this method simplifies moving entities + in your model. This method is responsible for moving persistent indexes + in the model, which you would otherwise be required to do yourself. + + Using beginMoveColumns and endMoveColumns is an alternative to emitting + layoutAboutToBeChanged and layoutChanged directly along with changePersistentIndexes. + layoutAboutToBeChanged is emitted by this method for compatibility reasons. + + The \a sourceParent index corresponds to the parent from which the + columns are moved; \a sourceFirst and \a sourceLast are the column numbers of the + columns to be moved. The \a destinationParent index corresponds to the parent into which + the columns are moved. The \a destinationColumn is the column to which the columns will be moved. + That is, the index at column \a sourceFirst in \a sourceParent will become column \a destinationColumn + in \a destinationParent. Its siblings will be moved correspondingly. + + Note that \a sourceParent and \a destinationParent may be the same, in which case you must + ensure that the \a destinationColumn is not within the range of \a sourceFirst and \a sourceLast. + You must also ensure that you do not attempt to move a row to one of its own chilren or ancestors. + This method returns false if either condition is true, in which case you should abort your move operation. + + \sa endMoveColumns() + + \since 4.6 +*/ +bool QAbstractItemModel::beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationChild) +{ + Q_ASSERT(sourceFirst >= 0); + Q_ASSERT(sourceLast >= sourceFirst); + Q_ASSERT(destinationChild >= 0); + Q_D(QAbstractItemModel); + + if (!d->allowMove(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild, Qt::Horizontal)) { + return false; + } + + d->changes.push(QAbstractItemModelPrivate::Change(sourceParent, sourceFirst, sourceLast)); + int destinationLast = destinationChild + (sourceLast - sourceFirst); + d->changes.push(QAbstractItemModelPrivate::Change(destinationParent, destinationChild, destinationLast)); + + d->itemsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild, Qt::Horizontal); + + emit columnsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild); + emit layoutAboutToBeChanged(); + return true; +} + +/*! + Ends a column move operation. + + When implementing a subclass, you must call this + function \e after moving data within the model's underlying data + store. + + layoutChanged is emitted by this method for compatibility reasons. + + \sa beginMoveColumns() + + \since 4.6 +*/ +void QAbstractItemModel::endMoveColumns() +{ + Q_D(QAbstractItemModel); + + QAbstractItemModelPrivate::Change insertChange = d->changes.pop(); + QAbstractItemModelPrivate::Change removeChange = d->changes.pop(); + + d->itemsMoved(removeChange.parent, removeChange.first, removeChange.last, insertChange.parent, insertChange.first, Qt::Horizontal); + + emit columnsMoved(removeChange.parent, removeChange.first, removeChange.last, insertChange.parent, insertChange.first); + emit layoutChanged(); +} + +/*! Resets the model to its original state in any attached views. The view to which the model is attached to will be reset as well. diff --git a/src/corelib/kernel/qabstractitemmodel.h b/src/corelib/kernel/qabstractitemmodel.h index 00f6cb2..b47e4cb 100644 --- a/src/corelib/kernel/qabstractitemmodel.h +++ b/src/corelib/kernel/qabstractitemmodel.h @@ -252,6 +252,13 @@ private: // can only be emitted by QAbstractItemModel void modelAboutToBeReset(); void modelReset(); + void rowsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow ); + void rowsMoved( const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row ); + + void columnsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn ); + void columnsMoved( const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column ); + + public Q_SLOTS: virtual bool submit(); virtual void revert(); @@ -272,12 +279,18 @@ protected: void beginRemoveRows(const QModelIndex &parent, int first, int last); void endRemoveRows(); + bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationRow); + void endMoveRows(); + void beginInsertColumns(const QModelIndex &parent, int first, int last); void endInsertColumns(); void beginRemoveColumns(const QModelIndex &parent, int first, int last); void endRemoveColumns(); + bool beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationColumn); + void endMoveColumns(); + void reset(); void changePersistentIndex(const QModelIndex &from, const QModelIndex &to); diff --git a/src/corelib/kernel/qabstractitemmodel_p.h b/src/corelib/kernel/qabstractitemmodel_p.h index e81e627..aae3cba 100644 --- a/src/corelib/kernel/qabstractitemmodel_p.h +++ b/src/corelib/kernel/qabstractitemmodel_p.h @@ -80,6 +80,7 @@ class Q_CORE_EXPORT QAbstractItemModelPrivate : public QObjectPrivate public: QAbstractItemModelPrivate() : QObjectPrivate(), supportedDragActions(-1), roleNames(defaultRoleNames()) {} void removePersistentIndexData(QPersistentModelIndexData *data); + void movePersistentIndexes(QVector indexes, int change, const QModelIndex &parent, Qt::Orientation orientation); void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); void rowsInserted(const QModelIndex &parent, int first, int last); void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); @@ -91,6 +92,10 @@ public: static QAbstractItemModel *staticEmptyModel(); static bool variantLessThan(const QVariant &v1, const QVariant &v2); + void itemsAboutToBeMoved(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation); + void itemsMoved(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation); + bool allowMove(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation); + inline QModelIndex createIndex(int row, int column, void *data = 0) const { return q_func()->createIndex(row, column, data); } @@ -132,6 +137,8 @@ public: Change(const QModelIndex &p, int f, int l) : parent(p), first(f), last(l) {} QModelIndex parent; int first, last; + + bool isValid() { return first >= 0 && last >= 0; } }; QStack changes; diff --git a/tests/auto/qabstractitemmodel/dynamictreemodel.cpp b/tests/auto/qabstractitemmodel/dynamictreemodel.cpp new file mode 100644 index 0000000..6c3e0cb --- /dev/null +++ b/tests/auto/qabstractitemmodel/dynamictreemodel.cpp @@ -0,0 +1,245 @@ +/* + Copyright (c) 2009 Stephen Kelly + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "dynamictreemodel.h" + +#include +#include +#include + +#include + +#include + +DynamicTreeModel::DynamicTreeModel(QObject *parent) + : QAbstractItemModel(parent), + nextId(1) +{ +} + +QModelIndex DynamicTreeModel::index(int row, int column, const QModelIndex &parent) const +{ +// if (column != 0) +// return QModelIndex(); + + + if ( column < 0 || row < 0 ) + return QModelIndex(); + + QList > childIdColumns = m_childItems.value(parent.internalId()); + + + if (childIdColumns.size() == 0) + return QModelIndex(); + + if (column >= childIdColumns.size()) + return QModelIndex(); + + QList rowIds = childIdColumns.at(column); + + if ( row >= rowIds.size()) + return QModelIndex(); + + qint64 id = rowIds.at(row); + + return createIndex(row, column, reinterpret_cast(id)); + +} + +qint64 DynamicTreeModel::findParentId(qint64 searchId) const +{ + if (searchId <= 0) + return -1; + + QHashIterator > > i(m_childItems); + while (i.hasNext()) + { + i.next(); + QListIterator > j(i.value()); + while (j.hasNext()) + { + QList l = j.next(); + if (l.contains(searchId)) + { + return i.key(); + } + } + } + return -1; +} + +QModelIndex DynamicTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + qint64 searchId = index.internalId(); + qint64 parentId = findParentId(searchId); + // Will never happen for valid index, but what the hey... + if (parentId <= 0) + return QModelIndex(); + + qint64 grandParentId = findParentId(parentId); + if (grandParentId < 0) + grandParentId = 0; + + int column = 0; + QList childList = m_childItems.value(grandParentId).at(column); + + int row = childList.indexOf(parentId); + + return createIndex(row, column, reinterpret_cast(parentId)); + +} + +int DynamicTreeModel::rowCount(const QModelIndex &index ) const +{ + QList > cols = m_childItems.value(index.internalId()); + + if (cols.size() == 0 ) + return 0; + + if (index.column() > 0) + return 0; + + return cols.at(0).size(); +} + +int DynamicTreeModel::columnCount(const QModelIndex &index ) const +{ +// Q_UNUSED(index); + return m_childItems.value(index.internalId()).size(); +} + +QVariant DynamicTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (Qt::DisplayRole == role) + { + return m_items.value(index.internalId()); + } + return QVariant(); +} + +void DynamicTreeModel::clear() +{ + m_items.clear(); + m_childItems.clear(); + nextId = 1; + reset(); +} + + +ModelChangeCommand::ModelChangeCommand( DynamicTreeModel *model, QObject *parent ) + : QObject(parent), m_model(model), m_numCols(1), m_startRow(-1), m_endRow(-1) +{ + +} + +QModelIndex ModelChangeCommand::findIndex(QList rows) +{ + const int col = 0; + QModelIndex parent = QModelIndex(); + QListIterator i(rows); + while (i.hasNext()) + { + parent = m_model->index(i.next(), col, parent); + Q_ASSERT(parent.isValid()); + } + return parent; +} + +ModelInsertCommand::ModelInsertCommand(DynamicTreeModel *model, QObject *parent ) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelInsertCommand::doCommand() +{ + QModelIndex parent = findIndex(m_rowNumbers); + m_model->beginInsertRows(parent, m_startRow, m_endRow); + qint64 parentId = parent.internalId(); + for (int row = m_startRow; row <= m_endRow; row++) + { + for(int col = 0; col < m_numCols; col++ ) + { + if (m_model->m_childItems[parentId].size() <= col) + { + m_model->m_childItems[parentId].append(QList()); + } +// QString name = QUuid::createUuid().toString(); + qint64 id = m_model->newId(); + QString name = QString::number(id); + + m_model->m_items.insert(id, name); + m_model->m_childItems[parentId][col].insert(row, id); + + } + } + m_model->endInsertRows(); +} + + +ModelMoveCommand::ModelMoveCommand(DynamicTreeModel *model, QObject *parent) + : ModelChangeCommand(model, parent) +{ + +} + +void ModelMoveCommand::doCommand() +{ + QModelIndex srcParent = findIndex(m_rowNumbers); + QModelIndex destParent = findIndex(m_destRowNumbers); + + if (!m_model->beginMoveRows(srcParent, m_startRow, m_endRow, destParent, m_destRow)) + { + return; + } + + for (int column = 0; column < m_numCols; ++column) + { + QList l = m_model->m_childItems.value(srcParent.internalId())[column].mid(m_startRow, m_endRow - m_startRow + 1 ); + + for (int i = m_startRow; i <= m_endRow ; i++) + { + m_model->m_childItems[srcParent.internalId()][column].removeAt(m_startRow); + } + int d; + if (m_destRow < m_startRow) + d = m_destRow; + else + { + if (srcParent == destParent) + d = m_destRow - (m_endRow - m_startRow + 1); + else + d = m_destRow - (m_endRow - m_startRow) + 1; + } + + foreach(const qint64 id, l) + { + m_model->m_childItems[destParent.internalId()][column].insert(d++, id); + } + } + + m_model->endMoveRows(); +} + diff --git a/tests/auto/qabstractitemmodel/dynamictreemodel.h b/tests/auto/qabstractitemmodel/dynamictreemodel.h new file mode 100644 index 0000000..88e293c --- /dev/null +++ b/tests/auto/qabstractitemmodel/dynamictreemodel.h @@ -0,0 +1,141 @@ +/* + Copyright (c) 2009 Stephen Kelly + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#ifndef DYNAMICTREEMODEL_H +#define DYNAMICTREEMODEL_H + +#include + +#include +#include + +#include + +#include + +template class QList; + +class DynamicTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + DynamicTreeModel(QObject *parent = 0); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &index = QModelIndex()) const; + int columnCount(const QModelIndex &index = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + void clear(); + +protected slots: + + /** + Finds the parent id of the string with id @p searchId. + + Returns -1 if not found. + */ + qint64 findParentId(qint64 searchId) const; + +private: + QHash m_items; + QHash > > m_childItems; + qint64 nextId; + qint64 newId() { return nextId++; }; + + QModelIndex m_nextParentIndex; + int m_nextRow; + + int m_depth; + int maxDepth; + + friend class ModelInsertCommand; + friend class ModelMoveCommand; + +}; + + +class ModelChangeCommand : public QObject +{ + Q_OBJECT +public: + + ModelChangeCommand( DynamicTreeModel *model, QObject *parent = 0 ); + + virtual ~ModelChangeCommand() {} + + void setAncestorRowNumbers(QList rowNumbers) { m_rowNumbers = rowNumbers; } + + QModelIndex findIndex(QList rows); + + void setStartRow(int row) { m_startRow = row; } + + void setEndRow(int row) { m_endRow = row; } + + void setNumCols(int cols) { m_numCols = cols; } + + virtual void doCommand() = 0; + +protected: + DynamicTreeModel* m_model; + QList m_rowNumbers; + int m_numCols; + int m_startRow; + int m_endRow; + +}; + +typedef QList ModelChangeCommandList; + +class ModelInsertCommand : public ModelChangeCommand +{ + Q_OBJECT + +public: + + ModelInsertCommand(DynamicTreeModel *model, QObject *parent = 0 ); + virtual ~ModelInsertCommand() {} + + virtual void doCommand(); +}; + +class ModelMoveCommand : public ModelChangeCommand +{ + Q_OBJECT +public: + ModelMoveCommand(DynamicTreeModel *model, QObject *parent); + + virtual ~ModelMoveCommand() {} + + virtual void doCommand(); + + void setDestAncestors( QList rows ) { m_destRowNumbers = rows; } + + void setDestRow(int row) { m_destRow = row; } + +protected: + QList m_destRowNumbers; + int m_destRow; +}; + + +#endif diff --git a/tests/auto/qabstractitemmodel/qabstractitemmodel.pro b/tests/auto/qabstractitemmodel/qabstractitemmodel.pro index 5ad1020..84ed5a2 100644 --- a/tests/auto/qabstractitemmodel/qabstractitemmodel.pro +++ b/tests/auto/qabstractitemmodel/qabstractitemmodel.pro @@ -1,3 +1,6 @@ load(qttest_p4) -SOURCES += tst_qabstractitemmodel.cpp +SOURCES += tst_qabstractitemmodel.cpp dynamictreemodel.cpp +HEADERS += dynamictreemodel.h + QT = core + diff --git a/tests/auto/qabstractitemmodel/tst_qabstractitemmodel.cpp b/tests/auto/qabstractitemmodel/tst_qabstractitemmodel.cpp index e99ce06..9c83474 100644 --- a/tests/auto/qabstractitemmodel/tst_qabstractitemmodel.cpp +++ b/tests/auto/qabstractitemmodel/tst_qabstractitemmodel.cpp @@ -46,6 +46,10 @@ //TESTED_CLASS=QAbstractListModel QAbstractTableModel //TESTED_FILES= +#include "dynamictreemodel.h" + +Q_DECLARE_METATYPE(QModelIndex) + /*! Note that this doesn't test models, but any functionality that QAbstractItemModel shoudl provide */ @@ -86,6 +90,30 @@ private slots: void complexChangesWithPersistent(); + void testMoveSameParentUp_data(); + void testMoveSameParentUp(); + + void testMoveSameParentDown_data(); + void testMoveSameParentDown(); + + void testMoveToGrandParent_data(); + void testMoveToGrandParent(); + + void testMoveToSibling_data(); + void testMoveToSibling(); + + void testMoveToUncle_data(); + void testMoveToUncle(); + + void testMoveToDescendants(); + + void testMoveWithinOwnRange_data(); + void testMoveWithinOwnRange(); + + +private: + DynamicTreeModel *m_model; + }; /*! @@ -242,7 +270,20 @@ void tst_QAbstractItemModel::cleanupTestCase() void tst_QAbstractItemModel::init() { - + m_model = new DynamicTreeModel(this); + + ModelInsertCommand *insertCommand = new ModelInsertCommand(m_model, this); + insertCommand->setNumCols(4); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + insertCommand = new ModelInsertCommand(m_model, this); + insertCommand->setAncestorRowNumbers(QList() << 5); + insertCommand->setNumCols(4); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); } void tst_QAbstractItemModel::cleanup() @@ -815,5 +856,803 @@ void tst_QAbstractItemModel::complexChangesWithPersistent() } +void tst_QAbstractItemModel::testMoveSameParentDown_data() +{ + QTest::addColumn("startRow"); + QTest::addColumn("endRow"); + QTest::addColumn("destRow"); + + // Move from the start to the middle + QTest::newRow("move01") << 0 << 2 << 8; + // Move from the start to the end + QTest::newRow("move02") << 0 << 2 << 10; + // Move from the middle to the middle + QTest::newRow("move03") << 3 << 5 << 8; + // Move from the middle to the end + QTest::newRow("move04") << 3 << 5 << 10; +} + +void tst_QAbstractItemModel::testMoveSameParentDown() +{ + QFETCH( int, startRow); + QFETCH( int, endRow); + QFETCH( int, destRow); + + QList persistentList; + QModelIndexList indexList; + + for (int column = 0; column < m_model->columnCount(); ++column) + { + for (int row= 0; row < m_model->rowCount(); ++row) + { + QModelIndex idx = m_model->index(row, column); + QVERIFY(idx.isValid()); + indexList << idx; + persistentList << QPersistentModelIndex(idx); + } + } + + QModelIndex parent = m_model->index(5, 0); + for (int column = 0; column < m_model->columnCount(); ++column) + { + for (int row= 0; row < m_model->rowCount(parent); ++row) + { + QModelIndex idx = m_model->index(row, column, parent); + QVERIFY(idx.isValid()); + indexList << idx; + persistentList << QPersistentModelIndex(idx); + } + } + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + ModelMoveCommand *moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setNumCols(4); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + moveCommand->setDestRow(destRow); + moveCommand->doCommand(); + + QVariantList beforeSignal = beforeSpy.takeAt(0); + QVariantList afterSignal = afterSpy.takeAt(0); + + QCOMPARE(beforeSignal.size(), 5); + QCOMPARE(beforeSignal.at(0).value(), QModelIndex()); + QCOMPARE(beforeSignal.at(1).toInt(), startRow); + QCOMPARE(beforeSignal.at(2).toInt(), endRow); + QCOMPARE(beforeSignal.at(3).value(), QModelIndex()); + QCOMPARE(beforeSignal.at(4).toInt(), destRow); + + QCOMPARE(afterSignal.size(), 5); + QCOMPARE(afterSignal.at(0).value(), QModelIndex()); + QCOMPARE(afterSignal.at(1).toInt(), startRow); + QCOMPARE(afterSignal.at(2).toInt(), endRow); + QCOMPARE(afterSignal.at(3).value(), QModelIndex()); + QCOMPARE(afterSignal.at(4).toInt(), destRow); + + for (int i = 0; i < indexList.size(); i++) + { + QModelIndex idx = indexList.at(i); + QModelIndex persistentIndex = persistentList.at(i); + if (idx.parent() == QModelIndex()) + { + int row = idx.row(); + if ( row >= startRow) + { + if (row <= endRow) + { + QCOMPARE(row + destRow - endRow - 1, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idx.parent(), persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else if ( row < destRow) + { + QCOMPARE(row - (endRow - startRow + 1), persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idx.parent(), persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + QCOMPARE(idx, persistentIndex); + } + } +} + +void tst_QAbstractItemModel::testMoveSameParentUp_data() +{ + QTest::addColumn("startRow"); + QTest::addColumn("endRow"); + QTest::addColumn("destRow"); + + // Move from the middle to the start + QTest::newRow("move01") << 5 << 7 << 0; + // Move from the end to the start + QTest::newRow("move02") << 8 << 9 << 0; + // Move from the middle to the middle + QTest::newRow("move03") << 5 << 7 << 2; + // Move from the end to the middle + QTest::newRow("move04") << 8 << 9 << 5; +} + +void tst_QAbstractItemModel::testMoveSameParentUp() +{ + + QFETCH( int, startRow); + QFETCH( int, endRow); + QFETCH( int, destRow); + + QList persistentList; + QModelIndexList indexList; + + for (int column = 0; column < m_model->columnCount(); ++column) + { + for (int row= 0; row < m_model->rowCount(); ++row) + { + QModelIndex idx = m_model->index(row, column); + QVERIFY(idx.isValid()); + indexList << idx; + persistentList << QPersistentModelIndex(idx); + } + } + + QModelIndex parent = m_model->index(2, 0); + for (int column = 0; column < m_model->columnCount(); ++column) + { + for (int row= 0; row < m_model->rowCount(parent); ++row) + { + QModelIndex idx = m_model->index(row, column, parent); + QVERIFY(idx.isValid()); + indexList << idx; + persistentList << QPersistentModelIndex(idx); + } + } + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + + ModelMoveCommand *moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setNumCols(4); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + moveCommand->setDestRow(destRow); + moveCommand->doCommand(); + + QVariantList beforeSignal = beforeSpy.takeAt(0); + QVariantList afterSignal = afterSpy.takeAt(0); + + QCOMPARE(beforeSignal.size(), 5); + QCOMPARE(beforeSignal.at(0).value(), QModelIndex()); + QCOMPARE(beforeSignal.at(1).toInt(), startRow); + QCOMPARE(beforeSignal.at(2).toInt(), endRow); + QCOMPARE(beforeSignal.at(3).value(), QModelIndex()); + QCOMPARE(beforeSignal.at(4).toInt(), destRow); + + QCOMPARE(afterSignal.size(), 5); + QCOMPARE(afterSignal.at(0).value(), QModelIndex()); + QCOMPARE(afterSignal.at(1).toInt(), startRow); + QCOMPARE(afterSignal.at(2).toInt(), endRow); + QCOMPARE(afterSignal.at(3).value(), QModelIndex()); + QCOMPARE(afterSignal.at(4).toInt(), destRow); + + + for (int i = 0; i < indexList.size(); i++) + { + QModelIndex idx = indexList.at(i); + QModelIndex persistentIndex = persistentList.at(i); + if (idx.parent() == QModelIndex()) + { + int row = idx.row(); + if ( row >= destRow) + { + if (row < startRow) + { + QCOMPARE(row + endRow - startRow + 1, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idx.parent(), persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else if ( row <= endRow) + { + QCOMPARE(row + destRow - startRow, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idx.parent(), persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + QCOMPARE(idx, persistentIndex); + } + } +} + +void tst_QAbstractItemModel::testMoveToGrandParent_data() +{ + QTest::addColumn("startRow"); + QTest::addColumn("endRow"); + QTest::addColumn("destRow"); + + // Move from the start to the middle + QTest::newRow("move01") << 0 << 2 << 8; + // Move from the start to the end + QTest::newRow("move02") << 0 << 2 << 10; + // Move from the middle to the middle + QTest::newRow("move03") << 3 << 5 << 8; + // Move from the middle to the end + QTest::newRow("move04") << 3 << 5 << 10; + + // Move from the middle to the start + QTest::newRow("move05") << 5 << 7 << 0; + // Move from the end to the start + QTest::newRow("move06") << 8 << 9 << 0; + // Move from the middle to the middle + QTest::newRow("move07") << 5 << 7 << 2; + // Move from the end to the middle + QTest::newRow("move08") << 8 << 9 << 5; + + // Moving to the same row in a different parent doesn't confuse things. + QTest::newRow("move09") << 8 << 8 << 8; + + // Moving to the row of my parent and its neighbours doesn't confuse things + QTest::newRow("move09") << 8 << 8 << 4; + QTest::newRow("move10") << 8 << 8 << 5; + QTest::newRow("move11") << 8 << 8 << 6; + + // Moving everything from one parent to another + QTest::newRow("move12") << 0 << 9 << 10; +} + +void tst_QAbstractItemModel::testMoveToGrandParent() +{ + + QFETCH( int, startRow); + QFETCH( int, endRow); + QFETCH( int, destRow); + + QList persistentList; + QModelIndexList indexList; + QModelIndexList parentsList; + + for (int column = 0; column < m_model->columnCount(); ++column) + { + for (int row= 0; row < m_model->rowCount(); ++row) + { + QModelIndex idx = m_model->index(row, column); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + } + + QModelIndex sourceIndex = m_model->index(5, 0); + for (int column = 0; column < m_model->columnCount(); ++column) + { + for (int row= 0; row < m_model->rowCount(sourceIndex); ++row) + { + QModelIndex idx = m_model->index(row, column, sourceIndex); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + } + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + + ModelMoveCommand *moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setAncestorRowNumbers(QList() << 5); + moveCommand->setNumCols(4); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + moveCommand->setDestRow(destRow); + moveCommand->doCommand(); + + QVariantList beforeSignal = beforeSpy.takeAt(0); + QVariantList afterSignal = afterSpy.takeAt(0); + + QCOMPARE(beforeSignal.size(), 5); + QCOMPARE(beforeSignal.at(0).value(), sourceIndex); + QCOMPARE(beforeSignal.at(1).toInt(), startRow); + QCOMPARE(beforeSignal.at(2).toInt(), endRow); + QCOMPARE(beforeSignal.at(3).value(), QModelIndex()); + QCOMPARE(beforeSignal.at(4).toInt(), destRow); + + QCOMPARE(afterSignal.size(), 5); + QCOMPARE(afterSignal.at(0).value(), sourceIndex); + QCOMPARE(afterSignal.at(1).toInt(), startRow); + QCOMPARE(afterSignal.at(2).toInt(), endRow); + QCOMPARE(afterSignal.at(3).value(), QModelIndex()); + QCOMPARE(afterSignal.at(4).toInt(), destRow); + + for (int i = 0; i < indexList.size(); i++) + { + QModelIndex idx = indexList.at(i); + QModelIndex idxParent = parentsList.at(i); + QModelIndex persistentIndex = persistentList.at(i); + int row = idx.row(); + if (idxParent == QModelIndex()) + { + if ( row >= destRow) + { + QCOMPARE(row + endRow - startRow + 1, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idxParent, persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + if (row < startRow) + { + QCOMPARE(idx, persistentIndex); + } else if (row <= endRow) + { + QCOMPARE(row + destRow - startRow, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(QModelIndex(), persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else { + QCOMPARE(row - (endRow - startRow + 1), persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + + if (idxParent.row() >= destRow) + { + QModelIndex adjustedParent; + adjustedParent = idxParent.sibling( idxParent.row() + endRow - startRow + 1, idxParent.column()); + QCOMPARE(adjustedParent, persistentIndex.parent()); + } else + { + QCOMPARE(idxParent, persistentIndex.parent()); + } + QCOMPARE(idx.model(), persistentIndex.model()); + } + } + } +} + +void tst_QAbstractItemModel::testMoveToSibling_data() +{ + QTest::addColumn("startRow"); + QTest::addColumn("endRow"); + QTest::addColumn("destRow"); + + // Move from the start to the middle + QTest::newRow("move01") << 0 << 2 << 8; + // Move from the start to the end + QTest::newRow("move02") << 0 << 2 << 10; + // Move from the middle to the middle + QTest::newRow("move03") << 2 << 4 << 8; + // Move from the middle to the end + QTest::newRow("move04") << 2 << 4 << 10; + + // Move from the middle to the start + QTest::newRow("move05") << 8 << 8 << 0; + // Move from the end to the start + QTest::newRow("move06") << 8 << 9 << 0; + // Move from the middle to the middle + QTest::newRow("move07") << 6 << 8 << 2; + // Move from the end to the middle + QTest::newRow("move08") << 8 << 9 << 5; + + // Moving to the same row in a different parent doesn't confuse things. + QTest::newRow("move09") << 8 << 8 << 8; + + // Moving to the row of my target and its neighbours doesn't confuse things + QTest::newRow("move09") << 8 << 8 << 4; + QTest::newRow("move10") << 8 << 8 << 5; + QTest::newRow("move11") << 8 << 8 << 6; +} + +void tst_QAbstractItemModel::testMoveToSibling() +{ + + QFETCH( int, startRow); + QFETCH( int, endRow); + QFETCH( int, destRow); + + QList persistentList; + QModelIndexList indexList; + QModelIndexList parentsList; + + const int column = 0; + + for (int i= 0; i < m_model->rowCount(); ++i) + { + QModelIndex idx = m_model->index(i, column); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + + QModelIndex destIndex = m_model->index(5, 0); + QModelIndex sourceIndex; + for (int i= 0; i < m_model->rowCount(destIndex); ++i) + { + QModelIndex idx = m_model->index(i, column, destIndex); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + + ModelMoveCommand *moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setNumCols(4); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + moveCommand->setDestAncestors(QList() << 5); + moveCommand->setDestRow(destRow); + moveCommand->doCommand(); + + QVariantList beforeSignal = beforeSpy.takeAt(0); + QVariantList afterSignal = afterSpy.takeAt(0); + + QCOMPARE(beforeSignal.size(), 5); + QCOMPARE(beforeSignal.at(0).value(), sourceIndex); + QCOMPARE(beforeSignal.at(1).toInt(), startRow); + QCOMPARE(beforeSignal.at(2).toInt(), endRow); + QCOMPARE(beforeSignal.at(3).value(), destIndex); + QCOMPARE(beforeSignal.at(4).toInt(), destRow); + + QCOMPARE(afterSignal.size(), 5); + QCOMPARE(afterSignal.at(0).value(), sourceIndex); + QCOMPARE(afterSignal.at(1).toInt(), startRow); + QCOMPARE(afterSignal.at(2).toInt(), endRow); + QCOMPARE(afterSignal.at(3).value(), destIndex); + QCOMPARE(afterSignal.at(4).toInt(), destRow); + + for (int i = 0; i < indexList.size(); i++) + { + QModelIndex idx = indexList.at(i); + QModelIndex idxParent = parentsList.at(i); + QModelIndex persistentIndex = persistentList.at(i); + + QModelIndex adjustedDestination = destIndex.sibling(destIndex.row() - (endRow - startRow + 1), destIndex.column()); + int row = idx.row(); + if (idxParent == destIndex) + { + if ( row >= destRow) + { + QCOMPARE(row + endRow - startRow + 1, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + if (idxParent.row() > startRow) + { + QCOMPARE(adjustedDestination, persistentIndex.parent()); + } else { + QCOMPARE(destIndex, persistentIndex.parent()); + } + QCOMPARE(idx.model(), persistentIndex.model()); + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + if (row < startRow) + { + QCOMPARE(idx, persistentIndex); + } else if (row <= endRow) + { + QCOMPARE(row + destRow - startRow, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + if (destIndex.row() > startRow) + { + QCOMPARE(adjustedDestination, persistentIndex.parent()); + } else { + QCOMPARE(destIndex, persistentIndex.parent()); + } + + QCOMPARE(idx.model(), persistentIndex.model()); + + } else { + QCOMPARE(row - (endRow - startRow + 1), persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idxParent, persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } + } + } +} + +void tst_QAbstractItemModel::testMoveToUncle_data() +{ + + QTest::addColumn("startRow"); + QTest::addColumn("endRow"); + QTest::addColumn("destRow"); + + // Move from the start to the middle + QTest::newRow("move01") << 0 << 2 << 8; + // Move from the start to the end + QTest::newRow("move02") << 0 << 2 << 10; + // Move from the middle to the middle + QTest::newRow("move03") << 3 << 5 << 8; + // Move from the middle to the end + QTest::newRow("move04") << 3 << 5 << 10; + + // Move from the middle to the start + QTest::newRow("move05") << 5 << 7 << 0; + // Move from the end to the start + QTest::newRow("move06") << 8 << 9 << 0; + // Move from the middle to the middle + QTest::newRow("move07") << 5 << 7 << 2; + // Move from the end to the middle + QTest::newRow("move08") << 8 << 9 << 5; + + // Moving to the same row in a different parent doesn't confuse things. + QTest::newRow("move09") << 8 << 8 << 8; + + // Moving to the row of my parent and its neighbours doesn't confuse things + QTest::newRow("move09") << 8 << 8 << 4; + QTest::newRow("move10") << 8 << 8 << 5; + QTest::newRow("move11") << 8 << 8 << 6; + + // Moving everything from one parent to another + QTest::newRow("move12") << 0 << 9 << 10; +} + +void tst_QAbstractItemModel::testMoveToUncle() +{ + // Need to have some extra rows available. + ModelInsertCommand *insertCommand = new ModelInsertCommand(m_model, this); + insertCommand->setAncestorRowNumbers(QList() << 9); + insertCommand->setNumCols(4); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + + QFETCH( int, startRow); + QFETCH( int, endRow); + QFETCH( int, destRow); + + QList persistentList; + QModelIndexList indexList; + QModelIndexList parentsList; + + const int column = 0; + + QModelIndex sourceIndex = m_model->index(9, 0); + for (int i= 0; i < m_model->rowCount(sourceIndex); ++i) + { + QModelIndex idx = m_model->index(i, column, sourceIndex); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + + QModelIndex destIndex = m_model->index(5, 0); + for (int i= 0; i < m_model->rowCount(destIndex); ++i) + { + QModelIndex idx = m_model->index(i, column, destIndex); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + ModelMoveCommand *moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setAncestorRowNumbers(QList() << 9); + moveCommand->setNumCols(4); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + moveCommand->setDestAncestors(QList() << 5); + moveCommand->setDestRow(destRow); + moveCommand->doCommand(); + + QVariantList beforeSignal = beforeSpy.takeAt(0); + QVariantList afterSignal = afterSpy.takeAt(0); + + QCOMPARE(beforeSignal.size(), 5); + QCOMPARE(beforeSignal.at(0).value(), sourceIndex); + QCOMPARE(beforeSignal.at(1).toInt(), startRow); + QCOMPARE(beforeSignal.at(2).toInt(), endRow); + QCOMPARE(beforeSignal.at(3).value(), destIndex); + QCOMPARE(beforeSignal.at(4).toInt(), destRow); + + QCOMPARE(afterSignal.size(), 5); + QCOMPARE(afterSignal.at(0).value(), sourceIndex); + QCOMPARE(afterSignal.at(1).toInt(), startRow); + QCOMPARE(afterSignal.at(2).toInt(), endRow); + QCOMPARE(afterSignal.at(3).value(), destIndex); + QCOMPARE(afterSignal.at(4).toInt(), destRow); + + for (int i = 0; i < indexList.size(); i++) + { + QModelIndex idx = indexList.at(i); + QModelIndex idxParent = parentsList.at(i); + QModelIndex persistentIndex = persistentList.at(i); + + int row = idx.row(); + if (idxParent == destIndex) + { + if ( row >= destRow) + { + QCOMPARE(row + endRow - startRow + 1, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(destIndex, persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } else + { + QCOMPARE(idx, persistentIndex); + } + } else + { + if (row < startRow) + { + QCOMPARE(idx, persistentIndex); + } else if (row <= endRow) + { + QCOMPARE(row + destRow - startRow, persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(destIndex, persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + + } else { + QCOMPARE(row - (endRow - startRow + 1), persistentIndex.row() ); + QCOMPARE(idx.column(), persistentIndex.column()); + QCOMPARE(idxParent, persistentIndex.parent()); + QCOMPARE(idx.model(), persistentIndex.model()); + } + } + } +} + +void tst_QAbstractItemModel::testMoveToDescendants() +{ + // Attempt to move a row to its ancestors depth rows deep. + const int depth = 6; + + // Need to have some extra rows available in a tree. + QList rows; + ModelInsertCommand *insertCommand; + for (int i = 0; i < depth; i++) + { + insertCommand = new ModelInsertCommand(m_model, this); + insertCommand->setAncestorRowNumbers(rows); + insertCommand->setNumCols(4); + insertCommand->setStartRow(0); + insertCommand->setEndRow(9); + insertCommand->doCommand(); + rows << 9; + } + + QList persistentList; + QModelIndexList indexList; + QModelIndexList parentsList; + + const int column = 0; + + QModelIndex sourceIndex = m_model->index(9, 0); + for (int i= 0; i < m_model->rowCount(sourceIndex); ++i) + { + QModelIndex idx = m_model->index(i, column, sourceIndex); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + + QModelIndex destIndex = m_model->index(5, 0); + for (int i= 0; i < m_model->rowCount(destIndex); ++i) + { + QModelIndex idx = m_model->index(i, column, destIndex); + QVERIFY(idx.isValid()); + indexList << idx; + parentsList << idx.parent(); + persistentList << QPersistentModelIndex(idx); + } + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + ModelMoveCommand *moveCommand; + QList ancestors; + while (ancestors.size() < depth) + { + ancestors << 9; + for (int row = 0; row <= 9; row++) + { + moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setNumCols(4); + moveCommand->setStartRow(9); + moveCommand->setEndRow(9); + moveCommand->setDestAncestors(ancestors); + moveCommand->setDestRow(row); + moveCommand->doCommand(); + + QVERIFY(beforeSpy.size() == 0); + QVERIFY(afterSpy.size() == 0); + } + } +} + +void tst_QAbstractItemModel::testMoveWithinOwnRange_data() +{ + QTest::addColumn("startRow"); + QTest::addColumn("endRow"); + QTest::addColumn("destRow"); + + QTest::newRow("move01") << 0 << 0 << 0; + QTest::newRow("move02") << 0 << 0 << 1; + QTest::newRow("move03") << 0 << 5 << 0; + QTest::newRow("move04") << 0 << 5 << 1; + QTest::newRow("move05") << 0 << 5 << 2; + QTest::newRow("move06") << 0 << 5 << 3; + QTest::newRow("move07") << 0 << 5 << 4; + QTest::newRow("move08") << 0 << 5 << 5; + QTest::newRow("move09") << 0 << 5 << 6; + QTest::newRow("move08") << 3 << 5 << 5; + QTest::newRow("move08") << 3 << 5 << 6; + QTest::newRow("move09") << 4 << 5 << 5; + QTest::newRow("move10") << 4 << 5 << 6; + QTest::newRow("move11") << 5 << 5 << 5; + QTest::newRow("move12") << 5 << 5 << 6; + QTest::newRow("move13") << 5 << 9 << 9; + QTest::newRow("move14") << 5 << 9 << 10; + QTest::newRow("move15") << 6 << 9 << 9; + QTest::newRow("move16") << 6 << 9 << 10; + QTest::newRow("move17") << 7 << 9 << 9; + QTest::newRow("move18") << 7 << 9 << 10; + QTest::newRow("move19") << 8 << 9 << 9; + QTest::newRow("move20") << 8 << 9 << 10; + QTest::newRow("move21") << 9 << 9 << 9; + QTest::newRow("move22") << 0 << 9 << 10; + +} + +void tst_QAbstractItemModel::testMoveWithinOwnRange() +{ + + QFETCH( int, startRow); + QFETCH( int, endRow); + QFETCH( int, destRow); + + + QSignalSpy beforeSpy(m_model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + QSignalSpy afterSpy(m_model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + + ModelMoveCommand *moveCommand = new ModelMoveCommand(m_model, this); + moveCommand->setNumCols(4); + moveCommand->setStartRow(startRow); + moveCommand->setEndRow(endRow); + moveCommand->setDestRow(destRow); + moveCommand->doCommand(); + + QVERIFY(beforeSpy.size() == 0); + QVERIFY(afterSpy.size() == 0); + + +} + + + QTEST_MAIN(tst_QAbstractItemModel) #include "tst_qabstractitemmodel.moc" -- cgit v0.12