diff options
Diffstat (limited to 'src/corelib/itemmodels')
-rw-r--r-- | src/corelib/itemmodels/itemmodels.pri | 20 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractitemmodel.cpp | 3464 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractitemmodel.h | 425 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractitemmodel_p.h | 176 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractproxymodel.cpp | 388 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractproxymodel.h | 113 | ||||
-rw-r--r-- | src/corelib/itemmodels/qabstractproxymodel_p.h | 76 | ||||
-rw-r--r-- | src/corelib/itemmodels/qidentityproxymodel.cpp | 578 | ||||
-rw-r--r-- | src/corelib/itemmodels/qidentityproxymodel.h | 120 | ||||
-rw-r--r-- | src/corelib/itemmodels/qitemselectionmodel.cpp | 1641 | ||||
-rw-r--r-- | src/corelib/itemmodels/qitemselectionmodel.h | 256 | ||||
-rw-r--r-- | src/corelib/itemmodels/qitemselectionmodel_p.h | 113 | ||||
-rw-r--r-- | src/corelib/itemmodels/qsortfilterproxymodel.cpp | 2703 | ||||
-rw-r--r-- | src/corelib/itemmodels/qsortfilterproxymodel.h | 205 | ||||
-rw-r--r-- | src/corelib/itemmodels/qstringlistmodel.cpp | 310 | ||||
-rw-r--r-- | src/corelib/itemmodels/qstringlistmodel.h | 91 |
16 files changed, 10679 insertions, 0 deletions
diff --git a/src/corelib/itemmodels/itemmodels.pri b/src/corelib/itemmodels/itemmodels.pri new file mode 100644 index 0000000000..83ec4c5dbf --- /dev/null +++ b/src/corelib/itemmodels/itemmodels.pri @@ -0,0 +1,20 @@ +# Qt itemmodels core module + +HEADERS += \ + itemmodels/qabstractitemmodel.h \ + itemmodels/qabstractitemmodel_p.h \ + itemmodels/qabstractproxymodel.h \ + itemmodels/qabstractproxymodel_p.h \ + itemmodels/qitemselectionmodel.h \ + itemmodels/qitemselectionmodel_p.h \ + itemmodels/qidentityproxymodel.h \ + itemmodels/qsortfilterproxymodel.h \ + itemmodels/qstringlistmodel.h + +SOURCES += \ + itemmodels/qabstractitemmodel.cpp \ + itemmodels/qabstractproxymodel.cpp \ + itemmodels/qitemselectionmodel.cpp \ + itemmodels/qidentityproxymodel.cpp \ + itemmodels/qsortfilterproxymodel.cpp \ + itemmodels/qstringlistmodel.cpp diff --git a/src/corelib/itemmodels/qabstractitemmodel.cpp b/src/corelib/itemmodels/qabstractitemmodel.cpp new file mode 100644 index 0000000000..dec1fe4cef --- /dev/null +++ b/src/corelib/itemmodels/qabstractitemmodel.cpp @@ -0,0 +1,3464 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractitemmodel.h" +#include <private/qabstractitemmodel_p.h> +#include <qdatastream.h> +#include <qstringlist.h> +#include <qsize.h> +#include <qmimedata.h> +#include <qdebug.h> +#include <qvector.h> +#include <qstack.h> +#include <qbitarray.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +QPersistentModelIndexData *QPersistentModelIndexData::create(const QModelIndex &index) +{ + Q_ASSERT(index.isValid()); // we will _never_ insert an invalid index in the list + QPersistentModelIndexData *d = 0; + QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model()); + QHash<QModelIndex, QPersistentModelIndexData *> &indexes = model->d_func()->persistent.indexes; + const QHash<QModelIndex, QPersistentModelIndexData *>::iterator it = indexes.find(index); + if (it != indexes.end()) { + d = (*it); + } else { + d = new QPersistentModelIndexData(index); + indexes.insert(index, d); + } + Q_ASSERT(d); + return d; +} + +void QPersistentModelIndexData::destroy(QPersistentModelIndexData *data) +{ + Q_ASSERT(data); + Q_ASSERT(data->ref.load() == 0); + QAbstractItemModel *model = const_cast<QAbstractItemModel *>(data->model); + // a valid persistent model index with a null model pointer can only happen if the model was destroyed + if (model) { + QAbstractItemModelPrivate *p = model->d_func(); + Q_ASSERT(p); + p->removePersistentIndexData(data); + } + delete data; +} + +/*! + \class QPersistentModelIndex + + \brief The QPersistentModelIndex class is used to locate data in a data model. + + \ingroup model-view + + A QPersistentModelIndex is a model index that can be stored by an + application, and later used to access information in a model. + Unlike the QModelIndex class, it is safe to store a + QPersistentModelIndex since the model will ensure that references + to items will continue to be valid as long as they can be accessed + by the model. + + It is good practice to check that persistent model indexes are valid + before using them. + + \sa {Model/View Programming}, QModelIndex, QAbstractItemModel +*/ + + +/*! + \fn QPersistentModelIndex::QPersistentModelIndex() + + \internal +*/ + +QPersistentModelIndex::QPersistentModelIndex() + : d(0) +{ +} + +/*! + \fn QPersistentModelIndex::QPersistentModelIndex(const QPersistentModelIndex &other) + + Creates a new QPersistentModelIndex that is a copy of the \a other persistent + model index. +*/ + +QPersistentModelIndex::QPersistentModelIndex(const QPersistentModelIndex &other) + : d(other.d) +{ + if (d) d->ref.ref(); +} + +/*! + Creates a new QPersistentModelIndex that is a copy of the model \a index. +*/ + +QPersistentModelIndex::QPersistentModelIndex(const QModelIndex &index) + : d(0) +{ + if (index.isValid()) { + d = QPersistentModelIndexData::create(index); + d->ref.ref(); + } +} + +/*! + \fn QPersistentModelIndex::~QPersistentModelIndex() + + \internal +*/ + +QPersistentModelIndex::~QPersistentModelIndex() +{ + if (d && !d->ref.deref()) { + QPersistentModelIndexData::destroy(d); + d = 0; + } +} + +/*! + Returns true if this persistent model index is equal to the \a other + persistent model index; otherwise returns false. + + All values in the persistent model index are used when comparing + with another persistent model index. +*/ + +bool QPersistentModelIndex::operator==(const QPersistentModelIndex &other) const +{ + if (d && other.d) + return d->index == other.d->index; + return d == other.d; +} + +/*! + \since 4.1 + + Returns true if this persistent model index is smaller than the \a other + persistent model index; otherwise returns false. + + All values in the persistent model index are used when comparing + with another persistent model index. +*/ + +bool QPersistentModelIndex::operator<(const QPersistentModelIndex &other) const +{ + if (d && other.d) + return d->index < other.d->index; + + return d < other.d; +} + +/*! + \fn bool QPersistentModelIndex::operator!=(const QPersistentModelIndex &other) const + \since 4.2 + + Returns true if this persistent model index is not equal to the \a + other persistent model index; otherwise returns false. +*/ + +/*! + Sets the persistent model index to refer to the same item in a model + as the \a other persistent model index. +*/ + +QPersistentModelIndex &QPersistentModelIndex::operator=(const QPersistentModelIndex &other) +{ + if (d == other.d) + return *this; + if (d && !d->ref.deref()) + QPersistentModelIndexData::destroy(d); + d = other.d; + if (d) d->ref.ref(); + return *this; +} + +/*! + Sets the persistent model index to refer to the same item in a model + as the \a other model index. +*/ + +QPersistentModelIndex &QPersistentModelIndex::operator=(const QModelIndex &other) +{ + if (d && !d->ref.deref()) + QPersistentModelIndexData::destroy(d); + if (other.isValid()) { + d = QPersistentModelIndexData::create(other); + if (d) d->ref.ref(); + } else { + d = 0; + } + return *this; +} + +/*! + \fn QPersistentModelIndex::operator const QModelIndex&() const + + Cast operator that returns a const QModelIndex&. +*/ + +QPersistentModelIndex::operator const QModelIndex&() const +{ + static const QModelIndex invalid; + if (d) + return d->index; + return invalid; +} + +/*! + \fn bool QPersistentModelIndex::operator==(const QModelIndex &other) const + + Returns true if this persistent model index refers to the same location as + the \a other model index; otherwise returns false. + + All values in the persistent model index are used when comparing with + another model index. +*/ + +bool QPersistentModelIndex::operator==(const QModelIndex &other) const +{ + if (d) + return d->index == other; + return !other.isValid(); +} + +/*! + \fn bool QPersistentModelIndex::operator!=(const QModelIndex &other) const + + Returns true if this persistent model index does not refer to the same + location as the \a other model index; otherwise returns false. +*/ + +bool QPersistentModelIndex::operator!=(const QModelIndex &other) const +{ + if (d) + return d->index != other; + return other.isValid(); +} + +/*! + \fn int QPersistentModelIndex::row() const + + Returns the row this persistent model index refers to. +*/ + +int QPersistentModelIndex::row() const +{ + if (d) + return d->index.row(); + return -1; +} + +/*! + \fn int QPersistentModelIndex::column() const + + Returns the column this persistent model index refers to. +*/ + +int QPersistentModelIndex::column() const +{ + if (d) + return d->index.column(); + return -1; +} + +/*! + \fn void *QPersistentModelIndex::internalPointer() const + + \internal + + Returns a \c{void} \c{*} pointer used by the model to associate the index with + the internal data structure. +*/ + +void *QPersistentModelIndex::internalPointer() const +{ + if (d) + return d->index.internalPointer(); + return 0; +} + +/*! + \fn void *QPersistentModelIndex::internalId() const + + \internal + + Returns a \c{qint64} used by the model to associate the index with + the internal data structure. +*/ + +qint64 QPersistentModelIndex::internalId() const +{ + if (d) + return d->index.internalId(); + return 0; +} + +/*! + Returns the parent QModelIndex for this persistent index, or an invalid + QModelIndex if it has no parent. + + \sa child() sibling() model() +*/ +QModelIndex QPersistentModelIndex::parent() const +{ + if (d) + return d->index.parent(); + return QModelIndex(); +} + +/*! + Returns the sibling at \a row and \a column or an invalid QModelIndex if + there is no sibling at this position. + + \sa parent() child() +*/ + +QModelIndex QPersistentModelIndex::sibling(int row, int column) const +{ + if (d) + return d->index.sibling(row, column); + return QModelIndex(); +} + +/*! + Returns the child of the model index that is stored in the given \a row + and \a column. + + \sa parent() sibling() +*/ + +QModelIndex QPersistentModelIndex::child(int row, int column) const +{ + if (d) + return d->index.child(row, column); + return QModelIndex(); +} + +/*! + Returns the data for the given \a role for the item referred to by the + index. + + \sa Qt::ItemDataRole, QAbstractItemModel::setData() +*/ +QVariant QPersistentModelIndex::data(int role) const +{ + if (d) + return d->index.data(role); + return QVariant(); +} + +/*! + \since 4.2 + + Returns the flags for the item referred to by the index. +*/ +Qt::ItemFlags QPersistentModelIndex::flags() const +{ + if (d) + return d->index.flags(); + return 0; +} + +/*! + Returns the model that the index belongs to. +*/ +const QAbstractItemModel *QPersistentModelIndex::model() const +{ + if (d) + return d->index.model(); + return 0; +} + +/*! + \fn bool QPersistentModelIndex::isValid() const + + Returns true if this persistent model index is valid; otherwise returns + false. + + A valid index belongs to a model, and has non-negative row and column + numbers. + + \sa model(), row(), column() +*/ + +bool QPersistentModelIndex::isValid() const +{ + return d && d->index.isValid(); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QModelIndex &idx) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QModelIndex(" << idx.row() << ',' << idx.column() + << ',' << idx.internalPointer() << ',' << idx.model() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QModelIndex to QDebug"); + return dbg; + Q_UNUSED(idx); +#endif +} + +QDebug operator<<(QDebug dbg, const QPersistentModelIndex &idx) +{ + if (idx.d) + dbg << idx.d->index; + else + dbg << QModelIndex(); + return dbg; +} +#endif + +class QEmptyItemModel : public QAbstractItemModel +{ +public: + explicit QEmptyItemModel(QObject *parent = 0) : QAbstractItemModel(parent) {} + QModelIndex index(int, int, const QModelIndex &) const { return QModelIndex(); } + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + int rowCount(const QModelIndex &) const { return 0; } + int columnCount(const QModelIndex &) const { return 0; } + bool hasChildren(const QModelIndex &) const { return false; } + QVariant data(const QModelIndex &, int) const { return QVariant(); } +}; + +Q_GLOBAL_STATIC(QEmptyItemModel, qEmptyModel) + +QAbstractItemModel *QAbstractItemModelPrivate::staticEmptyModel() +{ + return qEmptyModel(); +} + +namespace { + struct DefaultRoleNames : public QHash<int, QByteArray> + { + DefaultRoleNames() { + (*this)[Qt::DisplayRole] = "display"; + (*this)[Qt::DecorationRole] = "decoration"; + (*this)[Qt::EditRole] = "edit"; + (*this)[Qt::ToolTipRole] = "toolTip"; + (*this)[Qt::StatusTipRole] = "statusTip"; + (*this)[Qt::WhatsThisRole] = "whatsThis"; + } + }; +} + +Q_GLOBAL_STATIC(DefaultRoleNames, qDefaultRoleNames) + +const QHash<int,QByteArray> &QAbstractItemModelPrivate::defaultRoleNames() +{ + return *qDefaultRoleNames(); +} + + +static uint typeOfVariant(const QVariant &value) +{ + //return 0 for integer, 1 for floating point and 2 for other + switch (value.userType()) { + case QVariant::Bool: + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::Char: + case QMetaType::Short: + case QMetaType::UShort: + case QMetaType::UChar: + case QMetaType::ULong: + case QMetaType::Long: + return 0; + case QVariant::Double: + case QMetaType::Float: + return 1; + default: + return 2; + } +} + +/*! + \internal + return true if \a value contains a numerical type + + This function is used by our Q{Tree,Widget,Table}WidgetModel classes to sort. +*/ +bool QAbstractItemModelPrivate::variantLessThan(const QVariant &v1, const QVariant &v2) +{ + switch(qMax(typeOfVariant(v1), typeOfVariant(v2))) + { + case 0: //integer type + return v1.toLongLong() < v2.toLongLong(); + case 1: //floating point + return v1.toReal() < v2.toReal(); + default: + return v1.toString().localeAwareCompare(v2.toString()) < 0; + } +} + +void QAbstractItemModelPrivate::removePersistentIndexData(QPersistentModelIndexData *data) +{ + if (data->index.isValid()) { + int removed = persistent.indexes.remove(data->index); + Q_ASSERT_X(removed == 1, "QPersistentModelIndex::~QPersistentModelIndex", + "persistent model indexes corrupted"); //maybe the index was somewhat invalid? + // This assert may happen if the model use changePersistentIndex in a way that could result on two + // QPersistentModelIndex pointing to the same index. + Q_UNUSED(removed); + } + // make sure our optimization still works + for (int i = persistent.moved.count() - 1; i >= 0; --i) { + int idx = persistent.moved[i].indexOf(data); + if (idx >= 0) + persistent.moved[i].remove(idx); + } + // update the references to invalidated persistent indexes + for (int i = persistent.invalidated.count() - 1; i >= 0; --i) { + int idx = persistent.invalidated[i].indexOf(data); + if (idx >= 0) + persistent.invalidated[i].remove(idx); + } + +} + +void QAbstractItemModelPrivate::rowsAboutToBeInserted(const QModelIndex &parent, + int first, int last) +{ + Q_Q(QAbstractItemModel); + Q_UNUSED(last); + QVector<QPersistentModelIndexData *> persistent_moved; + if (first < q->rowCount(parent)) { + for (QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator it = persistent.indexes.constBegin(); + it != persistent.indexes.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + const QModelIndex &index = data->index; + if (index.row() >= first && index.isValid() && index.parent() == parent) { + persistent_moved.append(data); + } + } + } + persistent.moved.push(persistent_moved); +} + +void QAbstractItemModelPrivate::rowsInserted(const QModelIndex &parent, + int first, int last) +{ + QVector<QPersistentModelIndexData *> persistent_moved = persistent.moved.pop(); + int count = (last - first) + 1; // it is important to only use the delta, because the change could be nested + for (QVector<QPersistentModelIndexData *>::const_iterator it = persistent_moved.constBegin(); + it != persistent_moved.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + QModelIndex old = data->index; + persistent.indexes.erase(persistent.indexes.find(old)); + data->index = q_func()->index(old.row() + count, old.column(), parent); + if (data->index.isValid()) { + persistent.insertMultiAtEnd(data->index, data); + } else { + qWarning() << "QAbstractItemModel::endInsertRows: Invalid index (" << old.row() + count << ',' << old.column() << ") in model" << q_func(); + } + } +} + +void QAbstractItemModelPrivate::itemsAboutToBeMoved(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation) +{ + QVector<QPersistentModelIndexData *> persistent_moved_explicitly; + QVector<QPersistentModelIndexData *> persistent_moved_in_source; + QVector<QPersistentModelIndexData *> persistent_moved_in_destination; + + QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator it; + const QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator begin = persistent.indexes.constBegin(); + const QHash<QModelIndex, QPersistentModelIndexData *>::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<QPersistentModelIndexData *> indexes, int change, const QModelIndex &parent, Qt::Orientation orientation) +{ + QVector<QPersistentModelIndexData *>::const_iterator it; + const QVector<QPersistentModelIndexData *>::const_iterator begin = indexes.constBegin(); + const QVector<QPersistentModelIndexData *>::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<QPersistentModelIndexData *> moved_in_destination = persistent.moved.pop(); + QVector<QPersistentModelIndexData *> moved_in_source = persistent.moved.pop(); + QVector<QPersistentModelIndexData *> 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) +{ + QVector<QPersistentModelIndexData *> persistent_moved; + QVector<QPersistentModelIndexData *> persistent_invalidated; + // find the persistent indexes that are affected by the change, either by being in the removed subtree + // or by being on the same level and below the removed rows + for (QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator it = persistent.indexes.constBegin(); + it != persistent.indexes.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + bool level_changed = false; + QModelIndex current = data->index; + while (current.isValid()) { + QModelIndex current_parent = current.parent(); + if (current_parent == parent) { // on the same level as the change + if (!level_changed && current.row() > last) // below the removed rows + persistent_moved.append(data); + else if (current.row() <= last && current.row() >= first) // in the removed subtree + persistent_invalidated.append(data); + break; + } + current = current_parent; + level_changed = true; + } + } + + persistent.moved.push(persistent_moved); + persistent.invalidated.push(persistent_invalidated); +} + +void QAbstractItemModelPrivate::rowsRemoved(const QModelIndex &parent, + int first, int last) +{ + QVector<QPersistentModelIndexData *> persistent_moved = persistent.moved.pop(); + int count = (last - first) + 1; // it is important to only use the delta, because the change could be nested + for (QVector<QPersistentModelIndexData *>::const_iterator it = persistent_moved.constBegin(); + it != persistent_moved.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + QModelIndex old = data->index; + persistent.indexes.erase(persistent.indexes.find(old)); + data->index = q_func()->index(old.row() - count, old.column(), parent); + if (data->index.isValid()) { + persistent.insertMultiAtEnd(data->index, data); + } else { + qWarning() << "QAbstractItemModel::endRemoveRows: Invalid index (" << old.row() - count << ',' << old.column() << ") in model" << q_func(); + } + } + QVector<QPersistentModelIndexData *> persistent_invalidated = persistent.invalidated.pop(); + for (QVector<QPersistentModelIndexData *>::const_iterator it = persistent_invalidated.constBegin(); + it != persistent_invalidated.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + persistent.indexes.erase(persistent.indexes.find(data->index)); + data->index = QModelIndex(); + data->model = 0; + } +} + +void QAbstractItemModelPrivate::columnsAboutToBeInserted(const QModelIndex &parent, + int first, int last) +{ + Q_Q(QAbstractItemModel); + Q_UNUSED(last); + QVector<QPersistentModelIndexData *> persistent_moved; + if (first < q->columnCount(parent)) { + for (QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator it = persistent.indexes.constBegin(); + it != persistent.indexes.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + const QModelIndex &index = data->index; + if (index.column() >= first && index.isValid() && index.parent() == parent) + persistent_moved.append(data); + } + } + persistent.moved.push(persistent_moved); +} + +void QAbstractItemModelPrivate::columnsInserted(const QModelIndex &parent, + int first, int last) +{ + QVector<QPersistentModelIndexData *> persistent_moved = persistent.moved.pop(); + int count = (last - first) + 1; // it is important to only use the delta, because the change could be nested + for (QVector<QPersistentModelIndexData *>::const_iterator it = persistent_moved.constBegin(); + it != persistent_moved.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + QModelIndex old = data->index; + persistent.indexes.erase(persistent.indexes.find(old)); + data->index = q_func()->index(old.row(), old.column() + count, parent); + if (data->index.isValid()) { + persistent.insertMultiAtEnd(data->index, data); + } else { + qWarning() << "QAbstractItemModel::endInsertColumns: Invalid index (" << old.row() << ',' << old.column() + count << ") in model" << q_func(); + } + } +} + +void QAbstractItemModelPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, + int first, int last) +{ + QVector<QPersistentModelIndexData *> persistent_moved; + QVector<QPersistentModelIndexData *> persistent_invalidated; + // find the persistent indexes that are affected by the change, either by being in the removed subtree + // or by being on the same level and to the right of the removed columns + for (QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator it = persistent.indexes.constBegin(); + it != persistent.indexes.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + bool level_changed = false; + QModelIndex current = data->index; + while (current.isValid()) { + QModelIndex current_parent = current.parent(); + if (current_parent == parent) { // on the same level as the change + if (!level_changed && current.column() > last) // right of the removed columns + persistent_moved.append(data); + else if (current.column() <= last && current.column() >= first) // in the removed subtree + persistent_invalidated.append(data); + break; + } + current = current_parent; + level_changed = true; + } + } + + persistent.moved.push(persistent_moved); + persistent.invalidated.push(persistent_invalidated); + +} + +void QAbstractItemModelPrivate::columnsRemoved(const QModelIndex &parent, + int first, int last) +{ + QVector<QPersistentModelIndexData *> persistent_moved = persistent.moved.pop(); + int count = (last - first) + 1; // it is important to only use the delta, because the change could be nested + for (QVector<QPersistentModelIndexData *>::const_iterator it = persistent_moved.constBegin(); + it != persistent_moved.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + QModelIndex old = data->index; + persistent.indexes.erase(persistent.indexes.find(old)); + data->index = q_func()->index(old.row(), old.column() - count, parent); + if (data->index.isValid()) { + persistent.insertMultiAtEnd(data->index, data); + } else { + qWarning() << "QAbstractItemModel::endRemoveColumns: Invalid index (" << old.row() << ',' << old.column() - count << ") in model" << q_func(); + } + } + QVector<QPersistentModelIndexData *> persistent_invalidated = persistent.invalidated.pop(); + for (QVector<QPersistentModelIndexData *>::const_iterator it = persistent_invalidated.constBegin(); + it != persistent_invalidated.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + persistent.indexes.erase(persistent.indexes.find(data->index)); + data->index = QModelIndex(); + data->model = 0; + } +} + +/*! + \class QModelIndex + + \brief The QModelIndex class is used to locate data in a data model. + + \ingroup model-view + + + This class is used as an index into item models derived from + QAbstractItemModel. The index is used by item views, delegates, and + selection models to locate an item in the model. + + New QModelIndex objects are created by the model using the + QAbstractItemModel::createIndex() function. An \e invalid model index can + be constructed with the QModelIndex constructor. Invalid indexes are often + used as parent indexes when referring to top-level items in a model. + + Model indexes refer to items in models, and contain all the information + required to specify their locations in those models. Each index is located + in a given row and column, and may have a parent index; use row(), + column(), and parent() to obtain this information. Each top-level item in a + model is represented by a model index that does not have a parent index - + in this case, parent() will return an invalid model index, equivalent to an + index constructed with the zero argument form of the QModelIndex() + constructor. + + To obtain a model index that refers to an existing item in a model, call + QAbstractItemModel::index() with the required row and column values, and + the model index of the parent. When referring to top-level items in a + model, supply QModelIndex() as the parent index. + + The model() function returns the model that the index references as a + QAbstractItemModel. The child() function is used to examine items held + under the index in the model. The sibling() function allows you to traverse + items in the model on the same level as the index. + + \note Model indexes should be used immediately and then discarded. You + should not rely on indexes to remain valid after calling model functions + that change the structure of the model or delete items. If you need to + keep a model index over time use a QPersistentModelIndex. + + \sa {Model/View Programming}, QPersistentModelIndex, QAbstractItemModel +*/ + +/*! + \fn QModelIndex::QModelIndex() + + Creates a new empty model index. This type of model index is used to + indicate that the position in the model is invalid. + + \sa isValid() QAbstractItemModel +*/ + +/*! + \fn QModelIndex::QModelIndex(int row, int column, void *data, const QAbstractItemModel *model) + + \internal + + Creates a new model index at the given \a row and \a column, + pointing to some \a data. +*/ + +/*! + \fn QModelIndex::QModelIndex(const QModelIndex &other) + + Creates a new model index that is a copy of the \a other model + index. +*/ + +/*! + \fn QModelIndex::~QModelIndex() + + Destroys the model index. +*/ + +/*! + \fn int QModelIndex::row() const + + Returns the row this model index refers to. +*/ + + +/*! + \fn int QModelIndex::column() const + + Returns the column this model index refers to. +*/ + + +/*! + \fn void *QModelIndex::internalPointer() const + + Returns a \c{void} \c{*} pointer used by the model to associate + the index with the internal data structure. + + \sa QAbstractItemModel::createIndex() +*/ + +/*! + \fn void *QModelIndex::internalId() const + + Returns a \c{qint64} used by the model to associate + the index with the internal data structure. + + \sa QAbstractItemModel::createIndex() +*/ + +/*! + \fn bool QModelIndex::isValid() const + + Returns true if this model index is valid; otherwise returns false. + + A valid index belongs to a model, and has non-negative row and column + numbers. + + \sa model(), row(), column() +*/ + +/*! + \fn const QAbstractItemModel *QModelIndex::model() const + + Returns a pointer to the model containing the item that this index + refers to. + + A const pointer to the model is returned because calls to non-const + functions of the model might invalidate the model index and possibly + crash your application. +*/ + +/*! + \fn QModelIndex QModelIndex::sibling(int row, int column) const + + Returns the sibling at \a row and \a column. If there is no sibling at this + position, an invalid QModelIndex is returned. + + \sa parent(), child() +*/ + +/*! + \fn QModelIndex QModelIndex::child(int row, int column) const + + Returns the child of the model index that is stored in the given \a row and + \a column. + + \note This function does not work for an invalid model index which is often + used as the root index. + + \sa parent(), sibling() +*/ + +/*! + \fn QVariant QModelIndex::data(int role) const + + Returns the data for the given \a role for the item referred to by the + index. +*/ + +/*! + \fn Qt::ItemFlags QModelIndex::flags() const + \since 4.2 + + Returns the flags for the item referred to by the index. +*/ + +/*! + \fn bool QModelIndex::operator==(const QModelIndex &other) const + + Returns true if this model index refers to the same location as the + \a other model index; otherwise returns false. + + All values in the model index are used when comparing with another model + index. +*/ + + +/*! + \fn bool QModelIndex::operator!=(const QModelIndex &other) const + + Returns true if this model index does not refer to the same location as + the \a other model index; otherwise returns false. +*/ + + +/*! + \fn QModelIndex QModelIndex::parent() const + + Returns the parent of the model index, or QModelIndex() if it has no + parent. + + \sa child(), sibling(), model() +*/ + +/*! + \class QAbstractItemModel + + \brief The QAbstractItemModel class provides the abstract interface for + item model classes. + + \ingroup model-view + + + The QAbstractItemModel class defines the standard interface that item + models must use to be able to interoperate with other components in the + model/view architecture. It is not supposed to be instantiated directly. + Instead, you should subclass it to create new models. + + The QAbstractItemModel class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + If you need a model to use with a QListView or a QTableView, you should + consider subclassing QAbstractListModel or QAbstractTableModel instead of + this class. + + The underlying data model is exposed to views and delegates as a hierarchy + of tables. If you do not make use of the hierarchy, then the model is a + simple table of rows and columns. Each item has a unique index specified by + a QModelIndex. + + \image modelindex-no-parent.png + + Every item of data that can be accessed via a model has an associated model + index. You can obtain this model index using the index() function. Each + index may have a sibling() index; child items have a parent() index. + + Each item has a number of data elements associated with it and they can be + retrieved by specifying a role (see \l Qt::ItemDataRole) to the model's + data() function. Data for all available roles can be obtained at the same + time using the itemData() function. + + Data for each role is set using a particular \l Qt::ItemDataRole. Data for + individual roles are set individually with setData(), or they can be set + for all roles with setItemData(). + + Items can be queried with flags() (see \l Qt::ItemFlag) to see if they can + be selected, dragged, or manipulated in other ways. + + If an item has child objects, hasChildren() returns true for the + corresponding index. + + The model has a rowCount() and a columnCount() for each level of the + hierarchy. Rows and columns can be inserted and removed with insertRows(), + insertColumns(), removeRows(), and removeColumns(). + + The model emits signals to indicate changes. For example, dataChanged() is + emitted whenever items of data made available by the model are changed. + Changes to the headers supplied by the model cause headerDataChanged() to + be emitted. If the structure of the underlying data changes, the model can + emit layoutChanged() to indicate to any attached views that they should + redisplay any items shown, taking the new structure into account. + + The items available through the model can be searched for particular data + using the match() function. + + To sort the model, you can use sort(). + + + \section1 Subclassing + + \note Some general guidelines for subclassing models are available in the + \l{Model Subclassing Reference}. + + When subclassing QAbstractItemModel, at the very least you must implement + index(), parent(), rowCount(), columnCount(), and data(). These functions + are used in all read-only models, and form the basis of editable models. + + You can also reimplement hasChildren() to provide special behavior for + models where the implementation of rowCount() is expensive. This makes it + possible for models to restrict the amount of data requested by views, and + can be used as a way to implement lazy population of model data. + + To enable editing in your model, you must also implement setData(), and + reimplement flags() to ensure that \c ItemIsEditable is returned. You can + also reimplement headerData() and setHeaderData() to control the way the + headers for your model are presented. + + The dataChanged() and headerDataChanged() signals must be emitted + explicitly when reimplementing the setData() and setHeaderData() functions, + respectively. + + Custom models need to create model indexes for other components to use. To + do this, call createIndex() with suitable row and column numbers for the + item, and an identifier for it, either as a pointer or as an integer value. + The combination of these values must be unique for each item. Custom models + typically use these unique identifiers in other reimplemented functions to + retrieve item data and access information about the item's parents and + children. See the \l{Simple Tree Model Example} for more information about + unique identifiers. + + It is not necessary to support every role defined in Qt::ItemDataRole. + Depending on the type of data contained within a model, it may only be + useful to implement the data() function to return valid information for + some of the more common roles. Most models provide at least a textual + representation of item data for the Qt::DisplayRole, and well-behaved + models should also provide valid information for the Qt::ToolTipRole and + Qt::WhatsThisRole. Supporting these roles enables models to be used with + standard Qt views. However, for some models that handle highly-specialized + data, it may be appropriate to provide data only for user-defined roles. + + Models that provide interfaces to resizable data structures can provide + implementations of insertRows(), removeRows(), insertColumns(),and + removeColumns(). When implementing these functions, it is important to + notify any connected views about changes to the model's dimensions both + \e before and \e after they occur: + + \list + \o An insertRows() implementation must call beginInsertRows() \e before + inserting new rows into the data structure, and endInsertRows() + \e{immediately afterwards}. + \o An insertColumns() implementation must call beginInsertColumns() + \e before inserting new columns into the data structure, and + endInsertColumns() \e{immediately afterwards}. + \o A removeRows() implementation must call beginRemoveRows() \e before + the rows are removed from the data structure, and endRemoveRows() + \e{immediately afterwards}. + \o A removeColumns() implementation must call beginRemoveColumns() + \e before the columns are removed from the data structure, and + endRemoveColumns() \e{immediately afterwards}. + \endlist + + The \e private signals that these functions emit give attached components + the chance to take action before any data becomes unavailable. The + encapsulation of the insert and remove operations with these begin and end + functions also enables the model to manage \l{QPersistentModelIndex} + {persistent model indexes} correctly. \bold{If you want selections to be + handled properly, you must ensure that you call these functions.} If you + insert or remove an item with children, you do not need to call these + functions for the child items. In other words, the parent item will take + care of its child items. + + To create models that populate incrementally, you can reimplement + fetchMore() and canFetchMore(). If the reimplementation of fetchMore() adds + rows to the model, \l{QAbstractItemModel::}{beginInsertRows()} and + \l{QAbstractItemModel::}{endInsertRows()} must be called. + + \sa {Model Classes}, {Model Subclassing Reference}, QModelIndex, + QAbstractItemView, {Using drag and drop with item views}, + {Simple DOM Model Example}, {Simple Tree Model Example}, + {Editable Tree Model Example}, {Fetch More Example} +*/ + +/*! + \fn QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex &parent) const = 0 + + Returns the index of the item in the model specified by the given \a row, + \a column and \a parent index. + + When reimplementing this function in a subclass, call createIndex() to + generate model indexes that other components can use to refer to items in + your model. + + \sa createIndex() +*/ + +/*! + \fn bool QAbstractItemModel::insertColumn(int column, const QModelIndex &parent) + + Inserts a single column before the given \a column in the child items of + the \a parent specified. + + Returns true if the column is inserted; otherwise returns false. + + \sa insertColumns() insertRow() removeColumn() +*/ + +/*! + \fn bool QAbstractItemModel::insertRow(int row, const QModelIndex &parent) + + \note The base class implementation of this function does nothing and + returns false. + + Inserts a single row before the given \a row in the child items of the + \a parent specified. + + Returns true if the row is inserted; otherwise returns false. + + \sa insertRows() insertColumn() removeRow() +*/ + +/*! + \fn QObject *QAbstractItemModel::parent() const + \internal +*/ + +/*! + \fn QModelIndex QAbstractItemModel::parent(const QModelIndex &index) const = 0 + + Returns the parent of the model item with the given \a index. If the item + has no parent, an invalid QModelIndex is returned. + + A common convention used in models that expose tree data structures is that + only items in the first column have children. For that case, when + reimplementing this function in a subclass the column of the returned + QModelIndex would be 0. + + When reimplementing this function in a subclass, be careful to avoid + calling QModelIndex member functions, such as QModelIndex::parent(), since + indexes belonging to your model will simply call your implementation, + leading to infinite recursion. + + \sa createIndex() +*/ + +/*! + \fn bool QAbstractItemModel::removeColumn(int column, const QModelIndex &parent) + + Removes the given \a column from the child items of the \a parent + specified. + + Returns true if the column is removed; otherwise returns false. + + \sa removeColumns(), removeRow(), insertColumn() +*/ + +/*! + \fn bool QAbstractItemModel::removeRow(int row, const QModelIndex &parent) + + Removes the given \a row from the child items of the \a parent specified. + + Returns true if the row is removed; otherwise returns false. + + This is a convenience function that calls removeRows(). The + QAbstractItemModel implementation of removeRows() does nothing. + + \sa removeRows(), removeColumn(), insertRow() +*/ + +/*! + \fn void QAbstractItemModel::headerDataChanged(Qt::Orientation orientation, int first, int last) + + This signal is emitted whenever a header is changed. The \a orientation + indicates whether the horizontal or vertical header has changed. The + sections in the header from the \a first to the \a last need to be updated. + + When reimplementing the setHeaderData() function, this signal must be + emitted explicitly. + + If you are changing the number of columns or rows you do not need to emit + this signal, but use the begin/end functions (refer to the section on + subclassing in the QAbstractItemModel class description for details). + + \sa headerData(), setHeaderData(), dataChanged() +*/ + +/*! + \fn void QAbstractItemModel::layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>()) + \since 4.2 + + This signal is emitted just before the layout of a model is changed. + Components connected to this signal use it to adapt to changes in the + model's layout. + + Subclasses should update any persistent model indexes after emitting + layoutAboutToBeChanged(). + + The optional @p parents parameter is used to give a more specific notification + about what parts of the layout of the model are changing. An empty list indicates + a change to the layout of the entire model. + + \sa layoutChanged(), changePersistentIndex() +*/ + +/*! + \fn void QAbstractItemModel::layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>()) + + This signal is emitted whenever the layout of items exposed by the model + has changed; for example, when the model has been sorted. When this signal + is received by a view, it should update the layout of items to reflect this + change. + + When subclassing QAbstractItemModel or QAbstractProxyModel, ensure that you + emit layoutAboutToBeChanged() before changing the order of items or + altering the structure of the data you expose to views, and emit + layoutChanged() after changing the layout. + + The optional @p parents parameter is used to give a more specific notification + about what parts of the layout of the model are changing. An empty list indicates + a change to the layout of the entire model. + + Subclasses should update any persistent model indexes before emitting + layoutChanged(). In other words, when the structure changes: + + \list + \o emit layoutAboutToBeChanged + \o Remember the QModelIndex that will change + \o Update your internal data + \o Call changePersistentIndex() + \o emit layoutChanged + \endlist + + \sa layoutAboutToBeChanged(), dataChanged(), headerDataChanged(), modelReset(), + changePersistentIndex() +*/ + +/*! + Constructs an abstract item model with the given \a parent. +*/ +QAbstractItemModel::QAbstractItemModel(QObject *parent) + : QObject(*new QAbstractItemModelPrivate, parent) +{ +} + +/*! + \internal +*/ +QAbstractItemModel::QAbstractItemModel(QAbstractItemModelPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ +} + +/*! + Destroys the abstract item model. +*/ +QAbstractItemModel::~QAbstractItemModel() +{ + d_func()->invalidatePersistentIndexes(); +} + +/*! + \fn QModelIndex QAbstractItemModel::sibling(int row, int column, const QModelIndex &index) const + + Returns the sibling at \a row and \a column for the item at \a index, or an + invalid QModelIndex if there is no sibling at that location. + + sibling() is just a convenience function that finds the item's parent, and + uses it to retrieve the index of the child item in the specified \a row and + \a column. + + \sa index(), QModelIndex::row(), QModelIndex::column() +*/ + + +/*! + \fn int QAbstractItemModel::rowCount(const QModelIndex &parent) const + + Returns the number of rows under the given \a parent. When the parent is + valid it means that rowCount is returning the number of children of parent. + + \note When implementing a table based model, rowCount() should return 0 + when the parent is valid. + + \sa columnCount() +*/ + +/*! + \fn int QAbstractItemModel::columnCount(const QModelIndex &parent) const + + Returns the number of columns for the children of the given \a parent. + + In most subclasses, the number of columns is independent of the \a parent. + + For example: + + \snippet examples/itemviews/simpledommodel/dommodel.cpp 2 + + \note When implementing a table based model, columnCount() should return 0 + when the parent is valid. + + \sa rowCount() +*/ + +/*! + \fn void QAbstractItemModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QSet<int> &roles = QSet<int>()) + + This signal is emitted whenever the data in an existing item changes. + + If the items are of the same parent, the affected ones are those between + \a topLeft and \a bottomRight inclusive. If the items do not have the same + parent, the behavior is undefined. + + When reimplementing the setData() function, this signal must be emitted + explicitly. + + The optional roles argument can be used to specify which data roles have actually + been modified. An empty set in the roles argument means that all roles should be + considered modified. + + \sa headerDataChanged(), setData(), layoutChanged() +*/ + +/*! + \fn void QAbstractItemModel::rowsInserted(const QModelIndex &parent, int start, int end) + + This signal is emitted after rows have been inserted into the + model. The new items are those between \a start and \a end + inclusive, under the given \a parent item. + + \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 insertRows(), beginInsertRows() +*/ + +/*! + \fn void QAbstractItemModel::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) + + This signal is emitted just before rows are inserted into the model. The + new items will be positioned between \a start and \a end inclusive, under + the given \a parent item. + + \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 insertRows(), beginInsertRows() +*/ + +/*! + \fn void QAbstractItemModel::rowsRemoved(const QModelIndex &parent, int start, int end) + + This signal is emitted after rows have been removed from the model. The + removed items are those between \a start and \a end inclusive, under the + given \a parent item. + + \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 removeRows(), beginRemoveRows() +*/ + +/*! + \fn void QAbstractItemModel::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) + + This signal is emitted just before rows are removed from the model. The + items that will be removed are those between \a start and \a end inclusive, + under the given \a parent item. + + \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 removeRows(), beginRemoveRows() +*/ + +/*! + \fn void QAbstractItemModel::rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) + \since 4.6 + + 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) + \since 4.6 + + 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::columnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) + \since 4.6 + + This signal is emitted after columns 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 column \a destinationColumn. + + \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::columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) + \since 4.6 + + This signal is emitted just before columns 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 column \a destinationColumn. + + \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 + new items are those between \a start and \a end inclusive, under the given + \a parent item. + + \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 insertColumns(), beginInsertColumns() +*/ + +/*! + \fn void QAbstractItemModel::columnsAboutToBeInserted(const QModelIndex &parent, int start, int end) + + This signal is emitted just before columns are inserted into the model. The + new items will be positioned between \a start and \a end inclusive, under + the given \a parent item. + + \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 insertColumns(), beginInsertColumns() +*/ + +/*! + \fn void QAbstractItemModel::columnsRemoved(const QModelIndex &parent, int start, int end) + + This signal is emitted after columns have been removed from the model. + The removed items are those between \a start and \a end inclusive, + under the given \a parent item. + + \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 removeColumns(), beginRemoveColumns() +*/ + +/*! + \fn void QAbstractItemModel::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) + + This signal is emitted just before columns are removed from the model. The + items to be removed are those between \a start and \a end inclusive, under + the given \a parent item. + + \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 removeColumns(), beginRemoveColumns() +*/ + +/*! + Returns true if the model returns a valid QModelIndex for \a row and + \a column with \a parent, otherwise returns false. +*/ +bool QAbstractItemModel::hasIndex(int row, int column, const QModelIndex &parent) const +{ + if (row < 0 || column < 0) + return false; + return row < rowCount(parent) && column < columnCount(parent); +} + + +/*! + Returns true if \a parent has any children; otherwise returns false. + + Use rowCount() on the parent to find out the number of children. + + \sa parent() index() +*/ +bool QAbstractItemModel::hasChildren(const QModelIndex &parent) const +{ + return (rowCount(parent) > 0) && (columnCount(parent) > 0); +} + + +/*! + Returns a map with values for all predefined roles in the model for the + item at the given \a index. + + Reimplement this function if you want to extend the default behavior of + this function to include custom roles in the map. + + \sa Qt::ItemDataRole, data() +*/ +QMap<int, QVariant> QAbstractItemModel::itemData(const QModelIndex &index) const +{ + QMap<int, QVariant> roles; + for (int i = 0; i < Qt::UserRole; ++i) { + QVariant variantData = data(index, i); + if (variantData.isValid()) + roles.insert(i, variantData); + } + return roles; +} + +/*! + Sets the \a role data for the item at \a index to \a value. + + Returns true if successful; otherwise returns false. + + The dataChanged() signal should be emitted if the data was successfully + set. + + The base class implementation returns false. This function and data() must + be reimplemented for editable models. + + \sa Qt::ItemDataRole, data(), itemData() +*/ +bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_UNUSED(index); + Q_UNUSED(value); + Q_UNUSED(role); + return false; +} + +/*! + \fn QVariant QAbstractItemModel::data(const QModelIndex &index, int role) const = 0 + + Returns the data stored under the given \a role for the item referred to + by the \a index. + + \note If you do not have a value to return, return an \bold invalid + QVariant instead of returning 0. + + \sa Qt::ItemDataRole, setData(), headerData() +*/ + +/*! + Sets the role data for the item at \a index to the associated value in + \a roles, for every Qt::ItemDataRole. + + Returns true if successful; otherwise returns false. + + Roles that are not in \a roles will not be modified. + + \sa setData() data() itemData() +*/ +bool QAbstractItemModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) +{ + bool b = true; + for (QMap<int, QVariant>::ConstIterator it = roles.begin(); it != roles.end(); ++it) + b = b && setData(index, it.value(), it.key()); + return b; +} + +/*! + Returns a list of MIME types that can be used to describe a list of model + indexes. + + \sa mimeData() +*/ +QStringList QAbstractItemModel::mimeTypes() const +{ + QStringList types; + types << QLatin1String("application/x-qabstractitemmodeldatalist"); + return types; +} + +/*! + Returns an object that contains serialized items of data corresponding to + the list of \a indexes specified. The formats used to describe the encoded + data is obtained from the mimeTypes() function. + + If the list of indexes is empty, or there are no supported MIME types, 0 is + returned rather than a serialized empty list. + + \sa mimeTypes(), dropMimeData() +*/ +QMimeData *QAbstractItemModel::mimeData(const QModelIndexList &indexes) const +{ + if (indexes.count() <= 0) + return 0; + QStringList types = mimeTypes(); + if (types.isEmpty()) + return 0; + QMimeData *data = new QMimeData(); + QString format = types.at(0); + QByteArray encoded; + QDataStream stream(&encoded, QIODevice::WriteOnly); + encodeData(indexes, stream); + data->setData(format, encoded); + return data; +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action. + + Returns true if the data and action can be handled by the model; otherwise + returns false. + + The specified \a row, \a column and \a parent indicate the location of an + item in the model where the operation ended. It is the responsibility of + the model to complete the action at the correct location. + + For instance, a drop action on an item in a QTreeView can result in new + items either being inserted as children of the item specified by \a row, + \a column, and \a parent, or as siblings of the item. + + When \a row and \a column are -1 it means that the dropped data should be + considered as dropped directly on \a parent. Usually this will mean + appending the data as child items of \a parent. If \a row and column are + greater than or equal zero, it means that the drop occurred just before the + specified \a row and \a column in the specified \a parent. + + \sa supportedDropActions(), {Using drag and drop with item views} +*/ +bool QAbstractItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + // check if the action is supported + if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) + return false; + // check if the format is supported + QStringList types = mimeTypes(); + if (types.isEmpty()) + return false; + QString format = types.at(0); + if (!data->hasFormat(format)) + return false; + if (row > rowCount(parent)) + row = rowCount(parent); + if (row == -1) + row = rowCount(parent); + if (column == -1) + column = 0; + // decode and insert + QByteArray encoded = data->data(format); + QDataStream stream(&encoded, QIODevice::ReadOnly); + return decodeData(row, column, parent, stream); +} + +/*! + \since 4.2 + + Returns the drop actions supported by this model. + + The default implementation returns Qt::CopyAction. Reimplement this + function if you wish to support additional actions. You must also + reimplement the dropMimeData() function to handle the additional + operations. + + \sa dropMimeData(), Qt::DropActions, {Using drag and drop with item + views} +*/ +Qt::DropActions QAbstractItemModel::supportedDropActions() const +{ + return Qt::CopyAction; +} + +/*! + Returns the actions supported by the data in this model. + + The default implementation returns supportedDropActions() unless specific + values have been set with setSupportedDragActions(). + + supportedDragActions() is used by QAbstractItemView::startDrag() as the + default values when a drag occurs. + + \sa Qt::DropActions, {Using drag and drop with item views} +*/ +Qt::DropActions QAbstractItemModel::supportedDragActions() const +{ + Q_D(const QAbstractItemModel); + if (d->supportedDragActions != -1) + return d->supportedDragActions; + return supportedDropActions(); +} + +/*! + \internal + */ +void QAbstractItemModel::doSetSupportedDragActions(Qt::DropActions actions) +{ + Q_D(QAbstractItemModel); + d->supportedDragActions = actions; +} + +/*! + \since 4.2 + \obsolete + + Sets the supported drag \a actions for the items in the model. + + \sa supportedDragActions(), {Using drag and drop with item views} +*/ + +/*! + \note The base class implementation of this function does nothing and + returns false. + + On models that support this, inserts \a count rows into the model before + the given \a row. Items in the new row will be children of the item + represented by the \a parent model index. + + If \a row is 0, the rows are prepended to any existing rows in the parent. + + If \a row is rowCount(), the rows are appended to any existing rows in the + parent. + + If \a parent has no children, a single column with \a count rows is + inserted. + + Returns true if the rows were successfully inserted; otherwise returns + false. + + If you implement your own model, you can reimplement this function if you + want to support insertions. Alternatively, you can provide your own API for + altering the data. In either case, you will need to call + beginInsertRows() and endInsertRows() to notify other components that the + model has changed. + + \sa insertColumns(), removeRows(), beginInsertRows(), endInsertRows() +*/ +bool QAbstractItemModel::insertRows(int, int, const QModelIndex &) +{ + return false; +} + +/*! + On models that support this, inserts \a count new columns into the model + before the given \a column. The items in each new column will be children + of the item represented by the \a parent model index. + + If \a column is 0, the columns are prepended to any existing columns. + + If \a column is columnCount(), the columns are appended to any existing + columns. + + If \a parent has no children, a single row with \a count columns is + inserted. + + Returns true if the columns were successfully inserted; otherwise returns + false. + + The base class implementation does nothing and returns false. + + If you implement your own model, you can reimplement this function if you + want to support insertions. Alternatively, you can provide your own API for + altering the data. + + \sa insertRows(), removeColumns(), beginInsertColumns(), endInsertColumns() +*/ +bool QAbstractItemModel::insertColumns(int, int, const QModelIndex &) +{ + return false; +} + +/*! + On models that support this, removes \a count rows starting with the given + \a row under parent \a parent from the model. + + Returns true if the rows were successfully removed; otherwise returns + false. + + The base class implementation does nothing and returns false. + + If you implement your own model, you can reimplement this function if you + want to support removing. Alternatively, you can provide your own API for + altering the data. + + \sa removeRow(), removeColumns(), insertColumns(), beginRemoveRows(), + endRemoveRows() +*/ +bool QAbstractItemModel::removeRows(int, int, const QModelIndex &) +{ + return false; +} + +/*! + On models that support this, removes \a count columns starting with the + given \a column under parent \a parent from the model. + + Returns true if the columns were successfully removed; otherwise returns + false. + + The base class implementation does nothing and returns false. + + If you implement your own model, you can reimplement this function if you + want to support removing. Alternatively, you can provide your own API for + altering the data. + + \sa removeColumn(), removeRows(), insertColumns(), beginRemoveColumns(), + endRemoveColumns() +*/ +bool QAbstractItemModel::removeColumns(int, int, const QModelIndex &) +{ + return false; +} + +/*! + Fetches any available data for the items with the parent specified by the + \a parent index. + + Reimplement this if you are populating your model incrementally. + + The default implementation does nothing. + + \sa canFetchMore() +*/ +void QAbstractItemModel::fetchMore(const QModelIndex &) +{ + // do nothing +} + +/*! + Returns true if there is more data available for \a parent; otherwise + returns false. + + The default implementation always returns false. + + If canFetchMore() returns true, QAbstractItemView will call fetchMore(). + However, the fetchMore() function is only called when the model is being + populated incrementally. + + \sa fetchMore() +*/ +bool QAbstractItemModel::canFetchMore(const QModelIndex &) const +{ + return false; +} + +/*! + Returns the item flags for the given \a index. + + The base class implementation returns a combination of flags that enables + the item (\c ItemIsEnabled) and allows it to be selected + (\c ItemIsSelectable). + + \sa Qt::ItemFlags +*/ +Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const +{ + Q_D(const QAbstractItemModel); + if (!d->indexValid(index)) + return 0; + + return Qt::ItemIsSelectable|Qt::ItemIsEnabled; +} + +/*! + Sorts the model by \a column in the given \a order. + + The base class implementation does nothing. +*/ +void QAbstractItemModel::sort(int column, Qt::SortOrder order) +{ + Q_UNUSED(column); + Q_UNUSED(order); + // do nothing +} + +/*! + Returns a model index for the buddy of the item represented by \a index. + When the user wants to edit an item, the view will call this function to + check whether another item in the model should be edited instead. Then, the + view will construct a delegate using the model index returned by the buddy + item. + + The default implementation of this function has each item as its own buddy. +*/ +QModelIndex QAbstractItemModel::buddy(const QModelIndex &index) const +{ + return index; +} + +/*! + Returns a list of indexes for the items in the column of the \a start index + where data stored under the given \a role matches the specified \a value. + The way the search is performed is defined by the \a flags given. The list + that is returned may be empty. + + The search begins from the \a start index, and continues until the number + of matching data items equals \a hits, the search reaches the last row, or + the search reaches \a start again - depending on whether \c MatchWrap is + specified in \a flags. If you want to search for all matching items, use + \a hits = -1. + + By default, this function will perform a wrapping, string-based comparison + on all items, searching for items that begin with the search term specified + by \a value. + + \note The default implementation of this function only searches columns. + Reimplement this function to include a different search behavior. +*/ +QModelIndexList QAbstractItemModel::match(const QModelIndex &start, int role, + const QVariant &value, int hits, + Qt::MatchFlags flags) const +{ + QModelIndexList result; + uint matchType = flags & 0x0F; + Qt::CaseSensitivity cs = flags & Qt::MatchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; + bool recurse = flags & Qt::MatchRecursive; + bool wrap = flags & Qt::MatchWrap; + bool allHits = (hits == -1); + QString text; // only convert to a string if it is needed + QModelIndex p = parent(start); + int from = start.row(); + int to = rowCount(p); + + // iterates twice if wrapping + for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) { + for (int r = from; (r < to) && (allHits || result.count() < hits); ++r) { + QModelIndex idx = index(r, start.column(), p); + if (!idx.isValid()) + continue; + QVariant v = data(idx, role); + // QVariant based matching + if (matchType == Qt::MatchExactly) { + if (value == v) + result.append(idx); + } else { // QString based matching + if (text.isEmpty()) // lazy conversion + text = value.toString(); + QString t = v.toString(); + switch (matchType) { + case Qt::MatchRegExp: + if (QRegExp(text, cs).exactMatch(t)) + result.append(idx); + break; + case Qt::MatchWildcard: + if (QRegExp(text, cs, QRegExp::Wildcard).exactMatch(t)) + result.append(idx); + break; + case Qt::MatchStartsWith: + if (t.startsWith(text, cs)) + result.append(idx); + break; + case Qt::MatchEndsWith: + if (t.endsWith(text, cs)) + result.append(idx); + break; + case Qt::MatchFixedString: + if (t.compare(text, cs) == 0) + result.append(idx); + break; + case Qt::MatchContains: + default: + if (t.contains(text, cs)) + result.append(idx); + } + } + if (recurse && hasChildren(idx)) { // search the hierarchy + result += match(index(0, idx.column(), idx), role, + (text.isEmpty() ? value : text), + (allHits ? -1 : hits - result.count()), flags); + } + } + // prepare for the next iteration + from = 0; + to = start.row(); + } + return result; +} + +/*! + Returns the row and column span of the item represented by \a index. + + \note Currently, span is not used. +*/ + +QSize QAbstractItemModel::span(const QModelIndex &) const +{ + return QSize(1, 1); +} + +/*! + \since 4.6 + \obsolete + + Sets the model's role names to \a roleNames. + + This function allows mapping of role identifiers to role property names in + Declarative UI. This function must be called before the model is used. + Modifying the role names after the model has been set may result in + undefined behaviour. + + \sa roleNames() +*/ + +/*! + \internal + */ +void QAbstractItemModel::doSetRoleNames(const QHash<int,QByteArray> &roleNames) +{ + Q_D(QAbstractItemModel); + d->roleNames = roleNames; +} + +/*! + \since 4.6 + + Returns the model's role names. + + \sa setRoleNames() +*/ +QHash<int,QByteArray> QAbstractItemModel::roleNames() const +{ + Q_D(const QAbstractItemModel); + return d->roleNames; +} + +/*! + Lets the model know that it should submit cached information to permanent + storage. This function is typically used for row editing. + + Returns true if there is no error; otherwise returns false. + + \sa revert() +*/ + +bool QAbstractItemModel::submit() +{ + return true; +} + +/*! + Lets the model know that it should discard cached information. This + function is typically used for row editing. + + \sa submit() +*/ + +void QAbstractItemModel::revert() +{ + // do nothing +} + +/*! + Returns the data for the given \a role and \a section in the header with + the specified \a orientation. + + For horizontal headers, the section number corresponds to the column + number. Similarly, for vertical headers, the section number corresponds to + the row number. + + \sa Qt::ItemDataRole, setHeaderData(), QHeaderView +*/ + +QVariant QAbstractItemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(orientation); + if (role == Qt::DisplayRole) + return section + 1; + return QVariant(); +} + +/*! + Sets the data for the given \a role and \a section in the header with the + specified \a orientation to the \a value supplied. + + Returns true if the header's data was updated; otherwise returns false. + + When reimplementing this function, the headerDataChanged() signal must be + emitted explicitly. + + \sa Qt::ItemDataRole, headerData() +*/ + +bool QAbstractItemModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_UNUSED(section); + Q_UNUSED(orientation); + Q_UNUSED(value); + Q_UNUSED(role); + return false; +} + +/*! + \fn QModelIndex QAbstractItemModel::createIndex(int row, int column, void *ptr) const + + Creates a model index for the given \a row and \a column with the internal + pointer \a ptr. + + When using a QSortFilterProxyModel, its indexes have their own internal + pointer. It is not advisable to access this internal pointer outside of the + model. Use the data() function instead. + + This function provides a consistent interface that model subclasses must + use to create model indexes. +*/ + +/*! + \fn QModelIndex QAbstractItemModel::createIndex(int row, int column, int id) const + \obsolete + + Use QModelIndex + QAbstractItemModel::createIndex(int row, int column, quint32 id) instead. +*/ + +/*! + \fn QModelIndex QAbstractItemModel::createIndex(int row, int column, quint32 id) const + + Creates a model index for the given \a row and \a column with the internal + identifier, \a id. + + This function provides a consistent interface that model subclasses must + use to create model indexes. + + \sa QModelIndex::internalId() +*/ + +/*! + \internal +*/ +void QAbstractItemModel::encodeData(const QModelIndexList &indexes, QDataStream &stream) const +{ + QModelIndexList::ConstIterator it = indexes.begin(); + for (; it != indexes.end(); ++it) + stream << (*it).row() << (*it).column() << itemData(*it); +} + +/*! + \internal + */ +bool QAbstractItemModel::decodeData(int row, int column, const QModelIndex &parent, + QDataStream &stream) +{ + int top = INT_MAX; + int left = INT_MAX; + int bottom = 0; + int right = 0; + QVector<int> rows, columns; + QVector<QMap<int, QVariant> > data; + + while (!stream.atEnd()) { + int r, c; + QMap<int, QVariant> v; + stream >> r >> c >> v; + rows.append(r); + columns.append(c); + data.append(v); + top = qMin(r, top); + left = qMin(c, left); + bottom = qMax(r, bottom); + right = qMax(c, right); + } + + // insert the dragged items into the table, use a bit array to avoid overwriting items, + // since items from different tables can have the same row and column + int dragRowCount = 0; + int dragColumnCount = right - left + 1; + + // Compute the number of continuous rows upon insertion and modify the rows to match + QVector<int> rowsToInsert(bottom + 1); + for (int i = 0; i < rows.count(); ++i) + rowsToInsert[rows.at(i)] = 1; + for (int i = 0; i < rowsToInsert.count(); ++i) { + if (rowsToInsert[i] == 1){ + rowsToInsert[i] = dragRowCount; + ++dragRowCount; + } + } + for (int i = 0; i < rows.count(); ++i) + rows[i] = top + rowsToInsert[rows[i]]; + + QBitArray isWrittenTo(dragRowCount * dragColumnCount); + + // make space in the table for the dropped data + int colCount = columnCount(parent); + if (colCount == 0) { + insertColumns(colCount, dragColumnCount - colCount, parent); + colCount = columnCount(parent); + } + insertRows(row, dragRowCount, parent); + + row = qMax(0, row); + column = qMax(0, column); + + QVector<QPersistentModelIndex> newIndexes(data.size()); + // set the data in the table + for (int j = 0; j < data.size(); ++j) { + int relativeRow = rows.at(j) - top; + int relativeColumn = columns.at(j) - left; + int destinationRow = relativeRow + row; + int destinationColumn = relativeColumn + column; + int flat = (relativeRow * dragColumnCount) + relativeColumn; + // if the item was already written to, or we just can't fit it in the table, create a new row + if (destinationColumn >= colCount || isWrittenTo.testBit(flat)) { + destinationColumn = qBound(column, destinationColumn, colCount - 1); + destinationRow = row + dragRowCount; + insertRows(row + dragRowCount, 1, parent); + flat = (dragRowCount * dragColumnCount) + relativeColumn; + isWrittenTo.resize(++dragRowCount * dragColumnCount); + } + if (!isWrittenTo.testBit(flat)) { + newIndexes[j] = index(destinationRow, destinationColumn, parent); + isWrittenTo.setBit(flat); + } + } + + for(int k = 0; k < newIndexes.size(); k++) { + if (newIndexes.at(k).isValid()) + setItemData(newIndexes.at(k), data.at(k)); + } + + return true; +} + +/*! + Begins a row insertion operation. + + When reimplementing insertRows() in a subclass, you must call this function + \e before inserting data into the model's underlying data store. + + The \a parent index corresponds to the parent into which the new rows are + inserted; \a first and \a last are the row numbers that the new rows will + have after they have been inserted. + + \table 80% + \row + \o \inlineimage modelview-begin-insert-rows.png Inserting rows + \o Specify the first and last row numbers for the span of rows you + want to insert into an item in a model. + + For example, as shown in the diagram, we insert three rows before + row 2, so \a first is 2 and \a last is 4: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 0 + + This inserts the three new rows as rows 2, 3, and 4. + \row + \o \inlineimage modelview-begin-append-rows.png Appending rows + \o To append rows, insert them after the last row. + + For example, as shown in the diagram, we append two rows to a + collection of 4 existing rows (ending in row 3), so \a first is 4 + and \a last is 5: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 1 + + This appends the two new rows as rows 4 and 5. + \endtable + + \note This function emits the rowsAboutToBeInserted() signal which + connected views (or proxies) must handle before the data is inserted. + Otherwise, the views may end up in an invalid state. + \sa endInsertRows() +*/ +void QAbstractItemModel::beginInsertRows(const QModelIndex &parent, int first, int last) +{ + Q_ASSERT(first >= 0); + Q_ASSERT(last >= first); + Q_D(QAbstractItemModel); + d->changes.push(QAbstractItemModelPrivate::Change(parent, first, last)); + emit rowsAboutToBeInserted(parent, first, last); + d->rowsAboutToBeInserted(parent, first, last); +} + +/*! + Ends a row insertion operation. + + When reimplementing insertRows() in a subclass, you must call this function + \e after inserting data into the model's underlying data store. + + \sa beginInsertRows() +*/ +void QAbstractItemModel::endInsertRows() +{ + Q_D(QAbstractItemModel); + QAbstractItemModelPrivate::Change change = d->changes.pop(); + d->rowsInserted(change.parent, change.first, change.last); + emit rowsInserted(change.parent, change.first, change.last); +} + +/*! + Begins a row removal operation. + + When reimplementing removeRows() in a subclass, you must call this + function \e before removing data from the model's underlying data store. + + The \a parent index corresponds to the parent from which the new rows are + removed; \a first and \a last are the row numbers of the rows to be + removed. + + \table 80% + \row + \o \inlineimage modelview-begin-remove-rows.png Removing rows + \o Specify the first and last row numbers for the span of rows you + want to remove from an item in a model. + + For example, as shown in the diagram, we remove the two rows from + row 2 to row 3, so \a first is 2 and \a last is 3: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 2 + \endtable + + \note This function emits the rowsAboutToBeRemoved() signal which connected + views (or proxies) must handle before the data is removed. Otherwise, the + views may end up in an invalid state. + + \sa endRemoveRows() +*/ +void QAbstractItemModel::beginRemoveRows(const QModelIndex &parent, int first, int last) +{ + Q_ASSERT(first >= 0); + Q_ASSERT(last >= first); + Q_D(QAbstractItemModel); + d->changes.push(QAbstractItemModelPrivate::Change(parent, first, last)); + emit rowsAboutToBeRemoved(parent, first, last); + d->rowsAboutToBeRemoved(parent, first, last); +} + +/*! + Ends a row removal operation. + + When reimplementing removeRows() in a subclass, you must call this function + \e after removing data from the model's underlying data store. + + \sa beginRemoveRows() +*/ +void QAbstractItemModel::endRemoveRows() +{ + Q_D(QAbstractItemModel); + QAbstractItemModelPrivate::Change change = d->changes.pop(); + d->rowsRemoved(change.parent, change.first, change.last); + emit rowsRemoved(change.parent, change.first, change.last); +} + +/*! + 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) +{ + // Don't move the range within itself. + if (destinationParent == srcParent) + return !(destinationStart >= start && destinationStart <= end + 1); + + 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; +} + +/*! + \since 4.6 + + 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. + + The \a sourceParent index corresponds to the parent from which the + rows are moved; \a sourceFirst and \a sourceLast are the first and last + row numbers of the rows to be moved. The \a destinationParent index + corresponds to the parent into which those rows are moved. The \a + destinationChild 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 destinationChild in \a destinationParent, followed by all other + rows up to \a sourceLast. + + However, when moving rows down in the same parent (\a sourceParent + and \a destinationParent are equal), the rows will be placed before the + \a destinationChild index. That is, if you wish to move rows 0 and 1 so + they will become rows 1 and 2, \a destinationChild should be 3. In this + case, the new index for the source row \c i (which is between + \a sourceFirst and \a sourceLast) is equal to + \c {(destinationChild-sourceLast-1+i)}. + + Note that if \a sourceParent and \a destinationParent are the same, + you must ensure that the \a destinationChild is not within the range + of \a sourceFirst and \a sourceLast + 1. You must also ensure that you + do not attempt to move a row to one of its own children or ancestors. + This method returns false if either condition is true, in which case you + should abort your move operation. + + \table 80% + \row + \o \inlineimage modelview-move-rows-1.png Moving rows to another parent + \o Specify the first and last row numbers for the span of rows in + the source parent you want to move in the model. Also specify + the row in the destination parent to move the span to. + + For example, as shown in the diagram, we move three rows from + row 2 to 4 in the source, so \a sourceFirst is 2 and \a sourceLast is 4. + We move those items to above row 2 in the destination, so \a destinationChild is 2. + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 6 + + This moves the three rows rows 2, 3, and 4 in the source to become 2, 3 and 4 in + the destination. Other affected siblings are displaced accordingly. + \row + \o \inlineimage modelview-move-rows-2.png Moving rows to append to another parent + \o To append rows to another parent, move them to after the last row. + + For example, as shown in the diagram, we move three rows to a + collection of 6 existing rows (ending in row 5), so \a destinationChild is 6: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 7 + + This moves the target rows to the end of the target parent as 6, 7 and 8. + \row + \o \inlineimage modelview-move-rows-3.png Moving rows in the same parent up + \o To move rows within the same parent, specify the row to move them to. + + For example, as shown in the diagram, we move one item from row 2 to row 0, + so \a sourceFirst and \a sourceLast are 2 and \a destinationChild is 0. + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 8 + + Note that other rows may be displaced accordingly. Note also that when moving + items within the same parent you should not attempt invalid or no-op moves. In + the above example, item 2 is at row 2 before the move, so it can not be moved + to row 2 (where it is already) or row 3 (no-op as row 3 means above row 3, where + it is already) + + \row + \o \inlineimage modelview-move-rows-4.png Moving rows in the same parent down + \o To move rows within the same parent, specify the row to move them to. + + For example, as shown in the diagram, we move one item from row 2 to row 4, + so \a sourceFirst and \a sourceLast are 2 and \a destinationChild is 4. + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 9 + + Note that other rows may be displaced accordingly. + \endtable + + \sa endMoveRows() +*/ +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; + } + + QAbstractItemModelPrivate::Change sourceChange(sourceParent, sourceFirst, sourceLast); + sourceChange.needsAdjust = sourceParent.isValid() && sourceParent.row() >= destinationChild && sourceParent.parent() == destinationParent; + d->changes.push(sourceChange); + int destinationLast = destinationChild + (sourceLast - sourceFirst); + QAbstractItemModelPrivate::Change destinationChange(destinationParent, destinationChild, destinationLast); + destinationChange.needsAdjust = destinationParent.isValid() && destinationParent.row() >= sourceLast && destinationParent.parent() == sourceParent; + d->changes.push(destinationChange); + + emit rowsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild); + d->itemsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild, Qt::Vertical); + 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. + + \sa beginMoveRows() + + \since 4.6 +*/ +void QAbstractItemModel::endMoveRows() +{ + Q_D(QAbstractItemModel); + + QAbstractItemModelPrivate::Change insertChange = d->changes.pop(); + QAbstractItemModelPrivate::Change removeChange = d->changes.pop(); + + QModelIndex adjustedSource = removeChange.parent; + QModelIndex adjustedDestination = insertChange.parent; + + const int numMoved = removeChange.last - removeChange.first + 1; + if (insertChange.needsAdjust) + adjustedDestination = createIndex(adjustedDestination.row() - numMoved, adjustedDestination.column(), adjustedDestination.internalPointer()); + + if (removeChange.needsAdjust) + adjustedSource = createIndex(adjustedSource.row() + numMoved, adjustedSource.column(), adjustedSource.internalPointer()); + + d->itemsMoved(adjustedSource, removeChange.first, removeChange.last, adjustedDestination, insertChange.first, Qt::Vertical); + + emit rowsMoved(adjustedSource, removeChange.first, removeChange.last, adjustedDestination, insertChange.first); +} + +/*! + Begins a column insertion operation. + + When reimplementing insertColumns() in a subclass, you must call this + function \e before inserting data into the model's underlying data store. + + The \a parent index corresponds to the parent into which the new columns + are inserted; \a first and \a last are the column numbers of the new + columns will have after they have been inserted. + + \table 80% + \row + \o \inlineimage modelview-begin-insert-columns.png Inserting columns + \o Specify the first and last column numbers for the span of columns + you want to insert into an item in a model. + + For example, as shown in the diagram, we insert three columns + before column 4, so \a first is 4 and \a last is 6: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 3 + + This inserts the three new columns as columns 4, 5, and 6. + \row + \o \inlineimage modelview-begin-append-columns.png Appending columns + \o To append columns, insert them after the last column. + + For example, as shown in the diagram, we append three columns to a + collection of six existing columns (ending in column 5), so + \a first is 6 and \a last is 8: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 4 + + This appends the two new columns as columns 6, 7, and 8. + \endtable + + \note This function emits the columnsAboutToBeInserted() signal which + connected views (or proxies) must handle before the data is inserted. + Otherwise, the views may end up in an invalid state. + + \sa endInsertColumns() +*/ +void QAbstractItemModel::beginInsertColumns(const QModelIndex &parent, int first, int last) +{ + Q_ASSERT(first >= 0); + Q_ASSERT(last >= first); + Q_D(QAbstractItemModel); + d->changes.push(QAbstractItemModelPrivate::Change(parent, first, last)); + emit columnsAboutToBeInserted(parent, first, last); + d->columnsAboutToBeInserted(parent, first, last); +} + +/*! + Ends a column insertion operation. + + When reimplementing insertColumns() in a subclass, you must call this + function \e after inserting data into the model's underlying data + store. + + \sa beginInsertColumns() +*/ +void QAbstractItemModel::endInsertColumns() +{ + Q_D(QAbstractItemModel); + QAbstractItemModelPrivate::Change change = d->changes.pop(); + d->columnsInserted(change.parent, change.first, change.last); + emit columnsInserted(change.parent, change.first, change.last); +} + +/*! + Begins a column removal operation. + + When reimplementing removeColumns() in a subclass, you must call this + function \e before removing data from the model's underlying data store. + + The \a parent index corresponds to the parent from which the new columns + are removed; \a first and \a last are the column numbers of the first and + last columns to be removed. + + \table 80% + \row + \o \inlineimage modelview-begin-remove-columns.png Removing columns + \o Specify the first and last column numbers for the span of columns + you want to remove from an item in a model. + + For example, as shown in the diagram, we remove the three columns + from column 4 to column 6, so \a first is 4 and \a last is 6: + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 5 + \endtable + + \note This function emits the columnsAboutToBeRemoved() signal which + connected views (or proxies) must handle before the data is removed. + Otherwise, the views may end up in an invalid state. + + \sa endRemoveColumns() +*/ +void QAbstractItemModel::beginRemoveColumns(const QModelIndex &parent, int first, int last) +{ + Q_ASSERT(first >= 0); + Q_ASSERT(last >= first); + Q_D(QAbstractItemModel); + d->changes.push(QAbstractItemModelPrivate::Change(parent, first, last)); + emit columnsAboutToBeRemoved(parent, first, last); + d->columnsAboutToBeRemoved(parent, first, last); +} + +/*! + Ends a column removal operation. + + When reimplementing removeColumns() in a subclass, you must call this + function \e after removing data from the model's underlying data store. + + \sa beginRemoveColumns() +*/ +void QAbstractItemModel::endRemoveColumns() +{ + Q_D(QAbstractItemModel); + QAbstractItemModelPrivate::Change change = d->changes.pop(); + d->columnsRemoved(change.parent, change.first, change.last); + emit columnsRemoved(change.parent, change.first, change.last); +} + +/*! + 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 beginMoveRows and endMoveRows + is an alternative to emitting layoutAboutToBeChanged and + layoutChanged directly along with changePersistentIndexes. + + The \a sourceParent index corresponds to the parent from which the + columns are moved; \a sourceFirst and \a sourceLast are the first and last + column numbers of the columns to be moved. The \a destinationParent index + corresponds to the parent into which those columns are moved. The \a + destinationChild 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 destinationChild in \a destinationParent, followed by all other + columns up to \a sourceLast. + + However, when moving columns down in the same parent (\a sourceParent + and \a destinationParent are equal), the columnss will be placed before the + \a destinationChild index. That is, if you wish to move columns 0 and 1 so + they will become columns 1 and 2, \a destinationChild should be 3. In this + case, the new index for the source column \c i (which is between + \a sourceFirst and \a sourceLast) is equal to + \c {(destinationChild-sourceLast-1+i)}. + + Note that if \a sourceParent and \a destinationParent are the same, + you must ensure that the \a destinationChild is not within the range + of \a sourceFirst and \a sourceLast + 1. You must also ensure that you + do not attempt to move a column to one of its own children 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; + } + + QAbstractItemModelPrivate::Change sourceChange(sourceParent, sourceFirst, sourceLast); + sourceChange.needsAdjust = sourceParent.isValid() && sourceParent.row() >= destinationChild && sourceParent.parent() == destinationParent; + d->changes.push(sourceChange); + int destinationLast = destinationChild + (sourceLast - sourceFirst); + QAbstractItemModelPrivate::Change destinationChange(destinationParent, destinationChild, destinationLast); + destinationChange.needsAdjust = destinationParent.isValid() && destinationParent.row() >= sourceLast && destinationParent.parent() == sourceParent; + d->changes.push(destinationChange); + + d->itemsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild, Qt::Horizontal); + + emit columnsAboutToBeMoved(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild); + 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. + + \sa beginMoveColumns() + + \since 4.6 +*/ +void QAbstractItemModel::endMoveColumns() +{ + Q_D(QAbstractItemModel); + + QAbstractItemModelPrivate::Change insertChange = d->changes.pop(); + QAbstractItemModelPrivate::Change removeChange = d->changes.pop(); + + QModelIndex adjustedSource = removeChange.parent; + QModelIndex adjustedDestination = insertChange.parent; + + const int numMoved = removeChange.last - removeChange.first + 1; + if (insertChange.needsAdjust) + adjustedDestination = createIndex(adjustedDestination.row(), adjustedDestination.column() - numMoved, adjustedDestination.internalPointer()); + + if (removeChange.needsAdjust) + adjustedSource = createIndex(adjustedSource.row(), adjustedSource.column() + numMoved, adjustedSource.internalPointer()); + + d->itemsMoved(adjustedSource, removeChange.first, removeChange.last, adjustedDestination, insertChange.first, Qt::Horizontal); + + emit columnsMoved(adjustedSource, removeChange.first, removeChange.last, adjustedDestination, insertChange.first); +} + +/*! + Resets the model to its original state in any attached views. + + \note Use beginResetModel() and endResetModel() instead whenever possible. + Use this method only if there is no way to call beginResetModel() before invalidating the model. + Otherwise it could lead to unexpected behaviour, especially when used with proxy models. +*/ +void QAbstractItemModel::reset() +{ + Q_D(QAbstractItemModel); + emit modelAboutToBeReset(); + d->invalidatePersistentIndexes(); + emit modelReset(); +} + +/*! + Begins a model reset operation. + + A reset operation resets the model to its current state in any attached views. + + \note Any views attached to this model will be reset as well. + + When a model is reset it means that any previous data reported from the + model is now invalid and has to be queried for again. This also means that + the current item and any selected items will become invalid. + + When a model radically changes its data it can sometimes be easier to just + call this function rather than emit dataChanged() to inform other + components when the underlying data source, or its structure, has changed. + + You must call this function before resetting any internal data structures in your model + or proxy model. + + \sa modelAboutToBeReset(), modelReset(), endResetModel() + \since 4.6 +*/ +void QAbstractItemModel::beginResetModel() +{ + emit modelAboutToBeReset(); +} + +/*! + Completes a model reset operation. + + You must call this function after resetting any internal data structure in your model + or proxy model. + + \sa beginResetModel() + \since 4.6 +*/ +void QAbstractItemModel::endResetModel() +{ + Q_D(QAbstractItemModel); + d->invalidatePersistentIndexes(); + emit modelReset(); +} + +/*! + Changes the QPersistentModelIndex that is equal to the given \a from model + index to the given \a to model index. + + If no persistent model index equal to the given \a from model index was + found, nothing is changed. + + \sa persistentIndexList(), changePersistentIndexList() +*/ +void QAbstractItemModel::changePersistentIndex(const QModelIndex &from, const QModelIndex &to) +{ + Q_D(QAbstractItemModel); + if (d->persistent.indexes.isEmpty()) + return; + // find the data and reinsert it sorted + const QHash<QModelIndex, QPersistentModelIndexData *>::iterator it = d->persistent.indexes.find(from); + if (it != d->persistent.indexes.end()) { + QPersistentModelIndexData *data = *it; + d->persistent.indexes.erase(it); + data->index = to; + if (to.isValid()) + d->persistent.insertMultiAtEnd(to, data); + else + data->model = 0; + } +} + +/*! + \since 4.1 + + Changes the QPersistentModelIndexes that is equal to the indexes in the + given \a from model index list to the given \a to model index list. + + If no persistent model indexes equal to the indexes in the given \a from + model index list was found, nothing is changed. + + \sa persistentIndexList(), changePersistentIndex() +*/ +void QAbstractItemModel::changePersistentIndexList(const QModelIndexList &from, + const QModelIndexList &to) +{ + Q_D(QAbstractItemModel); + if (d->persistent.indexes.isEmpty()) + return; + QVector<QPersistentModelIndexData *> toBeReinserted; + toBeReinserted.reserve(to.count()); + for (int i = 0; i < from.count(); ++i) { + if (from.at(i) == to.at(i)) + continue; + const QHash<QModelIndex, QPersistentModelIndexData *>::iterator it = d->persistent.indexes.find(from.at(i)); + if (it != d->persistent.indexes.end()) { + QPersistentModelIndexData *data = *it; + d->persistent.indexes.erase(it); + data->index = to.at(i); + if (data->index.isValid()) + toBeReinserted << data; + else + data->model = 0; + } + } + + for (QVector<QPersistentModelIndexData *>::const_iterator it = toBeReinserted.constBegin(); + it != toBeReinserted.constEnd() ; ++it) { + QPersistentModelIndexData *data = *it; + d->persistent.insertMultiAtEnd(data->index, data); + } +} + +/*! + \since 4.2 + + Returns the list of indexes stored as persistent indexes in the model. +*/ +QModelIndexList QAbstractItemModel::persistentIndexList() const +{ + Q_D(const QAbstractItemModel); + QModelIndexList result; + for (QHash<QModelIndex, QPersistentModelIndexData *>::const_iterator it = d->persistent.indexes.constBegin(); + it != d->persistent.indexes.constEnd(); ++it) { + QPersistentModelIndexData *data = *it; + result.append(data->index); + } + return result; +} + + +/*! + \class QAbstractTableModel + \brief The QAbstractTableModel class provides an abstract model that can be + subclassed to create table models. + + \ingroup model-view + + QAbstractTableModel provides a standard interface for models that represent + their data as a two-dimensional array of items. It is not used directly, + but must be subclassed. + + Since the model provides a more specialized interface than + QAbstractItemModel, it is not suitable for use with tree views, although it + can be used to provide data to a QListView. If you need to represent a + simple list of items, and only need a model to contain a single column of + data, subclassing the QAbstractListModel may be more appropriate. + + The rowCount() and columnCount() functions return the dimensions of the + table. To retrieve a model index corresponding to an item in the model, use + index() and provide only the row and column numbers. + + \section1 Subclassing + + When subclassing QAbstractTableModel, you must implement rowCount(), + columnCount(), and data(). Default implementations of the index() and + parent() functions are provided by QAbstractTableModel. + Well behaved models will also implement headerData(). + + Editable models need to implement setData(), and implement flags() to + return a value containing + \l{Qt::ItemFlags}{Qt::ItemIsEditable}. + + Models that provide interfaces to resizable data structures can + provide implementations of insertRows(), removeRows(), insertColumns(), + and removeColumns(). When implementing these functions, it is + important to call the appropriate functions so that all connected views + are aware of any changes: + + \list + \o An insertRows() implementation must call beginInsertRows() + \e before inserting new rows into the data structure, and it must + call endInsertRows() \e{immediately afterwards}. + \o An insertColumns() implementation must call beginInsertColumns() + \e before inserting new columns into the data structure, and it must + call endInsertColumns() \e{immediately afterwards}. + \o A removeRows() implementation must call beginRemoveRows() + \e before the rows are removed from the data structure, and it must + call endRemoveRows() \e{immediately afterwards}. + \o A removeColumns() implementation must call beginRemoveColumns() + \e before the columns are removed from the data structure, and it must + call endRemoveColumns() \e{immediately afterwards}. + \endlist + + \note Some general guidelines for subclassing models are available in the + \l{Model Subclassing Reference}. + + \note + + \sa {Model Classes}, QAbstractItemModel, QAbstractListModel, + {Pixelator Example} +*/ + +/*! + Constructs an abstract table model for the given \a parent. +*/ + +QAbstractTableModel::QAbstractTableModel(QObject *parent) + : QAbstractItemModel(parent) +{ + +} + +/*! + \internal + + Constructs an abstract table model with \a dd and the given \a parent. +*/ + +QAbstractTableModel::QAbstractTableModel(QAbstractItemModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + +} + +/*! + Destroys the abstract table model. +*/ + +QAbstractTableModel::~QAbstractTableModel() +{ + +} + +/*! + \fn QModelIndex QAbstractTableModel::index(int row, int column, const QModelIndex &parent = QModelIndex()) const + + Returns the index of the data in \a row and \a column with \a parent. + + \sa parent() +*/ + +QModelIndex QAbstractTableModel::index(int row, int column, const QModelIndex &parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex(); +} + +/*! + \fn QModelIndex QAbstractTableModel::parent(const QModelIndex &index) const + + Returns the parent of the model item with the given \a index. + + \sa index() hasChildren() +*/ + +QModelIndex QAbstractTableModel::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + +bool QAbstractTableModel::hasChildren(const QModelIndex &parent) const +{ + if (parent.model() == this || !parent.isValid()) + return rowCount(parent) > 0 && columnCount(parent) > 0; + return false; +} + +/*! + \class QAbstractListModel + \brief The QAbstractListModel class provides an abstract model that can be + subclassed to create one-dimensional list models. + + \ingroup model-view + + QAbstractListModel provides a standard interface for models that represent + their data as a simple non-hierarchical sequence of items. It is not used + directly, but must be subclassed. + + Since the model provides a more specialized interface than + QAbstractItemModel, it is not suitable for use with tree views; you will + need to subclass QAbstractItemModel if you want to provide a model for + that purpose. If you need to use a number of list models to manage data, + it may be more appropriate to subclass QAbstractTableModel class instead. + + Simple models can be created by subclassing this class and implementing + the minimum number of required functions. For example, we could implement + a simple read-only QStringList-based model that provides a list of strings + to a QListView widget. In such a case, we only need to implement the + rowCount() function to return the number of items in the list, and the + data() function to retrieve items from the list. + + Since the model represents a one-dimensional structure, the rowCount() + function returns the total number of items in the model. The columnCount() + function is implemented for interoperability with all kinds of views, but + by default informs views that the model contains only one column. + + \section1 Subclassing + + When subclassing QAbstractListModel, you must provide implementations + of the rowCount() and data() functions. Well behaved models also provide + a headerData() implementation. + + For editable list models, you must also provide an implementation of + setData(), implement the flags() function so that it returns a value + containing \l{Qt::ItemFlags}{Qt::ItemIsEditable}. + + Note that QAbstractListModel provides a default implementation of + columnCount() that informs views that there is only a single column + of items in this model. + + Models that provide interfaces to resizable list-like data structures + can provide implementations of insertRows() and removeRows(). When + implementing these functions, it is important to call the appropriate + functions so that all connected views are aware of any changes: + + \list + \o An insertRows() implementation must call beginInsertRows() + \e before inserting new rows into the data structure, and it must + call endInsertRows() \e{immediately afterwards}. + \o A removeRows() implementation must call beginRemoveRows() + \e before the rows are removed from the data structure, and it must + call endRemoveRows() \e{immediately afterwards}. + \endlist + + \note Some general guidelines for subclassing models are available in the + \l{Model Subclassing Reference}. + + \sa {Model Classes}, {Model Subclassing Reference}, QAbstractItemView, + QAbstractTableModel, {Item Views Puzzle Example} +*/ + +/*! + Constructs an abstract list model with the given \a parent. +*/ + +QAbstractListModel::QAbstractListModel(QObject *parent) + : QAbstractItemModel(parent) +{ + +} + +/*! + \internal + + Constructs an abstract list model with \a dd and the given \a parent. +*/ + +QAbstractListModel::QAbstractListModel(QAbstractItemModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + +} + +/*! + Destroys the abstract list model. +*/ + +QAbstractListModel::~QAbstractListModel() +{ + +} + +/*! + \fn QModelIndex QAbstractListModel::index(int row, int column, const QModelIndex &parent = QModelIndex()) const + + Returns the index of the data in \a row and \a column with \a parent. + + \sa parent() +*/ + +QModelIndex QAbstractListModel::index(int row, int column, const QModelIndex &parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex(); +} + +/*! + Returns the parent of the model item with the given \a index. + + \sa index() hasChildren() +*/ + +QModelIndex QAbstractListModel::parent(const QModelIndex & /* index */) const +{ + return QModelIndex(); +} + +/*! + \internal + + Returns the number of columns in the list with the given \a parent. + + \sa rowCount() +*/ + +int QAbstractListModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 1; +} + +bool QAbstractListModel::hasChildren(const QModelIndex &parent) const +{ + return parent.isValid() ? false : (rowCount() > 0); +} + +/*! + \typedef QModelIndexList + \relates QModelIndex + + Synonym for QList<QModelIndex>. +*/ + +/*! + \reimp +*/ +bool QAbstractTableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) + return false; + + QStringList types = mimeTypes(); + if (types.isEmpty()) + return false; + QString format = types.at(0); + if (!data->hasFormat(format)) + return false; + + QByteArray encoded = data->data(format); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + // if the drop is on an item, replace the data in the items + if (parent.isValid() && row == -1 && column == -1) { + int top = INT_MAX; + int left = INT_MAX; + QVector<int> rows, columns; + QVector<QMap<int, QVariant> > data; + + while (!stream.atEnd()) { + int r, c; + QMap<int, QVariant> v; + stream >> r >> c >> v; + rows.append(r); + columns.append(c); + data.append(v); + top = qMin(r, top); + left = qMin(c, left); + } + + for (int i = 0; i < data.size(); ++i) { + int r = (rows.at(i) - top) + parent.row(); + int c = (columns.at(i) - left) + parent.column(); + if (hasIndex(r, c)) + setItemData(index(r, c), data.at(i)); + } + + return true; + } + + // otherwise insert new rows for the data + return decodeData(row, column, parent, stream); +} + +/*! + \reimp +*/ +bool QAbstractListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) + return false; + + QStringList types = mimeTypes(); + if (types.isEmpty()) + return false; + QString format = types.at(0); + if (!data->hasFormat(format)) + return false; + + QByteArray encoded = data->data(format); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + // if the drop is on an item, replace the data in the items + if (parent.isValid() && row == -1 && column == -1) { + int top = INT_MAX; + int left = INT_MAX; + QVector<int> rows, columns; + QVector<QMap<int, QVariant> > data; + + while (!stream.atEnd()) { + int r, c; + QMap<int, QVariant> v; + stream >> r >> c >> v; + rows.append(r); + columns.append(c); + data.append(v); + top = qMin(r, top); + left = qMin(c, left); + } + + for (int i = 0; i < data.size(); ++i) { + int r = (rows.at(i) - top) + parent.row(); + if (columns.at(i) == left && hasIndex(r, 0)) + setItemData(index(r), data.at(i)); + } + + return true; + } + + if (row == -1) + row = rowCount(parent); + + // otherwise insert new rows for the data + return decodeData(row, column, parent, stream); +} + +/*! + \fn QAbstractItemModel::modelAboutToBeReset() + \since 4.2 + + This signal is emitted when reset() is called, before the model's internal + state (e.g. persistent model indexes) has been invalidated. + + \sa beginResetModel(), modelReset() +*/ + +/*! + \fn QAbstractItemModel::modelReset() + \since 4.1 + + This signal is emitted when reset() is called, after the model's internal + state (e.g. persistent model indexes) has been invalidated. + + \sa endResetModel(), modelAboutToBeReset() +*/ + +/*! + \fn bool QModelIndex::operator<(const QModelIndex &other) const + \since 4.1 + + Returns true if this model index is smaller than the \a other + model index; otherwise returns false. +*/ + +/*! + \fn uint qHash(const QPersistentModelIndex &index) + \since 4.5 + + Returns a hash of the QPersistentModelIndex + */ + + +/*! + \internal + QHash::insertMulti insert the value before the old value. and find() return the new value. + We need insertMultiAtEnd because we don't want to overwrite the old one, which should be removed later + + There should be only one instance QPersistentModelIndexData per index, but in some intermediate state there may be + severals of PersistantModelIndex pointing to the same index, but one is already updated, and the other one is not. + This make sure than when updating the first one we don't overwrite the second one in the hash, and the second one + will be updated right later. + */ +void QAbstractItemModelPrivate::Persistent::insertMultiAtEnd(const QModelIndex& key, QPersistentModelIndexData *data) +{ + QHash<QModelIndex,QPersistentModelIndexData *>::iterator newIt = + indexes.insertMulti(key, data); + QHash<QModelIndex,QPersistentModelIndexData *>::iterator it = newIt + 1; + while (it != indexes.end() && it.key() == key) { + qSwap(*newIt,*it); + newIt = it; + ++it; + } +} + +QT_END_NAMESPACE diff --git a/src/corelib/itemmodels/qabstractitemmodel.h b/src/corelib/itemmodels/qabstractitemmodel.h new file mode 100644 index 0000000000..46dd7880ce --- /dev/null +++ b/src/corelib/itemmodels/qabstractitemmodel.h @@ -0,0 +1,425 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMMODEL_H +#define QABSTRACTITEMMODEL_H + +#include <QtCore/qvariant.h> +#include <QtCore/qobject.h> +#include <QtCore/qhash.h> +#include <QtCore/qset.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QAbstractItemModel; +class QPersistentModelIndex; + +class Q_CORE_EXPORT QModelIndex +{ + friend class QAbstractItemModel; + friend class QProxyModel; +public: + inline QModelIndex() : r(-1), c(-1), p(0), m(0) {} + inline QModelIndex(const QModelIndex &other) + : r(other.r), c(other.c), p(other.p), m(other.m) {} + inline ~QModelIndex() { p = 0; m = 0; } + inline int row() const { return r; } + inline int column() const { return c; } + inline void *internalPointer() const { return p; } + inline qint64 internalId() const { return reinterpret_cast<qint64>(p); } + inline QModelIndex parent() const; + inline QModelIndex sibling(int row, int column) const; + inline QModelIndex child(int row, int column) const; + inline QVariant data(int role = Qt::DisplayRole) const; + inline Qt::ItemFlags flags() const; + inline const QAbstractItemModel *model() const { return m; } + inline bool isValid() const { return (r >= 0) && (c >= 0) && (m != 0); } + inline bool operator==(const QModelIndex &other) const + { return (other.r == r) && (other.p == p) && (other.c == c) && (other.m == m); } + inline bool operator!=(const QModelIndex &other) const + { return !(*this == other); } + inline bool operator<(const QModelIndex &other) const + { + if (r < other.r) return true; + if (r == other.r) { + if (c < other.c) return true; + if (c == other.c) { + if (p < other.p) return true; + if (p == other.p) return m < other.m; + } + } + return false; } +private: + inline QModelIndex(int row, int column, void *ptr, const QAbstractItemModel *model); + int r, c; + void *p; + const QAbstractItemModel *m; +}; +Q_DECLARE_TYPEINFO(QModelIndex, Q_MOVABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug, const QModelIndex &); +#endif + +class QPersistentModelIndexData; + +class Q_CORE_EXPORT QPersistentModelIndex +{ +public: + QPersistentModelIndex(); + QPersistentModelIndex(const QModelIndex &index); + QPersistentModelIndex(const QPersistentModelIndex &other); + ~QPersistentModelIndex(); + bool operator<(const QPersistentModelIndex &other) const; + bool operator==(const QPersistentModelIndex &other) const; + inline bool operator!=(const QPersistentModelIndex &other) const + { return !operator==(other); } + QPersistentModelIndex &operator=(const QPersistentModelIndex &other); + bool operator==(const QModelIndex &other) const; + bool operator!=(const QModelIndex &other) const; + QPersistentModelIndex &operator=(const QModelIndex &other); + operator const QModelIndex&() const; + int row() const; + int column() const; + void *internalPointer() const; + qint64 internalId() const; + QModelIndex parent() const; + QModelIndex sibling(int row, int column) const; + QModelIndex child(int row, int column) const; + QVariant data(int role = Qt::DisplayRole) const; + Qt::ItemFlags flags() const; + const QAbstractItemModel *model() const; + bool isValid() const; +private: + QPersistentModelIndexData *d; + friend uint qHash(const QPersistentModelIndex &); +#ifndef QT_NO_DEBUG_STREAM + friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QPersistentModelIndex &); +#endif +}; +Q_DECLARE_TYPEINFO(QPersistentModelIndex, Q_MOVABLE_TYPE); + +inline uint qHash(const QPersistentModelIndex &index) +{ return qHash(index.d); } + + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug, const QPersistentModelIndex &); +#endif + +template<typename T> class QList; +typedef QList<QModelIndex> QModelIndexList; + +class QMimeData; +class QAbstractItemModelPrivate; +template <class Key, class T> class QMap; + + +class Q_CORE_EXPORT QAbstractItemModel : public QObject +{ + Q_OBJECT + + friend class QPersistentModelIndexData; + friend class QAbstractItemViewPrivate; + friend class QIdentityProxyModel; +public: + + explicit QAbstractItemModel(QObject *parent = 0); + virtual ~QAbstractItemModel(); + + bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const = 0; + virtual QModelIndex parent(const QModelIndex &child) const = 0; + + inline QModelIndex sibling(int row, int column, const QModelIndex &idx) const + { return index(row, column, parent(idx)); } + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0; + virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role = Qt::EditRole); + + virtual QMap<int, QVariant> itemData(const QModelIndex &index) const; + virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles); + + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QModelIndexList &indexes) const; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + virtual Qt::DropActions supportedDropActions() const; + + virtual Qt::DropActions supportedDragActions() const; +#if QT_DEPRECATED_SINCE(5, 0) + void setSupportedDragActions(Qt::DropActions actions) + { + doSetSupportedDragActions(actions); + } +#endif + + virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + virtual bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + virtual bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + inline bool insertRow(int row, const QModelIndex &parent = QModelIndex()); + inline bool insertColumn(int column, const QModelIndex &parent = QModelIndex()); + inline bool removeRow(int row, const QModelIndex &parent = QModelIndex()); + inline bool removeColumn(int column, const QModelIndex &parent = QModelIndex()); + + virtual void fetchMore(const QModelIndex &parent); + virtual bool canFetchMore(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + virtual QModelIndex buddy(const QModelIndex &index) const; + virtual QModelIndexList match(const QModelIndex &start, int role, + const QVariant &value, int hits = 1, + Qt::MatchFlags flags = + Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const; + virtual QSize span(const QModelIndex &index) const; + + virtual QHash<int,QByteArray> roleNames() const; + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + +Q_SIGNALS: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QSet<int> &roles = QSet<int>()); + void headerDataChanged(Qt::Orientation orientation, int first, int last); + void layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>()); + void layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>()); + +#if !defined(Q_MOC_RUN) && !defined(qdoc) +private: // can only be emitted by QAbstractItemModel +#endif + 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); + void rowsRemoved(const QModelIndex &parent, int first, int last); + + void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void columnsInserted(const QModelIndex &parent, int first, int last); + + void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void columnsRemoved(const QModelIndex &parent, int first, int last); + + 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(); + +protected: + QAbstractItemModel(QAbstractItemModelPrivate &dd, QObject *parent = 0); + + inline QModelIndex createIndex(int row, int column, void *data = 0) const; + inline QModelIndex createIndex(int row, int column, int id) const; + inline QModelIndex createIndex(int row, int column, quint32 id) const; + + void encodeData(const QModelIndexList &indexes, QDataStream &stream) const; + bool decodeData(int row, int column, const QModelIndex &parent, QDataStream &stream); + + void beginInsertRows(const QModelIndex &parent, int first, int last); + void endInsertRows(); + + 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 beginResetModel(); + void endResetModel(); + + void changePersistentIndex(const QModelIndex &from, const QModelIndex &to); + void changePersistentIndexList(const QModelIndexList &from, const QModelIndexList &to); + QModelIndexList persistentIndexList() const; + +#if QT_DEPRECATED_SINCE(5,0) + QT_DEPRECATED void setRoleNames(const QHash<int,QByteArray> &theRoleNames) + { + doSetRoleNames(theRoleNames); + } +#endif + +private: + void doSetRoleNames(const QHash<int,QByteArray> &roleNames); + void doSetSupportedDragActions(Qt::DropActions actions); + + Q_DECLARE_PRIVATE(QAbstractItemModel) + Q_DISABLE_COPY(QAbstractItemModel) +}; + +inline bool QAbstractItemModel::insertRow(int arow, const QModelIndex &aparent) +{ return insertRows(arow, 1, aparent); } +inline bool QAbstractItemModel::insertColumn(int acolumn, const QModelIndex &aparent) +{ return insertColumns(acolumn, 1, aparent); } +inline bool QAbstractItemModel::removeRow(int arow, const QModelIndex &aparent) +{ return removeRows(arow, 1, aparent); } +inline bool QAbstractItemModel::removeColumn(int acolumn, const QModelIndex &aparent) +{ return removeColumns(acolumn, 1, aparent); } + +inline QModelIndex QAbstractItemModel::createIndex(int arow, int acolumn, void *adata) const +{ return QModelIndex(arow, acolumn, adata, this); } +inline QModelIndex QAbstractItemModel::createIndex(int arow, int acolumn, int aid) const +#if defined(Q_CC_MSVC) +#pragma warning( push ) +#pragma warning( disable : 4312 ) // avoid conversion warning on 64-bit +#endif +{ return QModelIndex(arow, acolumn, reinterpret_cast<void*>(aid), this); } +#if defined(Q_CC_MSVC) +#pragma warning( pop ) +#endif +inline QModelIndex QAbstractItemModel::createIndex(int arow, int acolumn, quint32 aid) const +#if defined(Q_CC_MSVC) +#pragma warning( push ) +#pragma warning( disable : 4312 ) // avoid conversion warning on 64-bit +#endif +{ return QModelIndex(arow, acolumn, reinterpret_cast<void*>(aid), this); } +#if defined(Q_CC_MSVC) +#pragma warning( pop ) +#endif + + +class Q_CORE_EXPORT QAbstractTableModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit QAbstractTableModel(QObject *parent = 0); + ~QAbstractTableModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); +protected: + QAbstractTableModel(QAbstractItemModelPrivate &dd, QObject *parent); + +private: + Q_DISABLE_COPY(QAbstractTableModel) + QModelIndex parent(const QModelIndex &child) const; + bool hasChildren(const QModelIndex &parent) const; +}; + +class Q_CORE_EXPORT QAbstractListModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit QAbstractListModel(QObject *parent = 0); + ~QAbstractListModel(); + + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); +protected: + QAbstractListModel(QAbstractItemModelPrivate &dd, QObject *parent); + +private: + Q_DISABLE_COPY(QAbstractListModel) + QModelIndex parent(const QModelIndex &child) const; + int columnCount(const QModelIndex &parent) const; + bool hasChildren(const QModelIndex &parent) const; +}; + +// inline implementations + +inline QModelIndex::QModelIndex(int arow, int acolumn, void *adata, + const QAbstractItemModel *amodel) + : r(arow), c(acolumn), p(adata), m(amodel) {} + +inline QModelIndex QModelIndex::parent() const +{ return m ? m->parent(*this) : QModelIndex(); } + +inline QModelIndex QModelIndex::sibling(int arow, int acolumn) const +{ return m ? (r == arow && c == acolumn) ? *this : m->index(arow, acolumn, m->parent(*this)) : QModelIndex(); } + +inline QModelIndex QModelIndex::child(int arow, int acolumn) const +{ return m ? m->index(arow, acolumn, *this) : QModelIndex(); } + +inline QVariant QModelIndex::data(int arole) const +{ return m ? m->data(*this, arole) : QVariant(); } + +inline Qt::ItemFlags QModelIndex::flags() const +{ return m ? m->flags(*this) : Qt::ItemFlags(0); } + +inline uint qHash(const QModelIndex &index) +{ return uint((index.row() << 4) + index.column() + index.internalId()); } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTITEMMODEL_H diff --git a/src/corelib/itemmodels/qabstractitemmodel_p.h b/src/corelib/itemmodels/qabstractitemmodel_p.h new file mode 100644 index 0000000000..3c5d8e5d20 --- /dev/null +++ b/src/corelib/itemmodels/qabstractitemmodel_p.h @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMMODEL_P_H +#define QABSTRACTITEMMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qobject_p.h" +#include "QtCore/qstack.h" +#include "QtCore/qset.h" +#include "QtCore/qhash.h" + +QT_BEGIN_NAMESPACE + +class QPersistentModelIndexData +{ +public: + QPersistentModelIndexData() : model(0) {} + QPersistentModelIndexData(const QModelIndex &idx) : index(idx), model(idx.model()) {} + QModelIndex index; + QAtomicInt ref; + const QAbstractItemModel *model; + static QPersistentModelIndexData *create(const QModelIndex &index); + static void destroy(QPersistentModelIndexData *data); +}; + +class Q_CORE_EXPORT QAbstractItemModelPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractItemModel) + +public: + QAbstractItemModelPrivate() : QObjectPrivate(), supportedDragActions(-1), roleNames(defaultRoleNames()) {} + void removePersistentIndexData(QPersistentModelIndexData *data); + void movePersistentIndexes(QVector<QPersistentModelIndexData *> 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); + void rowsRemoved(const QModelIndex &parent, int first, int last); + void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void columnsInserted(const QModelIndex &parent, int first, int last); + void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void columnsRemoved(const QModelIndex &parent, int first, int last); + 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); + } + + inline QModelIndex createIndex(int row, int column, int id) const { + return q_func()->createIndex(row, column, id); + } + + inline bool indexValid(const QModelIndex &index) const { + return (index.row() >= 0) && (index.column() >= 0) && (index.model() == q_func()); + } + + inline void invalidatePersistentIndexes() { + foreach (QPersistentModelIndexData *data, persistent.indexes) { + data->index = QModelIndex(); + data->model = 0; + } + persistent.indexes.clear(); + } + + /*! + \internal + clean the QPersistentModelIndex relative to the index if there is one. + To be used before an index is invalided + */ + inline void invalidatePersistentIndex(const QModelIndex &index) { + QHash<QModelIndex, QPersistentModelIndexData *>::iterator it = persistent.indexes.find(index); + if(it != persistent.indexes.end()) { + QPersistentModelIndexData *data = *it; + persistent.indexes.erase(it); + data->index = QModelIndex(); + data->model = 0; + } + } + + struct Change { + Change() : first(-1), last(-1) {} + Change(const Change &c) : parent(c.parent), first(c.first), last(c.last), needsAdjust(c.needsAdjust) {} + Change(const QModelIndex &p, int f, int l) : parent(p), first(f), last(l), needsAdjust(false) {} + QModelIndex parent; + int first, last; + + + // In cases such as this: + // - A + // - B + // - C + // - - D + // - - E + // - - F + // + // If B is moved to above E, C is the source parent in the signal and its row is 2. When the move is + // completed however, C is at row 1 and there is no row 2 at the same level in the model at all. + // The QModelIndex is adjusted to correct that in those cases before reporting it though the + // rowsMoved signal. + bool needsAdjust; + + bool isValid() { return first >= 0 && last >= 0; } + }; + QStack<Change> changes; + + struct Persistent { + Persistent() {} + QHash<QModelIndex, QPersistentModelIndexData *> indexes; + QStack<QVector<QPersistentModelIndexData *> > moved; + QStack<QVector<QPersistentModelIndexData *> > invalidated; + void insertMultiAtEnd(const QModelIndex& key, QPersistentModelIndexData *data); + } persistent; + + Qt::DropActions supportedDragActions; + + QHash<int,QByteArray> roleNames; + static const QHash<int,QByteArray> &defaultRoleNames(); +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTITEMMODEL_P_H diff --git a/src/corelib/itemmodels/qabstractproxymodel.cpp b/src/corelib/itemmodels/qabstractproxymodel.cpp new file mode 100644 index 0000000000..46678403db --- /dev/null +++ b/src/corelib/itemmodels/qabstractproxymodel.cpp @@ -0,0 +1,388 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractproxymodel.h" + +#ifndef QT_NO_PROXYMODEL + +#include "qitemselectionmodel.h" +#include <private/qabstractproxymodel_p.h> +#include <QtCore/QSize> +#include <QtCore/QStringList> + + +QT_BEGIN_NAMESPACE + +/*! + \since 4.1 + \class QAbstractProxyModel + \brief The QAbstractProxyModel class provides a base class for proxy item + models that can do sorting, filtering or other data processing tasks. + \ingroup model-view + \inmodule QtCore + + This class defines the standard interface that proxy models must use to be + able to interoperate correctly with other model/view components. It is not + supposed to be instantiated directly. + + All standard proxy models are derived from the QAbstractProxyModel class. + If you need to create a new proxy model class, it is usually better to + subclass an existing class that provides the closest behavior to the one + you want to provide. + + Proxy models that filter or sort items of data from a source model should + be created by using or subclassing QSortFilterProxyModel. + + To subclass QAbstractProxyModel, you need to implement mapFromSource() and + mapToSource(). The mapSelectionFromSource() and mapSelectionToSource() + functions only need to be reimplemented if you need a behavior different + from the default behavior. + + \note If the source model is deleted or no source model is specified, the + proxy model operates on a empty placeholder model. + + \sa QSortFilterProxyModel, QAbstractItemModel, {Model/View Programming} +*/ + +//detects the deletion of the source model +void QAbstractProxyModelPrivate::_q_sourceModelDestroyed() +{ + model = QAbstractItemModelPrivate::staticEmptyModel(); +} + +/*! + Constructs a proxy model with the given \a parent. +*/ + +QAbstractProxyModel::QAbstractProxyModel(QObject *parent) + :QAbstractItemModel(*new QAbstractProxyModelPrivate, parent) +{ + setSourceModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + \internal +*/ + +QAbstractProxyModel::QAbstractProxyModel(QAbstractProxyModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + setSourceModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + Destroys the proxy model. +*/ +QAbstractProxyModel::~QAbstractProxyModel() +{ + +} + +/*! + Sets the given \a sourceModel to be processed by the proxy model. +*/ +void QAbstractProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + Q_D(QAbstractProxyModel); + if (d->model) + disconnect(d->model, SIGNAL(destroyed()), this, SLOT(_q_sourceModelDestroyed())); + + if (sourceModel) { + d->model = sourceModel; + connect(d->model, SIGNAL(destroyed()), this, SLOT(_q_sourceModelDestroyed())); + } else { + d->model = QAbstractItemModelPrivate::staticEmptyModel(); + } + d->roleNames = d->model->roleNames(); +} + +/*! + Returns the model that contains the data that is available through the proxy model. +*/ +QAbstractItemModel *QAbstractProxyModel::sourceModel() const +{ + Q_D(const QAbstractProxyModel); + if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) + return 0; + return d->model; +} + +/*! + \reimp + */ +bool QAbstractProxyModel::submit() +{ + Q_D(QAbstractProxyModel); + return d->model->submit(); +} + +/*! + \reimp + */ +void QAbstractProxyModel::revert() +{ + Q_D(QAbstractProxyModel); + d->model->revert(); +} + + +/*! + \fn QModelIndex QAbstractProxyModel::mapToSource(const QModelIndex &proxyIndex) const + + Reimplement this function to return the model index in the source model that + corresponds to the \a proxyIndex in the proxy model. + + \sa mapFromSource() +*/ + +/*! + \fn QModelIndex QAbstractProxyModel::mapFromSource(const QModelIndex &sourceIndex) const + + Reimplement this function to return the model index in the proxy model that + corresponds to the \a sourceIndex from the source model. + + \sa mapToSource() +*/ + +/*! + Returns a source selection mapped from the specified \a proxySelection. + + Reimplement this method to map proxy selections to source selections. + */ +QItemSelection QAbstractProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const +{ + QModelIndexList proxyIndexes = proxySelection.indexes(); + QItemSelection sourceSelection; + for (int i = 0; i < proxyIndexes.size(); ++i) { + const QModelIndex proxyIdx = mapToSource(proxyIndexes.at(i)); + if (!proxyIdx.isValid()) + continue; + sourceSelection << QItemSelectionRange(proxyIdx); + } + return sourceSelection; +} + +/*! + Returns a proxy selection mapped from the specified \a sourceSelection. + + Reimplement this method to map source selections to proxy selections. +*/ +QItemSelection QAbstractProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const +{ + QModelIndexList sourceIndexes = sourceSelection.indexes(); + QItemSelection proxySelection; + for (int i = 0; i < sourceIndexes.size(); ++i) { + const QModelIndex srcIdx = mapFromSource(sourceIndexes.at(i)); + if (!srcIdx.isValid()) + continue; + proxySelection << QItemSelectionRange(srcIdx); + } + return proxySelection; +} + +/*! + \reimp + */ +QVariant QAbstractProxyModel::data(const QModelIndex &proxyIndex, int role) const +{ + Q_D(const QAbstractProxyModel); + return d->model->data(mapToSource(proxyIndex), role); +} + +/*! + \reimp + */ +QVariant QAbstractProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QAbstractProxyModel); + int sourceSection; + if (orientation == Qt::Horizontal) { + const QModelIndex proxyIndex = index(0, section); + sourceSection = mapToSource(proxyIndex).column(); + } else { + const QModelIndex proxyIndex = index(section, 0); + sourceSection = mapToSource(proxyIndex).row(); + } + return d->model->headerData(sourceSection, orientation, role); +} + +/*! + \reimp + */ +QMap<int, QVariant> QAbstractProxyModel::itemData(const QModelIndex &proxyIndex) const +{ + Q_D(const QAbstractProxyModel); + return d->model->itemData(mapToSource(proxyIndex)); +} + +/*! + \reimp + */ +Qt::ItemFlags QAbstractProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return d->model->flags(mapToSource(index)); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QAbstractProxyModel); + return d->model->setData(mapToSource(index), value, role); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setItemData(const QModelIndex &index, const QMap< int, QVariant >& roles) +{ + Q_D(QAbstractProxyModel); + return d->model->setItemData(mapToSource(index), roles); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + Q_D(QAbstractProxyModel); + int sourceSection; + if (orientation == Qt::Horizontal) { + const QModelIndex proxyIndex = index(0, section); + sourceSection = mapToSource(proxyIndex).column(); + } else { + const QModelIndex proxyIndex = index(section, 0); + sourceSection = mapToSource(proxyIndex).row(); + } + return d->model->setHeaderData(sourceSection, orientation, value, role); +} + +/*! + \reimp + */ +QModelIndex QAbstractProxyModel::buddy(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return mapFromSource(d->model->buddy(mapToSource(index))); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QAbstractProxyModel); + return d->model->canFetchMore(mapToSource(parent)); +} + +/*! + \reimp + */ +void QAbstractProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QAbstractProxyModel); + d->model->fetchMore(mapToSource(parent)); +} + +/*! + \reimp + */ +void QAbstractProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QAbstractProxyModel); + d->model->sort(column, order); +} + +/*! + \reimp + */ +QSize QAbstractProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return d->model->span(mapToSource(index)); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QAbstractProxyModel); + return d->model->hasChildren(mapToSource(parent)); +} + +/*! + \reimp + */ +QMimeData* QAbstractProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QAbstractProxyModel); + QModelIndexList list; + foreach(const QModelIndex &index, indexes) + list << mapToSource(index); + return d->model->mimeData(list); +} + +/*! + \reimp + */ +QStringList QAbstractProxyModel::mimeTypes() const +{ + Q_D(const QAbstractProxyModel); + return d->model->mimeTypes(); +} + +/*! + \reimp + */ +Qt::DropActions QAbstractProxyModel::supportedDropActions() const +{ + Q_D(const QAbstractProxyModel); + return d->model->supportedDropActions(); +} + +QT_END_NAMESPACE + +#include "moc_qabstractproxymodel.cpp" + +#endif // QT_NO_PROXYMODEL diff --git a/src/corelib/itemmodels/qabstractproxymodel.h b/src/corelib/itemmodels/qabstractproxymodel.h new file mode 100644 index 0000000000..000fc8d0ce --- /dev/null +++ b/src/corelib/itemmodels/qabstractproxymodel.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROXYMODEL_H +#define QABSTRACTPROXYMODEL_H + +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_PROXYMODEL + +class QAbstractProxyModelPrivate; +class QItemSelection; + +class Q_CORE_EXPORT QAbstractProxyModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + QAbstractProxyModel(QObject *parent = 0); + ~QAbstractProxyModel(); + + virtual void setSourceModel(QAbstractItemModel *sourceModel); + QAbstractItemModel *sourceModel() const; + + virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const = 0; + virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const = 0; + + virtual QItemSelection mapSelectionToSource(const QItemSelection &selection) const; + virtual QItemSelection mapSelectionFromSource(const QItemSelection &selection) const; + + bool submit(); + void revert(); + + QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QMap<int, QVariant> itemData(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + bool setItemData(const QModelIndex& index, const QMap<int, QVariant> &roles); + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); + + QModelIndex buddy(const QModelIndex &index) const; + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + QSize span(const QModelIndex &index) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QMimeData* mimeData(const QModelIndexList &indexes) const; + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; + +protected: + QAbstractProxyModel(QAbstractProxyModelPrivate &, QObject *parent); + +private: + Q_DECLARE_PRIVATE(QAbstractProxyModel) + Q_DISABLE_COPY(QAbstractProxyModel) + Q_PRIVATE_SLOT(d_func(), void _q_sourceModelDestroyed()) +}; + +#endif // QT_NO_PROXYMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTPROXYMODEL_H diff --git a/src/corelib/itemmodels/qabstractproxymodel_p.h b/src/corelib/itemmodels/qabstractproxymodel_p.h new file mode 100644 index 0000000000..7d6e49a235 --- /dev/null +++ b/src/corelib/itemmodels/qabstractproxymodel_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROXYMODEL_P_H +#define QABSTRACTPROXYMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_PROXYMODEL + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QAbstractProxyModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QAbstractProxyModel) +public: + QAbstractProxyModelPrivate() : QAbstractItemModelPrivate(), model(0) {} + QAbstractItemModel *model; + virtual void _q_sourceModelDestroyed(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PROXYMODEL + +#endif // QABSTRACTPROXYMODEL_P_H diff --git a/src/corelib/itemmodels/qidentityproxymodel.cpp b/src/corelib/itemmodels/qidentityproxymodel.cpp new file mode 100644 index 0000000000..6edb5df0fb --- /dev/null +++ b/src/corelib/itemmodels/qidentityproxymodel.cpp @@ -0,0 +1,578 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Stephen Kelly <stephen.kelly@kdab.com> +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qidentityproxymodel.h" + +#ifndef QT_NO_IDENTITYPROXYMODEL + +#include "qitemselectionmodel.h" +#include <private/qabstractproxymodel_p.h> + +QT_BEGIN_NAMESPACE + +class QIdentityProxyModelPrivate : public QAbstractProxyModelPrivate +{ + QIdentityProxyModelPrivate() + { + + } + + Q_DECLARE_PUBLIC(QIdentityProxyModel) + + QList<QPersistentModelIndex> layoutChangePersistentIndexes; + QModelIndexList proxyIndexes; + + void _q_sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_sourceRowsInserted(const QModelIndex &parent, int start, int end); + void _q_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_sourceRowsRemoved(const QModelIndex &parent, int start, int end); + void _q_sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest); + void _q_sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest); + + void _q_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_sourceColumnsInserted(const QModelIndex &parent, int start, int end); + void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_sourceColumnsRemoved(const QModelIndex &parent, int start, int end); + void _q_sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest); + void _q_sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest); + + void _q_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last); + + void _q_sourceLayoutAboutToBeChanged(); + void _q_sourceLayoutChanged(); + void _q_sourceModelAboutToBeReset(); + void _q_sourceModelReset(); + +}; + +/*! + \since 4.8 + \class QIdentityProxyModel + \brief The QIdentityProxyModel class proxies its source model unmodified + + \ingroup model-view + \inmodule QtCore + + QIdentityProxyModel can be used to forward the structure of a source model exactly, with no sorting, filtering or other transformation. + This is similar in concept to an identity matrix where A.I = A. + + Because it does no sorting or filtering, this class is most suitable to proxy models which transform the data() of the source model. + For example, a proxy model could be created to define the font used, or the background colour, or the tooltip etc. This removes the + need to implement all data handling in the same class that creates the structure of the model, and can also be used to create + re-usable components. + + This also provides a way to change the data in the case where a source model is supplied by a third party which can not be modified. + + \snippet doc/src/snippets/code/src_gui_itemviews_qidentityproxymodel.cpp 0 + + \sa QAbstractProxyModel, {Model/View Programming}, QAbstractItemModel + +*/ + +/*! + Constructs an identity model with the given \a parent. +*/ +QIdentityProxyModel::QIdentityProxyModel(QObject* parent) + : QAbstractProxyModel(*new QIdentityProxyModelPrivate, parent) +{ + +} + +/*! \internal + */ +QIdentityProxyModel::QIdentityProxyModel(QIdentityProxyModelPrivate &dd, QObject* parent) + : QAbstractProxyModel(dd, parent) +{ + +} + +/*! + Destroys this identity model. +*/ +QIdentityProxyModel::~QIdentityProxyModel() +{ +} + +/*! + \reimp + */ +int QIdentityProxyModel::columnCount(const QModelIndex& parent) const +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(const QIdentityProxyModel); + return d->model->columnCount(mapToSource(parent)); +} + +/*! + \reimp + */ +bool QIdentityProxyModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(QIdentityProxyModel); + return d->model->dropMimeData(data, action, row, column, mapToSource(parent)); +} + +/*! + \reimp + */ +QModelIndex QIdentityProxyModel::index(int row, int column, const QModelIndex& parent) const +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(const QIdentityProxyModel); + if (!hasIndex(row, column, parent)) + return QModelIndex(); + const QModelIndex sourceParent = mapToSource(parent); + const QModelIndex sourceIndex = d->model->index(row, column, sourceParent); + Q_ASSERT(sourceIndex.isValid()); + return mapFromSource(sourceIndex); +} + +/*! + \reimp + */ +bool QIdentityProxyModel::insertColumns(int column, int count, const QModelIndex& parent) +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(QIdentityProxyModel); + return d->model->insertColumns(column, count, mapToSource(parent)); +} + +/*! + \reimp + */ +bool QIdentityProxyModel::insertRows(int row, int count, const QModelIndex& parent) +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(QIdentityProxyModel); + return d->model->insertRows(row, count, mapToSource(parent)); +} + +/*! + \reimp + */ +QModelIndex QIdentityProxyModel::mapFromSource(const QModelIndex& sourceIndex) const +{ + Q_D(const QIdentityProxyModel); + if (!d->model || !sourceIndex.isValid()) + return QModelIndex(); + + Q_ASSERT(sourceIndex.model() == d->model); + return createIndex(sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer()); +} + +/*! + \reimp + */ +QItemSelection QIdentityProxyModel::mapSelectionFromSource(const QItemSelection& selection) const +{ + Q_D(const QIdentityProxyModel); + QItemSelection proxySelection; + + if (!d->model) + return proxySelection; + + QItemSelection::const_iterator it = selection.constBegin(); + const QItemSelection::const_iterator end = selection.constEnd(); + for ( ; it != end; ++it) { + Q_ASSERT(it->model() == d->model); + const QItemSelectionRange range(mapFromSource(it->topLeft()), mapFromSource(it->bottomRight())); + proxySelection.append(range); + } + + return proxySelection; +} + +/*! + \reimp + */ +QItemSelection QIdentityProxyModel::mapSelectionToSource(const QItemSelection& selection) const +{ + Q_D(const QIdentityProxyModel); + QItemSelection sourceSelection; + + if (!d->model) + return sourceSelection; + + QItemSelection::const_iterator it = selection.constBegin(); + const QItemSelection::const_iterator end = selection.constEnd(); + for ( ; it != end; ++it) { + Q_ASSERT(it->model() == this); + const QItemSelectionRange range(mapToSource(it->topLeft()), mapToSource(it->bottomRight())); + sourceSelection.append(range); + } + + return sourceSelection; +} + +/*! + \reimp + */ +QModelIndex QIdentityProxyModel::mapToSource(const QModelIndex& proxyIndex) const +{ + Q_D(const QIdentityProxyModel); + if (!d->model || !proxyIndex.isValid()) + return QModelIndex(); + Q_ASSERT(proxyIndex.model() == this); + return d->model->createIndex(proxyIndex.row(), proxyIndex.column(), proxyIndex.internalPointer()); +} + +/*! + \reimp + */ +QModelIndexList QIdentityProxyModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const +{ + Q_D(const QIdentityProxyModel); + Q_ASSERT(start.isValid() ? start.model() == this : true); + if (!d->model) + return QModelIndexList(); + + const QModelIndexList sourceList = d->model->match(mapToSource(start), role, value, hits, flags); + QModelIndexList::const_iterator it = sourceList.constBegin(); + const QModelIndexList::const_iterator end = sourceList.constEnd(); + QModelIndexList proxyList; + for ( ; it != end; ++it) + proxyList.append(mapFromSource(*it)); + return proxyList; +} + +/*! + \reimp + */ +QModelIndex QIdentityProxyModel::parent(const QModelIndex& child) const +{ + Q_ASSERT(child.isValid() ? child.model() == this : true); + const QModelIndex sourceIndex = mapToSource(child); + const QModelIndex sourceParent = sourceIndex.parent(); + return mapFromSource(sourceParent); +} + +/*! + \reimp + */ +bool QIdentityProxyModel::removeColumns(int column, int count, const QModelIndex& parent) +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(QIdentityProxyModel); + return d->model->removeColumns(column, count, mapToSource(parent)); +} + +/*! + \reimp + */ +bool QIdentityProxyModel::removeRows(int row, int count, const QModelIndex& parent) +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(QIdentityProxyModel); + return d->model->removeRows(row, count, mapToSource(parent)); +} + +/*! + \reimp + */ +int QIdentityProxyModel::rowCount(const QModelIndex& parent) const +{ + Q_ASSERT(parent.isValid() ? parent.model() == this : true); + Q_D(const QIdentityProxyModel); + return d->model->rowCount(mapToSource(parent)); +} + +/*! + \reimp + */ +void QIdentityProxyModel::setSourceModel(QAbstractItemModel* sourceModel) +{ + beginResetModel(); + + if (sourceModel) { + disconnect(sourceModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(_q_sourceRowsInserted(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(_q_sourceRowsRemoved(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + this, SLOT(_q_sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + disconnect(sourceModel, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + this, SLOT(_q_sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + disconnect(sourceModel, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(columnsInserted(const QModelIndex &, int, int)), + this, SLOT(_q_sourceColumnsInserted(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), + this, SLOT(_q_sourceColumnsRemoved(const QModelIndex &, int, int))); + disconnect(sourceModel, SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + this, SLOT(_q_sourceColumnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + disconnect(sourceModel, SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + this, SLOT(_q_sourceColumnsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + disconnect(sourceModel, SIGNAL(modelAboutToBeReset()), + this, SLOT(_q_sourceModelAboutToBeReset())); + disconnect(sourceModel, SIGNAL(modelReset()), + this, SLOT(_q_sourceModelReset())); + disconnect(sourceModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, SLOT(_q_sourceDataChanged(const QModelIndex &, const QModelIndex &))); + disconnect(sourceModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + disconnect(sourceModel, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + disconnect(sourceModel, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + } + + QAbstractProxyModel::setSourceModel(sourceModel); + + if (sourceModel) { + connect(sourceModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + SLOT(_q_sourceRowsAboutToBeInserted(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + SLOT(_q_sourceRowsInserted(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + SLOT(_q_sourceRowsAboutToBeRemoved(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + SLOT(_q_sourceRowsRemoved(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + SLOT(_q_sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + connect(sourceModel, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + SLOT(_q_sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + connect(sourceModel, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), + SLOT(_q_sourceColumnsAboutToBeInserted(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(columnsInserted(const QModelIndex &, int, int)), + SLOT(_q_sourceColumnsInserted(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), + SLOT(_q_sourceColumnsAboutToBeRemoved(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), + SLOT(_q_sourceColumnsRemoved(const QModelIndex &, int, int))); + connect(sourceModel, SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + SLOT(_q_sourceColumnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + connect(sourceModel, SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), + SLOT(_q_sourceColumnsMoved(const QModelIndex &, int, int, const QModelIndex &, int))); + connect(sourceModel, SIGNAL(modelAboutToBeReset()), + SLOT(_q_sourceModelAboutToBeReset())); + connect(sourceModel, SIGNAL(modelReset()), + SLOT(_q_sourceModelReset())); + connect(sourceModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + SLOT(_q_sourceDataChanged(const QModelIndex &, const QModelIndex &))); + connect(sourceModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + connect(sourceModel, SIGNAL(layoutAboutToBeChanged()), + SLOT(_q_sourceLayoutAboutToBeChanged())); + connect(sourceModel, SIGNAL(layoutChanged()), + SLOT(_q_sourceLayoutChanged())); + } + + endResetModel(); +} + +void QIdentityProxyModelPrivate::_q_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + q->beginInsertColumns(q->mapFromSource(parent), start, end); +} + +void QIdentityProxyModelPrivate::_q_sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) +{ + Q_ASSERT(sourceParent.isValid() ? sourceParent.model() == model : true); + Q_ASSERT(destParent.isValid() ? destParent.model() == model : true); + Q_Q(QIdentityProxyModel); + q->beginMoveColumns(q->mapFromSource(sourceParent), sourceStart, sourceEnd, q->mapFromSource(destParent), dest); +} + +void QIdentityProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + q->beginRemoveColumns(q->mapFromSource(parent), start, end); +} + +void QIdentityProxyModelPrivate::_q_sourceColumnsInserted(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + Q_UNUSED(parent) + Q_UNUSED(start) + Q_UNUSED(end) + q->endInsertColumns(); +} + +void QIdentityProxyModelPrivate::_q_sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) +{ + Q_ASSERT(sourceParent.isValid() ? sourceParent.model() == model : true); + Q_ASSERT(destParent.isValid() ? destParent.model() == model : true); + Q_Q(QIdentityProxyModel); + Q_UNUSED(sourceParent) + Q_UNUSED(sourceStart) + Q_UNUSED(sourceEnd) + Q_UNUSED(destParent) + Q_UNUSED(dest) + q->endMoveColumns(); +} + +void QIdentityProxyModelPrivate::_q_sourceColumnsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + Q_UNUSED(parent) + Q_UNUSED(start) + Q_UNUSED(end) + q->endRemoveColumns(); +} + +void QIdentityProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_ASSERT(topLeft.isValid() ? topLeft.model() == model : true); + Q_ASSERT(bottomRight.isValid() ? bottomRight.model() == model : true); + Q_Q(QIdentityProxyModel); + q->dataChanged(q->mapFromSource(topLeft), q->mapFromSource(bottomRight)); +} + +void QIdentityProxyModelPrivate::_q_sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last) +{ + Q_Q(QIdentityProxyModel); + q->headerDataChanged(orientation, first, last); +} + +void QIdentityProxyModelPrivate::_q_sourceLayoutAboutToBeChanged() +{ + Q_Q(QIdentityProxyModel); + + foreach(const QPersistentModelIndex &proxyPersistentIndex, q->persistentIndexList()) { + proxyIndexes << proxyPersistentIndex; + Q_ASSERT(proxyPersistentIndex.isValid()); + const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex); + Q_ASSERT(srcPersistentIndex.isValid()); + layoutChangePersistentIndexes << srcPersistentIndex; + } + + q->layoutAboutToBeChanged(); +} + +void QIdentityProxyModelPrivate::_q_sourceLayoutChanged() +{ + Q_Q(QIdentityProxyModel); + + for (int i = 0; i < proxyIndexes.size(); ++i) { + q->changePersistentIndex(proxyIndexes.at(i), q->mapFromSource(layoutChangePersistentIndexes.at(i))); + } + + layoutChangePersistentIndexes.clear(); + proxyIndexes.clear(); + + q->layoutChanged(); +} + +void QIdentityProxyModelPrivate::_q_sourceModelAboutToBeReset() +{ + Q_Q(QIdentityProxyModel); + q->beginResetModel(); +} + +void QIdentityProxyModelPrivate::_q_sourceModelReset() +{ + Q_Q(QIdentityProxyModel); + q->endResetModel(); +} + +void QIdentityProxyModelPrivate::_q_sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + q->beginInsertRows(q->mapFromSource(parent), start, end); +} + +void QIdentityProxyModelPrivate::_q_sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) +{ + Q_ASSERT(sourceParent.isValid() ? sourceParent.model() == model : true); + Q_ASSERT(destParent.isValid() ? destParent.model() == model : true); + Q_Q(QIdentityProxyModel); + q->beginMoveRows(q->mapFromSource(sourceParent), sourceStart, sourceEnd, q->mapFromSource(destParent), dest); +} + +void QIdentityProxyModelPrivate::_q_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + q->beginRemoveRows(q->mapFromSource(parent), start, end); +} + +void QIdentityProxyModelPrivate::_q_sourceRowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + Q_UNUSED(parent) + Q_UNUSED(start) + Q_UNUSED(end) + q->endInsertRows(); +} + +void QIdentityProxyModelPrivate::_q_sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest) +{ + Q_ASSERT(sourceParent.isValid() ? sourceParent.model() == model : true); + Q_ASSERT(destParent.isValid() ? destParent.model() == model : true); + Q_Q(QIdentityProxyModel); + Q_UNUSED(sourceParent) + Q_UNUSED(sourceStart) + Q_UNUSED(sourceEnd) + Q_UNUSED(destParent) + Q_UNUSED(dest) + q->endMoveRows(); +} + +void QIdentityProxyModelPrivate::_q_sourceRowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(parent.isValid() ? parent.model() == model : true); + Q_Q(QIdentityProxyModel); + Q_UNUSED(parent) + Q_UNUSED(start) + Q_UNUSED(end) + q->endRemoveRows(); +} + +QT_END_NAMESPACE + +#include "moc_qidentityproxymodel.cpp" + +#endif // QT_NO_IDENTITYPROXYMODEL diff --git a/src/corelib/itemmodels/qidentityproxymodel.h b/src/corelib/itemmodels/qidentityproxymodel.h new file mode 100644 index 0000000000..9d3c085829 --- /dev/null +++ b/src/corelib/itemmodels/qidentityproxymodel.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Stephen Kelly <stephen.kelly@kdab.com> +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QIDENTITYPROXYMODEL_H +#define QIDENTITYPROXYMODEL_H + +#include <QtCore/qabstractproxymodel.h> + +#ifndef QT_NO_IDENTITYPROXYMODEL + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QIdentityProxyModelPrivate; + +class Q_CORE_EXPORT QIdentityProxyModel : public QAbstractProxyModel +{ + Q_OBJECT +public: + explicit QIdentityProxyModel(QObject* parent = 0); + ~QIdentityProxyModel(); + + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + QModelIndex mapFromSource(const QModelIndex& sourceIndex) const; + QModelIndex mapToSource(const QModelIndex& proxyIndex) const; + QModelIndex parent(const QModelIndex& child) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + + QItemSelection mapSelectionFromSource(const QItemSelection& selection) const; + QItemSelection mapSelectionToSource(const QItemSelection& selection) const; + QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const; + void setSourceModel(QAbstractItemModel* sourceModel); + + bool insertColumns(int column, int count, const QModelIndex& parent = QModelIndex()); + bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex& parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); + +protected: + QIdentityProxyModel(QIdentityProxyModelPrivate &dd, QObject* parent); + +private: + Q_DECLARE_PRIVATE(QIdentityProxyModel) + Q_DISABLE_COPY(QIdentityProxyModel) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int)) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceDataChanged(QModelIndex,QModelIndex)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last)) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceModelAboutToBeReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceModelReset()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_IDENTITYPROXYMODEL + +#endif // QIDENTITYPROXYMODEL_H + diff --git a/src/corelib/itemmodels/qitemselectionmodel.cpp b/src/corelib/itemmodels/qitemselectionmodel.cpp new file mode 100644 index 0000000000..3321735498 --- /dev/null +++ b/src/corelib/itemmodels/qitemselectionmodel.cpp @@ -0,0 +1,1641 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qitemselectionmodel.h" +#include <private/qitemselectionmodel_p.h> +#include <qdebug.h> + +#ifndef QT_NO_ITEMVIEWS + +QT_BEGIN_NAMESPACE + +/*! + \class QItemSelectionRange + + \brief The QItemSelectionRange class manages information about a + range of selected items in a model. + + \ingroup model-view + \inmodule QtCore + + A QItemSelectionRange contains information about a range of + selected items in a model. A range of items is a contiguous array + of model items, extending to cover a number of adjacent rows and + columns with a common parent item; this can be visualized as a + two-dimensional block of cells in a table. A selection range has a + top(), left() a bottom(), right() and a parent(). + + The QItemSelectionRange class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The model items contained in the selection range can be obtained + using the indexes() function. Use QItemSelectionModel::selectedIndexes() + to get a list of all selected items for a view. + + You can determine whether a given model item lies within a + particular range by using the contains() function. Ranges can also + be compared using the overloaded operators for equality and + inequality, and the intersects() function allows you to determine + whether two ranges overlap. + + \sa {Model/View Programming}, QAbstractItemModel, QItemSelection, + QItemSelectionModel +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange() + + Constructs an empty selection range. +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QItemSelectionRange &other) + + Copy constructor. Constructs a new selection range with the same contents + as the \a other range given. + +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight) + + Constructs a new selection range containing only the index specified + by the \a topLeft and the index \a bottomRight. + +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &index) + + Constructs a new selection range containing only the model item specified + by the model index \a index. +*/ + +/*! + \fn int QItemSelectionRange::top() const + + Returns the row index corresponding to the uppermost selected row in the + selection range. + +*/ + +/*! + \fn int QItemSelectionRange::left() const + + Returns the column index corresponding to the leftmost selected column in the + selection range. +*/ + +/*! + \fn int QItemSelectionRange::bottom() const + + Returns the row index corresponding to the lowermost selected row in the + selection range. + +*/ + +/*! + \fn int QItemSelectionRange::right() const + + Returns the column index corresponding to the rightmost selected column in + the selection range. + +*/ + +/*! + \fn int QItemSelectionRange::width() const + + Returns the number of selected columns in the selection range. + +*/ + +/*! + \fn int QItemSelectionRange::height() const + + Returns the number of selected rows in the selection range. + +*/ + +/*! + \fn const QAbstractItemModel *QItemSelectionRange::model() const + + Returns the model that the items in the selection range belong to. +*/ + +/*! + \fn QModelIndex QItemSelectionRange::topLeft() const + + Returns the index for the item located at the top-left corner of + the selection range. + + \sa top(), left(), bottomRight() +*/ + +/*! + \fn QModelIndex QItemSelectionRange::bottomRight() const + + Returns the index for the item located at the bottom-right corner + of the selection range. + + \sa bottom(), right(), topLeft() +*/ + +/*! + \fn QModelIndex QItemSelectionRange::parent() const + + Returns the parent model item index of the items in the selection range. + +*/ + +/*! + \fn bool QItemSelectionRange::contains(const QModelIndex &index) const + + Returns true if the model item specified by the \a index lies within the + range of selected items; otherwise returns false. +*/ + +/*! + \fn bool QItemSelectionRange::contains(int row, int column, + const QModelIndex &parentIndex) const + \overload + + Returns true if the model item specified by (\a row, \a column) + and with \a parentIndex as the parent item lies within the range + of selected items; otherwise returns false. +*/ + +/*! + \fn bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const + + Returns true if this selection range intersects (overlaps with) the \a other + range given; otherwise returns false. + +*/ +bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const +{ + return (isValid() && other.isValid() + && parent() == other.parent() + && model() == other.model() + && ((top() <= other.top() && bottom() >= other.top()) + || (top() >= other.top() && top() <= other.bottom())) + && ((left() <= other.left() && right() >= other.left()) + || (left() >= other.left() && left() <= other.right()))); +} + +/*! + \fn QItemSelectionRange QItemSelectionRange::intersect(const QItemSelectionRange &other) const + \obsolete + + Use intersected(\a other) instead. +*/ + +/*! + \fn QItemSelectionRange QItemSelectionRange::intersected(const QItemSelectionRange &other) const + \since 4.2 + + Returns a new selection range containing only the items that are found in + both the selection range and the \a other selection range. +*/ + +QItemSelectionRange QItemSelectionRange::intersect(const QItemSelectionRange &other) const +{ + if (model() == other.model() && parent() == other.parent()) { + QModelIndex topLeft = model()->index(qMax(top(), other.top()), + qMax(left(), other.left()), + other.parent()); + QModelIndex bottomRight = model()->index(qMin(bottom(), other.bottom()), + qMin(right(), other.right()), + other.parent()); + return QItemSelectionRange(topLeft, bottomRight); + } + return QItemSelectionRange(); +} + +/*! + \fn bool QItemSelectionRange::operator==(const QItemSelectionRange &other) const + + Returns true if the selection range is exactly the same as the \a other + range given; otherwise returns false. + +*/ + +/*! + \fn bool QItemSelectionRange::operator!=(const QItemSelectionRange &other) const + + Returns true if the selection range differs from the \a other range given; + otherwise returns false. + +*/ + +/*! + \fn bool QItemSelectionRange::isValid() const + + Returns true if the selection range is valid; otherwise returns false. + +*/ + +/* + \internal + + utility function for getting the indexes from a range + it avoid concatenating list and works on one + */ + +static void indexesFromRange(const QItemSelectionRange &range, QModelIndexList &result) +{ + if (range.isValid() && range.model()) { + for (int column = range.left(); column <= range.right(); ++column) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex index = range.model()->index(row, column, range.parent()); + Qt::ItemFlags flags = range.model()->flags(index); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + result.append(index); + } + } + } +} + +/*! + Returns true if the selection range contains no selectable item + \since 4.7 +*/ + +bool QItemSelectionRange::isEmpty() const +{ + if (!isValid() || !model()) + return true; + + for (int column = left(); column <= right(); ++column) { + for (int row = top(); row <= bottom(); ++row) { + QModelIndex index = model()->index(row, column, parent()); + Qt::ItemFlags flags = model()->flags(index); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + return false; + } + } + return true; +} + +/*! + Returns the list of model index items stored in the selection. +*/ + +QModelIndexList QItemSelectionRange::indexes() const +{ + QModelIndexList result; + indexesFromRange(*this, result); + return result; +} + +/*! + \class QItemSelection + + \brief The QItemSelection class manages information about selected items in a model. + + \ingroup model-view + \inmodule QtCore + + A QItemSelection describes the items in a model that have been + selected by the user. A QItemSelection is basically a list of + selection ranges, see QItemSelectionRange. It provides functions for + creating and manipulating selections, and selecting a range of items + from a model. + + The QItemSelection class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + An item selection can be constructed and initialized to contain a + range of items from an existing model. The following example constructs + a selection that contains a range of items from the given \c model, + beginning at the \c topLeft, and ending at the \c bottomRight. + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemselectionmodel.cpp 0 + + An empty item selection can be constructed, and later populated as + required. So, if the model is going to be unavailable when we construct + the item selection, we can rewrite the above code in the following way: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemselectionmodel.cpp 1 + + QItemSelection saves memory, and avoids unnecessary work, by working with + selection ranges rather than recording the model item index for each + item in the selection. Generally, an instance of this class will contain + a list of non-overlapping selection ranges. + + Use merge() to merge one item selection into another without making + overlapping ranges. Use split() to split one selection range into + smaller ranges based on a another selection range. + + \sa {Model/View Programming}, QItemSelectionModel +*/ + +/*! + \fn QItemSelection::QItemSelection() + + Constructs an empty selection. +*/ + +/*! + Constructs an item selection that extends from the top-left model item, + specified by the \a topLeft index, to the bottom-right item, specified + by \a bottomRight. +*/ +QItemSelection::QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + select(topLeft, bottomRight); +} + +/*! + Adds the items in the range that extends from the top-left model + item, specified by the \a topLeft index, to the bottom-right item, + specified by \a bottomRight to the list. + + \note \a topLeft and \a bottomRight must have the same parent. +*/ +void QItemSelection::select(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!topLeft.isValid() || !bottomRight.isValid()) + return; + + if ((topLeft.model() != bottomRight.model()) + || topLeft.parent() != bottomRight.parent()) { + qWarning("Can't select indexes from different model or with different parents"); + return; + } + if (topLeft.row() > bottomRight.row() || topLeft.column() > bottomRight.column()) { + int top = qMin(topLeft.row(), bottomRight.row()); + int bottom = qMax(topLeft.row(), bottomRight.row()); + int left = qMin(topLeft.column(), bottomRight.column()); + int right = qMax(topLeft.column(), bottomRight.column()); + QModelIndex tl = topLeft.sibling(top, left); + QModelIndex br = bottomRight.sibling(bottom, right); + append(QItemSelectionRange(tl, br)); + return; + } + append(QItemSelectionRange(topLeft, bottomRight)); +} + +/*! + Returns true if the selection contains the given \a index; otherwise + returns false. +*/ + +bool QItemSelection::contains(const QModelIndex &index) const +{ + if (index.flags() & Qt::ItemIsSelectable) { + QList<QItemSelectionRange>::const_iterator it = begin(); + for (; it != end(); ++it) + if ((*it).contains(index)) + return true; + } + return false; +} + +/*! + Returns a list of model indexes that correspond to the selected items. +*/ + +QModelIndexList QItemSelection::indexes() const +{ + QModelIndexList result; + QList<QItemSelectionRange>::const_iterator it = begin(); + for (; it != end(); ++it) + indexesFromRange(*it, result); + return result; +} + +/*! + Merges the \a other selection with this QItemSelection using the + \a command given. This method guarantees that no ranges are overlapping. + + Note that only QItemSelectionModel::Select, + QItemSelectionModel::Deselect, and QItemSelectionModel::Toggle are + supported. + + \sa split() +*/ +void QItemSelection::merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command) +{ + if (other.isEmpty() || + !(command & QItemSelectionModel::Select || + command & QItemSelectionModel::Deselect || + command & QItemSelectionModel::Toggle)) + return; + + QItemSelection newSelection = other; + // Collect intersections + QItemSelection intersections; + QItemSelection::iterator it = newSelection.begin(); + while (it != newSelection.end()) { + if (!(*it).isValid()) { + it = newSelection.erase(it); + continue; + } + for (int t = 0; t < count(); ++t) { + if ((*it).intersects(at(t))) + intersections.append(at(t).intersected(*it)); + } + ++it; + } + + // Split the old (and new) ranges using the intersections + for (int i = 0; i < intersections.count(); ++i) { // for each intersection + for (int t = 0; t < count();) { // splitt each old range + if (at(t).intersects(intersections.at(i))) { + split(at(t), intersections.at(i), this); + removeAt(t); + } else { + ++t; + } + } + // only split newSelection if Toggle is specified + for (int n = 0; (command & QItemSelectionModel::Toggle) && n < newSelection.count();) { + if (newSelection.at(n).intersects(intersections.at(i))) { + split(newSelection.at(n), intersections.at(i), &newSelection); + newSelection.removeAt(n); + } else { + ++n; + } + } + } + // do not add newSelection for Deselect + if (!(command & QItemSelectionModel::Deselect)) + operator+=(newSelection); +} + +/*! + Splits the selection \a range using the selection \a other range. + Removes all items in \a other from \a range and puts the result in \a result. + This can be compared with the semantics of the \e subtract operation of a set. + \sa merge() +*/ + +void QItemSelection::split(const QItemSelectionRange &range, + const QItemSelectionRange &other, QItemSelection *result) +{ + if (range.parent() != other.parent() || range.model() != other.model()) + return; + + QModelIndex parent = other.parent(); + int top = range.top(); + int left = range.left(); + int bottom = range.bottom(); + int right = range.right(); + int other_top = other.top(); + int other_left = other.left(); + int other_bottom = other.bottom(); + int other_right = other.right(); + const QAbstractItemModel *model = range.model(); + Q_ASSERT(model); + if (other_top > top) { + QModelIndex tl = model->index(top, left, parent); + QModelIndex br = model->index(other_top - 1, right, parent); + result->append(QItemSelectionRange(tl, br)); + top = other_top; + } + if (other_bottom < bottom) { + QModelIndex tl = model->index(other_bottom + 1, left, parent); + QModelIndex br = model->index(bottom, right, parent); + result->append(QItemSelectionRange(tl, br)); + bottom = other_bottom; + } + if (other_left > left) { + QModelIndex tl = model->index(top, left, parent); + QModelIndex br = model->index(bottom, other_left - 1, parent); + result->append(QItemSelectionRange(tl, br)); + left = other_left; + } + if (other_right < right) { + QModelIndex tl = model->index(top, other_right + 1, parent); + QModelIndex br = model->index(bottom, right, parent); + result->append(QItemSelectionRange(tl, br)); + right = other_right; + } +} + + +void QItemSelectionModelPrivate::initModel(QAbstractItemModel *model) +{ + this->model = model; + if (model) { + Q_Q(QItemSelectionModel); + QObject::connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + q, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + q, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + q, SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + q, SLOT(_q_layoutAboutToBeChanged())); + QObject::connect(model, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + q, SLOT(_q_layoutAboutToBeChanged())); + QObject::connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + q, SLOT(_q_layoutChanged())); + QObject::connect(model, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), + q, SLOT(_q_layoutChanged())); + QObject::connect(model, SIGNAL(layoutAboutToBeChanged()), + q, SLOT(_q_layoutAboutToBeChanged())); + QObject::connect(model, SIGNAL(layoutChanged()), + q, SLOT(_q_layoutChanged())); + } +} + +/*! + \internal + + returns a QItemSelection where all ranges have been expanded to: + Rows: left: 0 and right: columnCount()-1 + Columns: top: 0 and bottom: rowCount()-1 +*/ + +QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection &selection, + QItemSelectionModel::SelectionFlags command) const +{ + if (selection.isEmpty() && !((command & QItemSelectionModel::Rows) || + (command & QItemSelectionModel::Columns))) + return selection; + + QItemSelection expanded; + if (command & QItemSelectionModel::Rows) { + for (int i = 0; i < selection.count(); ++i) { + QModelIndex parent = selection.at(i).parent(); + int colCount = model->columnCount(parent); + QModelIndex tl = model->index(selection.at(i).top(), 0, parent); + QModelIndex br = model->index(selection.at(i).bottom(), colCount - 1, parent); + //we need to merge because the same row could have already been inserted + expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select); + } + } + if (command & QItemSelectionModel::Columns) { + for (int i = 0; i < selection.count(); ++i) { + QModelIndex parent = selection.at(i).parent(); + int rowCount = model->rowCount(parent); + QModelIndex tl = model->index(0, selection.at(i).left(), parent); + QModelIndex br = model->index(rowCount - 1, selection.at(i).right(), parent); + //we need to merge because the same column could have already been inserted + expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select); + } + } + return expanded; +} + +/*! + \internal +*/ +void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + Q_Q(QItemSelectionModel); + finalize(); + + // update current index + if (currentIndex.isValid() && parent == currentIndex.parent() + && currentIndex.row() >= start && currentIndex.row() <= end) { + QModelIndex old = currentIndex; + if (start > 0) // there are rows left above the change + currentIndex = model->index(start - 1, old.column(), parent); + else if (model && end < model->rowCount(parent) - 1) // there are rows left below the change + currentIndex = model->index(end + 1, old.column(), parent); + else // there are no rows left in the table + currentIndex = QModelIndex(); + emit q->currentChanged(currentIndex, old); + emit q->currentRowChanged(currentIndex, old); + if (currentIndex.column() != old.column()) + emit q->currentColumnChanged(currentIndex, old); + } + + QItemSelection deselected; + QItemSelection newParts; + QItemSelection::iterator it = ranges.begin(); + while (it != ranges.end()) { + if (it->topLeft().parent() != parent) { // Check parents until reaching root or contained in range + QModelIndex itParent = it->topLeft().parent(); + while (itParent.isValid() && itParent.parent() != parent) + itParent = itParent.parent(); + + if (itParent.isValid() && start <= itParent.row() && itParent.row() <= end) { + deselected.append(*it); + it = ranges.erase(it); + } else { + ++it; + } + } else if (start <= it->bottom() && it->bottom() <= end // Full inclusion + && start <= it->top() && it->top() <= end) { + deselected.append(*it); + it = ranges.erase(it); + } else if (start <= it->top() && it->top() <= end) { // Top intersection + deselected.append(QItemSelectionRange(it->topLeft(), model->index(end, it->left(), it->parent()))); + *it = QItemSelectionRange(model->index(end + 1, it->left(), it->parent()), it->bottomRight()); + ++it; + } else if (start <= it->bottom() && it->bottom() <= end) { // Bottom intersection + deselected.append(QItemSelectionRange(model->index(start, it->right(), it->parent()), it->bottomRight())); + *it = QItemSelectionRange(it->topLeft(), model->index(start - 1, it->right(), it->parent())); + ++it; + } else if (it->top() < start && end < it->bottom()) { // Middle intersection + // If the parent contains (1, 2, 3, 4, 5, 6, 7, 8) and [3, 4, 5, 6] is selected, + // and [4, 5] is removed, we need to split [3, 4, 5, 6] into [3], [4, 5] and [6]. + // [4, 5] is appended to deselected, and [3] and [6] remain part of the selection + // in ranges. + const QItemSelectionRange removedRange(model->index(start, it->right(), it->parent()), + model->index(end, it->left(), it->parent())); + deselected.append(removedRange); + QItemSelection::split(*it, removedRange, &newParts); + it = ranges.erase(it); + } else + ++it; + } + ranges.append(newParts); + + if (!deselected.isEmpty()) + emit q->selectionChanged(QItemSelection(), deselected); +} + +/*! + \internal +*/ +void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + Q_Q(QItemSelectionModel); + + // update current index + if (currentIndex.isValid() && parent == currentIndex.parent() + && currentIndex.column() >= start && currentIndex.column() <= end) { + QModelIndex old = currentIndex; + if (start > 0) // there are columns to the left of the change + currentIndex = model->index(old.row(), start - 1, parent); + else if (model && end < model->columnCount() - 1) // there are columns to the right of the change + currentIndex = model->index(old.row(), end + 1, parent); + else // there are no columns left in the table + currentIndex = QModelIndex(); + emit q->currentChanged(currentIndex, old); + if (currentIndex.row() != old.row()) + emit q->currentRowChanged(currentIndex, old); + emit q->currentColumnChanged(currentIndex, old); + } + + // update selections + QModelIndex tl = model->index(0, start, parent); + QModelIndex br = model->index(model->rowCount(parent) - 1, end, parent); + q->select(QItemSelection(tl, br), QItemSelectionModel::Deselect); + finalize(); +} + +/*! + \internal + + Split selection ranges if columns are about to be inserted in the middle. +*/ +void QItemSelectionModelPrivate::_q_columnsAboutToBeInserted(const QModelIndex &parent, + int start, int end) +{ + Q_UNUSED(end); + finalize(); + QList<QItemSelectionRange> split; + QList<QItemSelectionRange>::iterator it = ranges.begin(); + for (; it != ranges.end(); ) { + if ((*it).isValid() && (*it).parent() == parent + && (*it).left() < start && (*it).right() >= start) { + QModelIndex bottomMiddle = model->index((*it).bottom(), start - 1, (*it).parent()); + QItemSelectionRange left((*it).topLeft(), bottomMiddle); + QModelIndex topMiddle = model->index((*it).top(), start, (*it).parent()); + QItemSelectionRange right(topMiddle, (*it).bottomRight()); + it = ranges.erase(it); + split.append(left); + split.append(right); + } else { + ++it; + } + } + ranges += split; +} + +/*! + \internal + + Split selection ranges if rows are about to be inserted in the middle. +*/ +void QItemSelectionModelPrivate::_q_rowsAboutToBeInserted(const QModelIndex &parent, + int start, int end) +{ + Q_UNUSED(end); + finalize(); + QList<QItemSelectionRange> split; + QList<QItemSelectionRange>::iterator it = ranges.begin(); + for (; it != ranges.end(); ) { + if ((*it).isValid() && (*it).parent() == parent + && (*it).top() < start && (*it).bottom() >= start) { + QModelIndex middleRight = model->index(start - 1, (*it).right(), (*it).parent()); + QItemSelectionRange top((*it).topLeft(), middleRight); + QModelIndex middleLeft = model->index(start, (*it).left(), (*it).parent()); + QItemSelectionRange bottom(middleLeft, (*it).bottomRight()); + it = ranges.erase(it); + split.append(top); + split.append(bottom); + } else { + ++it; + } + } + ranges += split; +} + +/*! + \internal + + Split selection into individual (persistent) indexes. This is done in + preparation for the layoutChanged() signal, where the indexes can be + merged again. +*/ +void QItemSelectionModelPrivate::_q_layoutAboutToBeChanged() +{ + savedPersistentIndexes.clear(); + savedPersistentCurrentIndexes.clear(); + + // optimization for when all indexes are selected + // (only if there is lots of items (1000) because this is not entirely correct) + if (ranges.isEmpty() && currentSelection.count() == 1) { + QItemSelectionRange range = currentSelection.first(); + QModelIndex parent = range.parent(); + tableRowCount = model->rowCount(parent); + tableColCount = model->columnCount(parent); + if (tableRowCount * tableColCount > 1000 + && range.top() == 0 + && range.left() == 0 + && range.bottom() == tableRowCount - 1 + && range.right() == tableColCount - 1) { + tableSelected = true; + tableParent = parent; + return; + } + } + tableSelected = false; + + QModelIndexList indexes = ranges.indexes(); + QModelIndexList::const_iterator it; + for (it = indexes.constBegin(); it != indexes.constEnd(); ++it) + savedPersistentIndexes.append(QPersistentModelIndex(*it)); + indexes = currentSelection.indexes(); + for (it = indexes.constBegin(); it != indexes.constEnd(); ++it) + savedPersistentCurrentIndexes.append(QPersistentModelIndex(*it)); +} + +/*! + \internal + + Merges \a indexes into an item selection made up of ranges. + Assumes that the indexes are sorted. +*/ +static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes) +{ + QItemSelection colSpans; + // merge columns + int i = 0; + while (i < indexes.count()) { + QModelIndex tl = indexes.at(i); + QModelIndex br = tl; + while (++i < indexes.count()) { + QModelIndex next = indexes.at(i); + if ((next.parent() == br.parent()) + && (next.row() == br.row()) + && (next.column() == br.column() + 1)) + br = next; + else + break; + } + colSpans.append(QItemSelectionRange(tl, br)); + } + // merge rows + QItemSelection rowSpans; + i = 0; + while (i < colSpans.count()) { + QModelIndex tl = colSpans.at(i).topLeft(); + QModelIndex br = colSpans.at(i).bottomRight(); + QModelIndex prevTl = tl; + while (++i < colSpans.count()) { + QModelIndex nextTl = colSpans.at(i).topLeft(); + QModelIndex nextBr = colSpans.at(i).bottomRight(); + + if (nextTl.parent() != tl.parent()) + break; // we can't merge selection ranges from different parents + + if ((nextTl.column() == prevTl.column()) && (nextBr.column() == br.column()) + && (nextTl.row() == prevTl.row() + 1) && (nextBr.row() == br.row() + 1)) { + br = nextBr; + prevTl = nextTl; + } else { + break; + } + } + rowSpans.append(QItemSelectionRange(tl, br)); + } + return rowSpans; +} + +/*! + \internal + + Merge the selected indexes into selection ranges again. +*/ +void QItemSelectionModelPrivate::_q_layoutChanged() +{ + // special case for when all indexes are selected + if (tableSelected && tableColCount == model->columnCount(tableParent) + && tableRowCount == model->rowCount(tableParent)) { + ranges.clear(); + currentSelection.clear(); + int bottom = tableRowCount - 1; + int right = tableColCount - 1; + QModelIndex tl = model->index(0, 0, tableParent); + QModelIndex br = model->index(bottom, right, tableParent); + currentSelection << QItemSelectionRange(tl, br); + tableParent = QModelIndex(); + tableSelected = false; + return; + } + + if (savedPersistentCurrentIndexes.isEmpty() && savedPersistentIndexes.isEmpty()) { + // either the selection was actually empty, or we + // didn't get the layoutAboutToBeChanged() signal + return; + } + // clear the "old" selection + ranges.clear(); + currentSelection.clear(); + + // sort the "new" selection, as preparation for merging + qStableSort(savedPersistentIndexes.begin(), savedPersistentIndexes.end()); + qStableSort(savedPersistentCurrentIndexes.begin(), savedPersistentCurrentIndexes.end()); + + // update the selection by merging the individual indexes + ranges = mergeIndexes(savedPersistentIndexes); + currentSelection = mergeIndexes(savedPersistentCurrentIndexes); + + // release the persistent indexes + savedPersistentIndexes.clear(); + savedPersistentCurrentIndexes.clear(); +} + +/*! + \class QItemSelectionModel + + \brief The QItemSelectionModel class keeps track of a view's selected items. + + \ingroup model-view + \inmodule QtCore + + A QItemSelectionModel keeps track of the selected items in a view, or + in several views onto the same model. It also keeps track of the + currently selected item in a view. + + The QItemSelectionModel class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The selected items are stored using ranges. Whenever you want to + modify the selected items use select() and provide either a + QItemSelection, or a QModelIndex and a QItemSelectionModel::SelectionFlag. + + The QItemSelectionModel takes a two layer approach to selection + management, dealing with both selected items that have been committed + and items that are part of the current selection. The current + selected items are part of the current interactive selection (for + example with rubber-band selection or keyboard-shift selections). + + To update the currently selected items, use the bitwise OR of + QItemSelectionModel::Current and any of the other SelectionFlags. + If you omit the QItemSelectionModel::Current command, a new current + selection will be created, and the previous one added to the whole + selection. All functions operate on both layers; for example, + selectedItems() will return items from both layers. + + \sa {Model/View Programming}, QAbstractItemModel, {Chart Example} +*/ + +/*! + Constructs a selection model that operates on the specified item \a model. +*/ +QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model) + : QObject(*new QItemSelectionModelPrivate, model) +{ + d_func()->initModel(model); +} + +/*! + Constructs a selection model that operates on the specified item \a model with \a parent. +*/ +QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model, QObject *parent) + : QObject(*new QItemSelectionModelPrivate, parent) +{ + d_func()->initModel(model); +} + +/*! + \internal +*/ +QItemSelectionModel::QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model) + : QObject(dd, model) +{ + dd.initModel(model); +} + +/*! + Destroys the selection model. +*/ +QItemSelectionModel::~QItemSelectionModel() +{ +} + +/*! + Selects the model item \a index using the specified \a command, and emits + selectionChanged(). + + \sa QItemSelectionModel::SelectionFlags +*/ +void QItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + QItemSelection selection(index, index); + select(selection, command); +} + +/*! + \fn void QItemSelectionModel::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted whenever the current item changes. The \a previous + model item index is replaced by the \a current index as the selection's + current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentIndex() setCurrentIndex() selectionChanged() +*/ + +/*! + \fn void QItemSelectionModel::currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted if the \a current item changes and its column is + different to the column of the \a previous current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentChanged() currentRowChanged() currentIndex() setCurrentIndex() +*/ + +/*! + \fn void QItemSelectionModel::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted if the \a current item changes and its row is + different to the row of the \a previous current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentChanged() currentColumnChanged() currentIndex() setCurrentIndex() +*/ + +/*! + \fn void QItemSelectionModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) + + This signal is emitted whenever the selection changes. The change in the + selection is represented as an item selection of \a deselected items and + an item selection of \a selected items. + + Note the that the current index changes independently from the selection. + Also note that this signal will not be emitted when the item model is reset. + + \sa select() currentChanged() +*/ + +/*! + \enum QItemSelectionModel::SelectionFlag + + This enum describes the way the selection model will be updated. + + \value NoUpdate No selection will be made. + \value Clear The complete selection will be cleared. + \value Select All specified indexes will be selected. + \value Deselect All specified indexes will be deselected. + \value Toggle All specified indexes will be selected or + deselected depending on their current state. + \value Current The current selection will be updated. + \value Rows All indexes will be expanded to span rows. + \value Columns All indexes will be expanded to span columns. + \value SelectCurrent A combination of Select and Current, provided for + convenience. + \value ToggleCurrent A combination of Toggle and Current, provided for + convenience. + \value ClearAndSelect A combination of Clear and Select, provided for + convenience. +*/ + +/*! + Selects the item \a selection using the specified \a command, and emits + selectionChanged(). + + \sa QItemSelectionModel::SelectionFlag +*/ +void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QItemSelectionModel); + if (command == NoUpdate) + return; + + // store old selection + QItemSelection sel = selection; + // If d->ranges is non-empty when the source model is reset the persistent indexes + // it contains will be invalid. We can't clear them in a modelReset slot because that might already + // be too late if another model observer is connected to the same modelReset slot and is invoked first + // it might call select() on this selection model before any such QItemSelectionModelPrivate::_q_modelReset() slot + // is invoked, so it would not be cleared yet. We clear it invalid ranges in it here. + QItemSelection::iterator it = d->ranges.begin(); + while (it != d->ranges.end()) { + if (!it->isValid()) + it = d->ranges.erase(it); + else + ++it; + } + + QItemSelection old = d->ranges; + old.merge(d->currentSelection, d->currentCommand); + + // expand selection according to SelectionBehavior + if (command & Rows || command & Columns) + sel = d->expandSelection(sel, command); + + // clear ranges and currentSelection + if (command & Clear) { + d->ranges.clear(); + d->currentSelection.clear(); + } + + // merge and clear currentSelection if Current was not set (ie. start new currentSelection) + if (!(command & Current)) + d->finalize(); + + // update currentSelection + if (command & Toggle || command & Select || command & Deselect) { + d->currentCommand = command; + d->currentSelection = sel; + } + + // generate new selection, compare with old and emit selectionChanged() + QItemSelection newSelection = d->ranges; + newSelection.merge(d->currentSelection, d->currentCommand); + emitSelectionChanged(newSelection, old); +} + +/*! + Clears the selection model. Emits selectionChanged() and currentChanged(). +*/ +void QItemSelectionModel::clear() +{ + clearSelection(); + clearCurrentIndex(); +} + +/*! + Clears the current index. Emits currentChanged(). + */ +void QItemSelectionModel::clearCurrentIndex() +{ + Q_D(QItemSelectionModel); + QModelIndex previous = d->currentIndex; + d->currentIndex = QModelIndex(); + if (previous.isValid()) { + emit currentChanged(d->currentIndex, previous); + emit currentRowChanged(d->currentIndex, previous); + emit currentColumnChanged(d->currentIndex, previous); + } +} + +/*! + Clears the selection model. Does not emit any signals. +*/ +void QItemSelectionModel::reset() +{ + bool block = blockSignals(true); + clear(); + blockSignals(block); +} + +/*! + \since 4.2 + Clears the selection in the selection model. Emits selectionChanged(). +*/ +void QItemSelectionModel::clearSelection() +{ + Q_D(QItemSelectionModel); + if (d->ranges.count() == 0 && d->currentSelection.count() == 0) + return; + + select(QItemSelection(), Clear); +} + + +/*! + Sets the model item \a index to be the current item, and emits + currentChanged(). The current item is used for keyboard navigation and + focus indication; it is independent of any selected items, although a + selected item can also be the current item. + + Depending on the specified \a command, the \a index can also become part + of the current selection. + \sa select() +*/ +void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QItemSelectionModel); + if (index == d->currentIndex) { + if (command != NoUpdate) + select(index, command); // select item + return; + } + QPersistentModelIndex previous = d->currentIndex; + d->currentIndex = index; // set current before emitting selection changed below + if (command != NoUpdate) + select(d->currentIndex, command); // select item + emit currentChanged(d->currentIndex, previous); + if (d->currentIndex.row() != previous.row() || + d->currentIndex.parent() != previous.parent()) + emit currentRowChanged(d->currentIndex, previous); + if (d->currentIndex.column() != previous.column() || + d->currentIndex.parent() != previous.parent()) + emit currentColumnChanged(d->currentIndex, previous); +} + +/*! + Returns the model item index for the current item, or an invalid index + if there is no current item. +*/ +QModelIndex QItemSelectionModel::currentIndex() const +{ + return static_cast<QModelIndex>(d_func()->currentIndex); +} + +/*! + Returns true if the given model item \a index is selected. +*/ +bool QItemSelectionModel::isSelected(const QModelIndex &index) const +{ + Q_D(const QItemSelectionModel); + if (d->model != index.model() || !index.isValid()) + return false; + + bool selected = false; + // search model ranges + QList<QItemSelectionRange>::const_iterator it = d->ranges.begin(); + for (; it != d->ranges.end(); ++it) { + if ((*it).isValid() && (*it).contains(index)) { + selected = true; + break; + } + } + + // check currentSelection + if (d->currentSelection.count()) { + if ((d->currentCommand & Deselect) && selected) + selected = !d->currentSelection.contains(index); + else if (d->currentCommand & Toggle) + selected ^= d->currentSelection.contains(index); + else if ((d->currentCommand & Select) && !selected) + selected = d->currentSelection.contains(index); + } + + if (selected) { + Qt::ItemFlags flags = d->model->flags(index); + return (flags & Qt::ItemIsSelectable); + } + + return false; +} + +/*! + Returns true if all items are selected in the \a row with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same row and that unselectable items are + ignored. +*/ +bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + // return false if row exist in currentSelection (Deselect) + if (d->currentCommand & Deselect && d->currentSelection.count()) { + for (int i=0; i<d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).parent() == parent && + row >= d->currentSelection.at(i).top() && + row <= d->currentSelection.at(i).bottom()) + return false; + } + } + // return false if ranges in both currentSelection and ranges + // intersect and have the same row contained + if (d->currentCommand & Toggle && d->currentSelection.count()) { + for (int i=0; i<d->currentSelection.count(); ++i) + if (d->currentSelection.at(i).top() <= row && + d->currentSelection.at(i).bottom() >= row) + for (int j=0; j<d->ranges.count(); ++j) + if (d->ranges.at(j).top() <= row && d->ranges.at(j).bottom() >= row + && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) + return false; + } + // add ranges and currentSelection and check through them all + QList<QItemSelectionRange>::const_iterator it; + QList<QItemSelectionRange> joined = d->ranges; + if (d->currentSelection.count()) + joined += d->currentSelection; + int colCount = d->model->columnCount(parent); + for (int column = 0; column < colCount; ++column) { + for (it = joined.constBegin(); it != joined.constEnd(); ++it) { + if ((*it).contains(row, column, parent)) { + bool selectable = false; + for (int i = column; !selectable && i <= (*it).right(); ++i) { + Qt::ItemFlags flags = d->model->index(row, i, parent).flags(); + selectable = flags & Qt::ItemIsSelectable; + } + if (selectable){ + column = qMax(column, (*it).right()); + break; + } + } + } + if (it == joined.constEnd()) + return false; + } + return colCount > 0; // no columns means no selected items +} + +/*! + Returns true if all items are selected in the \a column with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same column and that unselectable items are + ignored. +*/ +bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + // return false if column exist in currentSelection (Deselect) + if (d->currentCommand & Deselect && d->currentSelection.count()) { + for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).parent() == parent && + column >= d->currentSelection.at(i).left() && + column <= d->currentSelection.at(i).right()) + return false; + } + } + // return false if ranges in both currentSelection and the selection model + // intersect and have the same column contained + if (d->currentCommand & Toggle && d->currentSelection.count()) { + for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).left() <= column && + d->currentSelection.at(i).right() >= column) { + for (int j = 0; j < d->ranges.count(); ++j) { + if (d->ranges.at(j).left() <= column && d->ranges.at(j).right() >= column + && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) { + return false; + } + } + } + } + } + // add ranges and currentSelection and check through them all + QList<QItemSelectionRange>::const_iterator it; + QList<QItemSelectionRange> joined = d->ranges; + if (d->currentSelection.count()) + joined += d->currentSelection; + int rowCount = d->model->rowCount(parent); + for (int row = 0; row < rowCount; ++row) { + for (it = joined.constBegin(); it != joined.constEnd(); ++it) { + if ((*it).contains(row, column, parent)) { + Qt::ItemFlags flags = d->model->index(row, column, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) { + row = qMax(row, (*it).bottom()); + break; + } + } + } + if (it == joined.constEnd()) + return false; + } + return rowCount > 0; // no rows means no selected items +} + +/*! + Returns true if there are any items selected in the \a row with the given + \a parent. +*/ +bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + for (int i = 0; i < sel.count(); ++i) { + int top = sel.at(i).top(); + int bottom = sel.at(i).bottom(); + int left = sel.at(i).left(); + int right = sel.at(i).right(); + if (top <= row && bottom >= row) { + for (int j = left; j <= right; j++) { + const Qt::ItemFlags flags = d->model->index(row, j, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + return true; + } + } + } + + return false; +} + +/*! + Returns true if there are any items selected in the \a column with the given + \a parent. +*/ +bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + for (int i = 0; i < sel.count(); ++i) { + int left = sel.at(i).left(); + int right = sel.at(i).right(); + int top = sel.at(i).top(); + int bottom = sel.at(i).bottom(); + if (left <= column && right >= column) { + for (int j = top; j <= bottom; j++) { + const Qt::ItemFlags flags = d->model->index(j, column, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + return true; + } + } + } + + return false; +} + +/*! + \since 4.2 + + Returns true if the selection model contains any selection ranges; + otherwise returns false. +*/ +bool QItemSelectionModel::hasSelection() const +{ + Q_D(const QItemSelectionModel); + if (d->currentCommand & (Toggle | Deselect)) { + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + return !sel.isEmpty(); + } else { + return !(d->ranges.isEmpty() && d->currentSelection.isEmpty()); + } +} + +/*! + Returns a list of all selected model item indexes. The list contains no + duplicates, and is not sorted. +*/ +QModelIndexList QItemSelectionModel::selectedIndexes() const +{ + Q_D(const QItemSelectionModel); + QItemSelection selected = d->ranges; + selected.merge(d->currentSelection, d->currentCommand); + return selected.indexes(); +} + +/*! + \since 4.2 + Returns the indexes in the given \a column for the rows where all columns are selected. + + \sa selectedIndexes(), selectedColumns() +*/ + +QModelIndexList QItemSelectionModel::selectedRows(int column) const +{ + QModelIndexList indexes; + //the QSet contains pairs of parent modelIndex + //and row number + QSet< QPair<QModelIndex, int> > rowsSeen; + + const QItemSelection ranges = selection(); + for (int i = 0; i < ranges.count(); ++i) { + const QItemSelectionRange &range = ranges.at(i); + QModelIndex parent = range.parent(); + for (int row = range.top(); row <= range.bottom(); row++) { + QPair<QModelIndex, int> rowDef = qMakePair(parent, row); + if (!rowsSeen.contains(rowDef)) { + rowsSeen << rowDef; + if (isRowSelected(row, parent)) { + indexes.append(model()->index(row, column, parent)); + } + } + } + } + + return indexes; +} + +/*! + \since 4.2 + Returns the indexes in the given \a row for columns where all rows are selected. + + \sa selectedIndexes(), selectedRows() +*/ + +QModelIndexList QItemSelectionModel::selectedColumns(int row) const +{ + QModelIndexList indexes; + //the QSet contains pairs of parent modelIndex + //and column number + QSet< QPair<QModelIndex, int> > columnsSeen; + + const QItemSelection ranges = selection(); + for (int i = 0; i < ranges.count(); ++i) { + const QItemSelectionRange &range = ranges.at(i); + QModelIndex parent = range.parent(); + for (int column = range.left(); column <= range.right(); column++) { + QPair<QModelIndex, int> columnDef = qMakePair(parent, column); + if (!columnsSeen.contains(columnDef)) { + columnsSeen << columnDef; + if (isColumnSelected(column, parent)) { + indexes.append(model()->index(row, column, parent)); + } + } + } + } + + return indexes; +} + +/*! + Returns the selection ranges stored in the selection model. +*/ +const QItemSelection QItemSelectionModel::selection() const +{ + Q_D(const QItemSelectionModel); + QItemSelection selected = d->ranges; + selected.merge(d->currentSelection, d->currentCommand); + int i = 0; + // make sure we have no invalid ranges + // ### should probably be handled more generic somewhere else + while (i<selected.count()) { + if (selected.at(i).isValid()) + ++i; + else + (selected.removeAt(i)); + } + return selected; +} + +/*! + Returns the item model operated on by the selection model. +*/ +const QAbstractItemModel *QItemSelectionModel::model() const +{ + return d_func()->model; +} + +/*! + Compares the two selections \a newSelection and \a oldSelection + and emits selectionChanged() with the deselected and selected items. +*/ +void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelection, + const QItemSelection &oldSelection) +{ + // if both selections are empty or equal we return + if ((oldSelection.isEmpty() && newSelection.isEmpty()) || + oldSelection == newSelection) + return; + + // if either selection is empty we do not need to compare + if (oldSelection.isEmpty() || newSelection.isEmpty()) { + emit selectionChanged(newSelection, oldSelection); + return; + } + + QItemSelection deselected = oldSelection; + QItemSelection selected = newSelection; + + // remove equal ranges + bool advance; + for (int o = 0; o < deselected.count(); ++o) { + advance = true; + for (int s = 0; s < selected.count() && o < deselected.count();) { + if (deselected.at(o) == selected.at(s)) { + deselected.removeAt(o); + selected.removeAt(s); + advance = false; + } else { + ++s; + } + } + if (advance) + ++o; + } + + // find intersections + QItemSelection intersections; + for (int o = 0; o < deselected.count(); ++o) { + for (int s = 0; s < selected.count(); ++s) { + if (deselected.at(o).intersects(selected.at(s))) + intersections.append(deselected.at(o).intersected(selected.at(s))); + } + } + + // compare remaining ranges with intersections and split them to find deselected and selected + for (int i = 0; i < intersections.count(); ++i) { + // split deselected + for (int o = 0; o < deselected.count();) { + if (deselected.at(o).intersects(intersections.at(i))) { + QItemSelection::split(deselected.at(o), intersections.at(i), &deselected); + deselected.removeAt(o); + } else { + ++o; + } + } + // split selected + for (int s = 0; s < selected.count();) { + if (selected.at(s).intersects(intersections.at(i))) { + QItemSelection::split(selected.at(s), intersections.at(i), &selected); + selected.removeAt(s); + } else { + ++s; + } + } + } + + if (!selected.isEmpty() || !deselected.isEmpty()) + emit selectionChanged(selected, deselected); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QItemSelectionRange &range) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QItemSelectionRange(" << range.topLeft() + << ',' << range.bottomRight() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QItemSelectionRange to QDebug"); + return dbg; + Q_UNUSED(range); +#endif +} +#endif + +QT_END_NAMESPACE + +#include "moc_qitemselectionmodel.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/corelib/itemmodels/qitemselectionmodel.h b/src/corelib/itemmodels/qitemselectionmodel.h new file mode 100644 index 0000000000..3b3fa8d1e8 --- /dev/null +++ b/src/corelib/itemmodels/qitemselectionmodel.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMSELECTIONMODEL_H +#define QITEMSELECTIONMODEL_H + +#include <QtCore/qset.h> +#include <QtCore/qvector.h> +#include <QtCore/qlist.h> +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_ITEMVIEWS + +class Q_CORE_EXPORT QItemSelectionRange +{ + +public: + inline QItemSelectionRange() {} + inline QItemSelectionRange(const QItemSelectionRange &other) + : tl(other.tl), br(other.br) {} + inline QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight); + explicit inline QItemSelectionRange(const QModelIndex &index) + { tl = index; br = tl; } + + inline int top() const { return tl.row(); } + inline int left() const { return tl.column(); } + inline int bottom() const { return br.row(); } + inline int right() const { return br.column(); } + inline int width() const { return br.column() - tl.column() + 1; } + inline int height() const { return br.row() - tl.row() + 1; } + + inline QModelIndex topLeft() const { return QModelIndex(tl); } + inline QModelIndex bottomRight() const { return QModelIndex(br); } + inline QModelIndex parent() const { return tl.parent(); } + inline const QAbstractItemModel *model() const { return tl.model(); } + + inline bool contains(const QModelIndex &index) const + { + return (parent() == index.parent() + && tl.row() <= index.row() && tl.column() <= index.column() + && br.row() >= index.row() && br.column() >= index.column()); + } + + inline bool contains(int row, int column, const QModelIndex &parentIndex) const + { + return (parent() == parentIndex + && tl.row() <= row && tl.column() <= column + && br.row() >= row && br.column() >= column); + } + + bool intersects(const QItemSelectionRange &other) const; + QItemSelectionRange intersect(const QItemSelectionRange &other) const; // ### Qt 5: make QT4_SUPPORT + inline QItemSelectionRange intersected(const QItemSelectionRange &other) const + { return intersect(other); } + + inline bool operator==(const QItemSelectionRange &other) const + { return (tl == other.tl && br == other.br); } + inline bool operator!=(const QItemSelectionRange &other) const + { return !operator==(other); } + inline bool operator<(const QItemSelectionRange &other) const + { + // Comparing parents will compare the models, but if two equivalent ranges + // in two different models have invalid parents, they would appear the same + if (other.tl.model() == tl.model()) { + // parent has to be calculated, so we only do so once. + const QModelIndex topLeftParent = tl.parent(); + const QModelIndex otherTopLeftParent = other.tl.parent(); + if (topLeftParent == otherTopLeftParent) { + if (other.tl.row() == tl.row()) { + if (other.tl.column() == tl.column()) { + if (other.br.row() == br.row()) { + return br.column() < other.br.column(); + } + return br.row() < other.br.row(); + } + return tl.column() < other.tl.column(); + } + return tl.row() < other.tl.row(); + } + return topLeftParent < otherTopLeftParent; + } + return tl.model() < other.tl.model(); + } + + inline bool isValid() const + { + return (tl.isValid() && br.isValid() && tl.parent() == br.parent() + && top() <= bottom() && left() <= right()); + } + + bool isEmpty() const; + + QModelIndexList indexes() const; + +private: + QPersistentModelIndex tl, br; +}; +Q_DECLARE_TYPEINFO(QItemSelectionRange, Q_MOVABLE_TYPE); + +inline QItemSelectionRange::QItemSelectionRange(const QModelIndex &atopLeft, + const QModelIndex &abottomRight) +{ tl = atopLeft; br = abottomRight; } + +class QItemSelection; +class QItemSelectionModelPrivate; + +class Q_CORE_EXPORT QItemSelectionModel : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QItemSelectionModel) + Q_FLAGS(SelectionFlags) + +public: + + enum SelectionFlag { + NoUpdate = 0x0000, + Clear = 0x0001, + Select = 0x0002, + Deselect = 0x0004, + Toggle = 0x0008, + Current = 0x0010, + Rows = 0x0020, + Columns = 0x0040, + SelectCurrent = Select | Current, + ToggleCurrent = Toggle | Current, + ClearAndSelect = Clear | Select + }; + + Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag) + + explicit QItemSelectionModel(QAbstractItemModel *model); + explicit QItemSelectionModel(QAbstractItemModel *model, QObject *parent); + virtual ~QItemSelectionModel(); + + QModelIndex currentIndex() const; + + bool isSelected(const QModelIndex &index) const; + bool isRowSelected(int row, const QModelIndex &parent) const; + bool isColumnSelected(int column, const QModelIndex &parent) const; + + bool rowIntersectsSelection(int row, const QModelIndex &parent) const; + bool columnIntersectsSelection(int column, const QModelIndex &parent) const; + + bool hasSelection() const; + + QModelIndexList selectedIndexes() const; + QModelIndexList selectedRows(int column = 0) const; + QModelIndexList selectedColumns(int row = 0) const; + const QItemSelection selection() const; + + const QAbstractItemModel *model() const; + +public Q_SLOTS: + virtual void setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + virtual void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + virtual void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command); + virtual void clear(); + virtual void reset(); + + void clearSelection(); + virtual void clearCurrentIndex(); + +Q_SIGNALS: + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous); + +protected: + QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model); + void emitSelectionChanged(const QItemSelection &newSelection, const QItemSelection &oldSelection); + +private: + Q_DISABLE_COPY(QItemSelectionModel) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QItemSelectionModel::SelectionFlags) + +// dummy implentation of qHash() necessary for instantiating QList<QItemSelectionRange>::toSet() with MSVC +inline uint qHash(const QItemSelectionRange &) { return 0; } + +class Q_CORE_EXPORT QItemSelection : public QList<QItemSelectionRange> +{ +public: + QItemSelection() {} + QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void select(const QModelIndex &topLeft, const QModelIndex &bottomRight); + bool contains(const QModelIndex &index) const; + QModelIndexList indexes() const; + void merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command); + static void split(const QItemSelectionRange &range, + const QItemSelectionRange &other, + QItemSelection *result); +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug, const QItemSelectionRange &); +#endif + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMSELECTIONMODEL_H diff --git a/src/corelib/itemmodels/qitemselectionmodel_p.h b/src/corelib/itemmodels/qitemselectionmodel_p.h new file mode 100644 index 0000000000..919c4fab05 --- /dev/null +++ b/src/corelib/itemmodels/qitemselectionmodel_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMSELECTIONMODEL_P_H +#define QITEMSELECTIONMODEL_P_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 "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_ITEMVIEWS +class QItemSelectionModelPrivate: public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QItemSelectionModel) +public: + QItemSelectionModelPrivate() + : model(0), + currentCommand(QItemSelectionModel::NoUpdate), + tableSelected(false), tableColCount(0), tableRowCount(0) {} + + QItemSelection expandSelection(const QItemSelection &selection, + QItemSelectionModel::SelectionFlags command) const; + + void initModel(QAbstractItemModel *model); + + void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_layoutAboutToBeChanged(); + void _q_layoutChanged(); + + inline void remove(QList<QItemSelectionRange> &r) + { + QList<QItemSelectionRange>::const_iterator it = r.constBegin(); + for (; it != r.constEnd(); ++it) + ranges.removeAll(*it); + } + + inline void finalize() + { + ranges.merge(currentSelection, currentCommand); + if (!currentSelection.isEmpty()) // ### perhaps this should be in QList + currentSelection.clear(); + } + + QPointer<QAbstractItemModel> model; + QItemSelection ranges; + QItemSelection currentSelection; + QPersistentModelIndex currentIndex; + QItemSelectionModel::SelectionFlags currentCommand; + QList<QPersistentModelIndex> savedPersistentIndexes; + QList<QPersistentModelIndex> savedPersistentCurrentIndexes; + // optimization when all indexes are selected + bool tableSelected; + QPersistentModelIndex tableParent; + int tableColCount, tableRowCount; +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +#endif // QITEMSELECTIONMODEL_P_H diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.cpp b/src/corelib/itemmodels/qsortfilterproxymodel.cpp new file mode 100644 index 0000000000..3a63c923d3 --- /dev/null +++ b/src/corelib/itemmodels/qsortfilterproxymodel.cpp @@ -0,0 +1,2703 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsortfilterproxymodel.h" + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include "qitemselectionmodel.h" +#include <qsize.h> +#include <qdebug.h> +#include <qdatetime.h> +#include <qpair.h> +#include <qstringlist.h> +#include <private/qabstractitemmodel_p.h> +#include <private/qabstractproxymodel_p.h> + +QT_BEGIN_NAMESPACE + +typedef QList<QPair<QModelIndex, QPersistentModelIndex> > QModelIndexPairList; + +static inline QSet<int> qVectorToSet(const QVector<int> &vector) +{ + QSet<int> set; + set.reserve(vector.size()); + for(int i=0; i < vector.size(); ++i) + set << vector.at(i); + return set; +} + +class QSortFilterProxyModelLessThan +{ +public: + inline QSortFilterProxyModelLessThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i1, i2); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + +class QSortFilterProxyModelGreaterThan +{ +public: + inline QSortFilterProxyModelGreaterThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), + source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i2, i1); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + + +//this struct is used to store what are the rows that are removed +//between a call to rowsAboutToBeRemoved and rowsRemoved +//it avoids readding rows to the mapping that are currently being removed +struct QRowsRemoval +{ + QRowsRemoval(const QModelIndex &parent_source, int start, int end) : parent_source(parent_source), start(start), end(end) + { + } + + QRowsRemoval() : start(-1), end(-1) + { + } + + bool contains(QModelIndex parent, int row) + { + do { + if (parent == parent_source) + return row >= start && row <= end; + row = parent.row(); + parent = parent.parent(); + } while (row >= 0); + return false; + } +private: + QModelIndex parent_source; + int start; + int end; +}; + +class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate +{ + Q_DECLARE_PUBLIC(QSortFilterProxyModel) + +public: + struct Mapping { + QVector<int> source_rows; + QVector<int> source_columns; + QVector<int> proxy_rows; + QVector<int> proxy_columns; + QVector<QModelIndex> mapped_children; + QHash<QModelIndex, Mapping *>::const_iterator map_iter; + }; + + mutable QHash<QModelIndex, Mapping*> source_index_mapping; + + int source_sort_column; + int proxy_sort_column; + Qt::SortOrder sort_order; + Qt::CaseSensitivity sort_casesensitivity; + int sort_role; + bool sort_localeaware; + + int filter_column; + QRegExp filter_regexp; + int filter_role; + + bool dynamic_sortfilter; + QRowsRemoval itemsBeingRemoved; + + QModelIndexPairList saved_persistent_indexes; + + QHash<QModelIndex, Mapping *>::const_iterator create_mapping( + const QModelIndex &source_parent) const; + QModelIndex proxy_to_source(const QModelIndex &proxyIndex) const; + QModelIndex source_to_proxy(const QModelIndex &sourceIndex) const; + bool can_create_mapping(const QModelIndex &source_parent) const; + + void remove_from_mapping(const QModelIndex &source_parent); + + inline QHash<QModelIndex, Mapping *>::const_iterator index_to_iterator( + const QModelIndex &proxy_index) const + { + Q_ASSERT(proxy_index.isValid()); + Q_ASSERT(proxy_index.model() == q_func()); + const void *p = proxy_index.internalPointer(); + Q_ASSERT(p); + QHash<QModelIndex, Mapping *>::const_iterator it = + static_cast<const Mapping*>(p)->map_iter; + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + return it; + } + + inline QModelIndex create_index(int row, int column, + QHash<QModelIndex, Mapping*>::const_iterator it) const + { + return q_func()->createIndex(row, column, *it); + } + + void _q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right); + void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end); + + void _q_sourceAboutToBeReset(); + void _q_sourceReset(); + + void _q_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents); + void _q_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents); + + void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, + int sourceStart, int sourceEnd, + const QModelIndex &destParent, int dest); + void _q_sourceRowsMoved(const QModelIndex &sourceParent, + int sourceStart, int sourceEnd, + const QModelIndex &destParent, int dest); + void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, + int sourceStart, int sourceEnd, + const QModelIndex &destParent, int dest); + void _q_sourceColumnsMoved(const QModelIndex &sourceParent, + int sourceStart, int sourceEnd, + const QModelIndex &destParent, int dest); + + void _q_clearMapping(); + + void sort(); + bool update_source_sort_column(); + void sort_source_rows(QVector<int> &source_rows, + const QModelIndex &source_parent) const; + QVector<QPair<int, QVector<int > > > proxy_intervals_for_source_items_to_add( + const QVector<int> &proxy_to_source, const QVector<int> &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const; + QVector<QPair<int, int > > proxy_intervals_for_source_items( + const QVector<int> &source_to_proxy, const QVector<int> &source_items) const; + void insert_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_proxy_interval( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + int proxy_start, int proxy_end, const QModelIndex &proxy_parent, + Qt::Orientation orient, bool emit_signal = true); + void build_source_to_proxy_mapping( + const QVector<int> &proxy_to_source, QVector<int> &source_to_proxy) const; + void source_items_inserted(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_about_to_be_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void proxy_item_range( + const QVector<int> &source_to_proxy, const QVector<int> &source_items, + int &proxy_low, int &proxy_high) const; + + QModelIndexPairList store_persistent_indexes(); + void update_persistent_indexes(const QModelIndexPairList &source_indexes); + + void filter_changed(const QModelIndex &source_parent = QModelIndex()); + QSet<int> handle_filter_changed( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient); + + void updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove); + + virtual void _q_sourceModelDestroyed(); +}; + +typedef QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping *> IndexMap; + +void QSortFilterProxyModelPrivate::_q_sourceModelDestroyed() +{ + QAbstractProxyModelPrivate::_q_sourceModelDestroyed(); + _q_clearMapping(); +} + +void QSortFilterProxyModelPrivate::remove_from_mapping(const QModelIndex &source_parent) +{ + if (Mapping *m = source_index_mapping.take(source_parent)) { + for (int i = 0; i < m->mapped_children.size(); ++i) + remove_from_mapping(m->mapped_children.at(i)); + delete m; + } +} + +void QSortFilterProxyModelPrivate::_q_clearMapping() +{ + // store the persistent indexes + QModelIndexPairList source_indexes = store_persistent_indexes(); + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + if (dynamic_sortfilter && update_source_sort_column()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + // update the persistent indexes + update_persistent_indexes(source_indexes); +} + +IndexMap::const_iterator QSortFilterProxyModelPrivate::create_mapping( + const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it != source_index_mapping.constEnd()) // was mapped already + return it; + + Mapping *m = new Mapping; + + int source_rows = model->rowCount(source_parent); + m->source_rows.reserve(source_rows); + for (int i = 0; i < source_rows; ++i) { + if (q->filterAcceptsRow(i, source_parent)) + m->source_rows.append(i); + } + int source_cols = model->columnCount(source_parent); + m->source_columns.reserve(source_cols); + for (int i = 0; i < source_cols; ++i) { + if (q->filterAcceptsColumn(i, source_parent)) + m->source_columns.append(i); + } + + sort_source_rows(m->source_rows, source_parent); + m->proxy_rows.resize(source_rows); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + m->proxy_columns.resize(source_cols); + build_source_to_proxy_mapping(m->source_columns, m->proxy_columns); + + it = IndexMap::const_iterator(source_index_mapping.insert(source_parent, m)); + m->map_iter = it; + + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it2 = create_mapping(source_grand_parent); + Q_ASSERT(it2 != source_index_mapping.constEnd()); + it2.value()->mapped_children.append(source_parent); + } + + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + + return it; +} + +QModelIndex QSortFilterProxyModelPrivate::proxy_to_source(const QModelIndex &proxy_index) const +{ + if (!proxy_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + if (proxy_index.model() != q_func()) { + qWarning() << "QSortFilterProxyModel: index from wrong model passed to mapToSource"; + Q_ASSERT(!"QSortFilterProxyModel: index from wrong model passed to mapToSource"); + return QModelIndex(); + } + IndexMap::const_iterator it = index_to_iterator(proxy_index); + Mapping *m = it.value(); + if ((proxy_index.row() >= m->source_rows.size()) || (proxy_index.column() >= m->source_columns.size())) + return QModelIndex(); + int source_row = m->source_rows.at(proxy_index.row()); + int source_col = m->source_columns.at(proxy_index.column()); + return model->index(source_row, source_col, it.key()); +} + +QModelIndex QSortFilterProxyModelPrivate::source_to_proxy(const QModelIndex &source_index) const +{ + if (!source_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + if (source_index.model() != model) { + qWarning() << "QSortFilterProxyModel: index from wrong model passed to mapFromSource"; + Q_ASSERT(!"QSortFilterProxyModel: index from wrong model passed to mapFromSource"); + return QModelIndex(); + } + QModelIndex source_parent = source_index.parent(); + IndexMap::const_iterator it = create_mapping(source_parent); + Mapping *m = it.value(); + if ((source_index.row() >= m->proxy_rows.size()) || (source_index.column() >= m->proxy_columns.size())) + return QModelIndex(); + int proxy_row = m->proxy_rows.at(source_index.row()); + int proxy_column = m->proxy_columns.at(source_index.column()); + if (proxy_row == -1 || proxy_column == -1) + return QModelIndex(); + return create_index(proxy_row, proxy_column, it); +} + +bool QSortFilterProxyModelPrivate::can_create_mapping(const QModelIndex &source_parent) const +{ + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it = source_index_mapping.constFind(source_grand_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for the grand parent + return false; + } + Mapping *gm = it.value(); + if (gm->proxy_rows.at(source_parent.row()) == -1 || + gm->proxy_columns.at(source_parent.column()) == -1) { + // Don't care, since parent is filtered + return false; + } + } + return true; +} + +/*! + \internal + + Sorts the existing mappings. +*/ +void QSortFilterProxyModelPrivate::sort() +{ + Q_Q(QSortFilterProxyModel); + emit q->layoutAboutToBeChanged(); + QModelIndexPairList source_indexes = store_persistent_indexes(); + IndexMap::const_iterator it = source_index_mapping.constBegin(); + for (; it != source_index_mapping.constEnd(); ++it) { + QModelIndex source_parent = it.key(); + Mapping *m = it.value(); + sort_source_rows(m->source_rows, source_parent); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + } + update_persistent_indexes(source_indexes); + emit q->layoutChanged(); +} + +/*! + \internal + + update the source_sort_column according to the proxy_sort_column + return true if the column was changed +*/ +bool QSortFilterProxyModelPrivate::update_source_sort_column() +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_index = q->index(0, proxy_sort_column, QModelIndex()); + int old_source_sort_colum = source_sort_column; + source_sort_column = q->mapToSource(proxy_index).column(); + return old_source_sort_colum != source_sort_column; +} + + +/*! + \internal + + Sorts the given \a source_rows according to current sort column and order. +*/ +void QSortFilterProxyModelPrivate::sort_source_rows( + QVector<int> &source_rows, const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + if (source_sort_column >= 0) { + if (sort_order == Qt::AscendingOrder) { + QSortFilterProxyModelLessThan lt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), lt); + } else { + QSortFilterProxyModelGreaterThan gt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), gt); + } + } else { // restore the source model order + qStableSort(source_rows.begin(), source_rows.end()); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and the set of + source items \a source_items (which are part of that mapping), + determines the corresponding proxy item intervals that should + be removed from the proxy model. + + The result is a vector of pairs, where each pair represents a + (start, end) tuple, sorted in ascending order. +*/ +QVector<QPair<int, int > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items( + const QVector<int> &source_to_proxy, const QVector<int> &source_items) const +{ + QVector<QPair<int, int> > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int source_items_index = 0; + while (source_items_index < source_items.size()) { + int first_proxy_item = source_to_proxy.at(source_items.at(source_items_index)); + Q_ASSERT(first_proxy_item != -1); + int last_proxy_item = first_proxy_item; + ++source_items_index; + // Find end of interval + while ((source_items_index < source_items.size()) + && (source_to_proxy.at(source_items.at(source_items_index)) == last_proxy_item + 1)) { + ++last_proxy_item; + ++source_items_index; + } + // Add interval to result + proxy_intervals.append(QPair<int, int>(first_proxy_item, last_proxy_item)); + } + qStableSort(proxy_intervals.begin(), proxy_intervals.end()); + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a src_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes \a source_items from this proxy model. + The corresponding proxy items are removed in intervals, so that the proper + rows/columnsRemoved(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::remove_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (already removed) + + QVector<QPair<int, int> > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items(source_to_proxy, source_items); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair<int, int> interval = proxy_intervals.at(i); + int proxy_start = interval.first; + int proxy_end = interval.second; + remove_proxy_interval(source_to_proxy, proxy_to_source, proxy_start, proxy_end, + proxy_parent, orient, emit_signal); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes items from \a proxy_start to \a proxy_end + (inclusive) from this proxy model. +*/ +void QSortFilterProxyModelPrivate::remove_proxy_interval( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, int proxy_start, int proxy_end, + const QModelIndex &proxy_parent, Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginRemoveRows(proxy_parent, proxy_start, proxy_end); + else + q->beginRemoveColumns(proxy_parent, proxy_start, proxy_end); + } + + // Remove items from proxy-to-source mapping + proxy_to_source.remove(proxy_start, proxy_end - proxy_start + 1); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endRemoveRows(); + else + q->endRemoveColumns(); + } +} + +/*! + \internal + + Given proxy-to-source mapping \a proxy_to_source and a set of + unmapped source items \a source_items, determines the proxy item + intervals at which the subsets of source items should be inserted + (but does not actually add them to the mapping). + + The result is a vector of pairs, each pair representing a tuple (start, + items), where items is a vector containing the (sorted) source items that + should be inserted at that proxy model location. +*/ +QVector<QPair<int, QVector<int > > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items_to_add( + const QVector<int> &proxy_to_source, const QVector<int> &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const +{ + Q_Q(const QSortFilterProxyModel); + QVector<QPair<int, QVector<int> > > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int proxy_low = 0; + int proxy_item = 0; + int source_items_index = 0; + QVector<int> source_items_in_interval; + bool compare = (orient == Qt::Vertical && source_sort_column >= 0 && dynamic_sortfilter); + while (source_items_index < source_items.size()) { + source_items_in_interval.clear(); + int first_new_source_item = source_items.at(source_items_index); + source_items_in_interval.append(first_new_source_item); + ++source_items_index; + + // Find proxy item at which insertion should be started + int proxy_high = proxy_to_source.size() - 1; + QModelIndex i1 = compare ? model->index(first_new_source_item, source_sort_column, source_parent) : QModelIndex(); + while (proxy_low <= proxy_high) { + proxy_item = (proxy_low + proxy_high) / 2; + if (compare) { + QModelIndex i2 = model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } else { + if (first_new_source_item < proxy_to_source.at(proxy_item)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } + } + proxy_item = proxy_low; + + // Find the sequence of new source items that should be inserted here + if (proxy_item >= proxy_to_source.size()) { + for ( ; source_items_index < source_items.size(); ++source_items_index) + source_items_in_interval.append(source_items.at(source_items_index)); + } else { + i1 = compare ? model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent) : QModelIndex(); + for ( ; source_items_index < source_items.size(); ++source_items_index) { + int new_source_item = source_items.at(source_items_index); + if (compare) { + QModelIndex i2 = model->index(new_source_item, source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + break; + } else { + if (proxy_to_source.at(proxy_item) < new_source_item) + break; + } + source_items_in_interval.append(new_source_item); + } + } + + // Add interval to result + proxy_intervals.append(QPair<int, QVector<int> >(proxy_item, source_items_in_interval)); + } + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, inserts the given \a source_items into this proxy model. + The source items are inserted in intervals (based on some sorted order), so + that the proper rows/columnsInserted(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::insert_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (source_parent is not mapped) + + QVector<QPair<int, QVector<int> > > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items_to_add( + proxy_to_source, source_items, source_parent, orient); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair<int, QVector<int> > interval = proxy_intervals.at(i); + int proxy_start = interval.first; + QVector<int> source_items = interval.second; + int proxy_end = proxy_start + source_items.size() - 1; + + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginInsertRows(proxy_parent, proxy_start, proxy_end); + else + q->beginInsertColumns(proxy_parent, proxy_start, proxy_end); + } + + for (int i = 0; i < source_items.size(); ++i) + proxy_to_source.insert(proxy_start + i, source_items.at(i)); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endInsertRows(); + else + q->endInsertColumns(); + } + } +} + +/*! + \internal + + Handles source model items insertion (columnsInserted(), rowsInserted()). + Determines + 1) which of the inserted items to also insert into proxy model (filtering), + 2) where to insert the items into the proxy model (sorting), + then inserts those items. + The items are inserted into the proxy model in intervals (based on + sorted order), so that the proper rows/columnsInserted(start, end) + signals will be generated. +*/ +void QSortFilterProxyModelPrivate::source_items_inserted( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + if (!can_create_mapping(source_parent)) + return; + it = create_mapping(source_parent); + Mapping *m = it.value(); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (m->source_rows.count() > 0) { + q->beginInsertRows(proxy_parent, 0, m->source_rows.count() - 1); + q->endInsertRows(); + } + if (m->source_columns.count() > 0) { + q->beginInsertColumns(proxy_parent, 0, m->source_columns.count() - 1); + q->endInsertColumns(); + } + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + int delta_item_count = end - start + 1; + int old_item_count = source_to_proxy.size(); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, false); + + // Expand source-to-proxy mapping to account for new items + if (start < 0 || start > source_to_proxy.size()) { + qWarning("QSortFilterProxyModel: invalid inserted rows reported by source model"); + remove_from_mapping(source_parent); + return; + } + source_to_proxy.insert(start, delta_item_count, -1); + + if (start < old_item_count) { + // Adjust existing "stale" indexes in proxy-to-source mapping + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) + proxy_to_source.replace(proxy_item, source_item + delta_item_count); + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + } + + // Figure out which items to add to mapping based on filter + QVector<int> source_items; + for (int i = start; i <= end; ++i) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(i, source_parent) + : q->filterAcceptsColumn(i, source_parent)) { + source_items.append(i); + } + } + + if (model->rowCount(source_parent) == delta_item_count) { + // Items were inserted where there were none before. + // If it was new rows make sure to create mappings for columns so that a + // valid mapping can be retrieved later and vice-versa. + + QVector<int> &orthogonal_proxy_to_source = (orient == Qt::Horizontal) ? m->source_rows : m->source_columns; + QVector<int> &orthogonal_source_to_proxy = (orient == Qt::Horizontal) ? m->proxy_rows : m->proxy_columns; + + if (orthogonal_source_to_proxy.isEmpty()) { + const int ortho_end = (orient == Qt::Horizontal) ? model->rowCount(source_parent) : model->columnCount(source_parent); + + orthogonal_source_to_proxy.resize(ortho_end); + + for (int ortho_item = 0; ortho_item < ortho_end; ++ortho_item) { + if ((orient == Qt::Horizontal) ? q->filterAcceptsRow(ortho_item, source_parent) + : q->filterAcceptsColumn(ortho_item, source_parent)) { + orthogonal_proxy_to_source.append(ortho_item); + } + } + if (orient == Qt::Horizontal) { + // We're reacting to columnsInserted, but we've just inserted new rows. Sort them. + sort_source_rows(orthogonal_proxy_to_source, source_parent); + } + build_source_to_proxy_mapping(orthogonal_proxy_to_source, orthogonal_source_to_proxy); + } + } + + // Sort and insert the items + if (orient == Qt::Vertical) // Only sort rows + sort_source_rows(source_items, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, source_items, source_parent, orient); +} + +/*! + \internal + + Handles source model items removal + (columnsAboutToBeRemoved(), rowsAboutToBeRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_about_to_be_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + // figure out which items to remove + QVector<int> source_items_to_remove; + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if ((source_item >= start) && (source_item <= end)) + source_items_to_remove.append(source_item); + } + + remove_source_items(source_to_proxy, proxy_to_source, source_items_to_remove, + source_parent, orient); +} + +/*! + \internal + + Handles source model items removal (columnsRemoved(), rowsRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + if (end >= source_to_proxy.size()) + end = source_to_proxy.size() - 1; + + // Shrink the source-to-proxy mapping to reflect the new item count + int delta_item_count = end - start + 1; + source_to_proxy.remove(start, delta_item_count); + + int proxy_count = proxy_to_source.size(); + if (proxy_count > source_to_proxy.size()) { + // mapping is in an inconsistent state -- redo the whole mapping + qWarning("QSortFilterProxyModel: inconsistent changes reported by source model"); + remove_from_mapping(source_parent); + Q_Q(QSortFilterProxyModel); + q->reset(); + return; + } + + // Adjust "stale" indexes in proxy-to-source mapping + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) { + Q_ASSERT(source_item - delta_item_count >= 0); + proxy_to_source.replace(proxy_item, source_item - delta_item_count); + } + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, true); + +} + + +/*! + \internal + updates the mapping of the children when inserting or removing items +*/ +void QSortFilterProxyModelPrivate::updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove) +{ + // see if any mapped children should be (re)moved + QVector<QPair<QModelIndex, Mapping*> > moved_source_index_mappings; + QVector<QModelIndex>::iterator it2 = parent_mapping->mapped_children.begin(); + for ( ; it2 != parent_mapping->mapped_children.end();) { + const QModelIndex source_child_index = *it2; + const int pos = (orient == Qt::Vertical) + ? source_child_index.row() + : source_child_index.column(); + if (pos < start) { + // not affected + ++it2; + } else if (remove && pos <= end) { + // in the removed interval + it2 = parent_mapping->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + // below the removed items -- recompute the index + QModelIndex new_index; + const int newpos = remove ? pos - delta_item_count : pos + delta_item_count; + if (orient == Qt::Vertical) { + new_index = model->index(newpos, + source_child_index.column(), + source_parent); + } else { + new_index = model->index(source_child_index.row(), + newpos, + source_parent); + } + *it2 = new_index; + ++it2; + + // update mapping + Mapping *cm = source_index_mapping.take(source_child_index); + Q_ASSERT(cm); + // we do not reinsert right away, because the new index might be identical with another, old index + moved_source_index_mappings.append(QPair<QModelIndex, Mapping*>(new_index, cm)); + } + } + + // reinsert moved, mapped indexes + QVector<QPair<QModelIndex, Mapping*> >::iterator it = moved_source_index_mappings.begin(); + for (; it != moved_source_index_mappings.end(); ++it) { +#ifdef QT_STRICT_ITERATORS + source_index_mapping.insert((*it).first, (*it).second); + (*it).second->map_iter = source_index_mapping.constFind((*it).first); +#else + (*it).second->map_iter = source_index_mapping.insert((*it).first, (*it).second); +#endif + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::proxy_item_range( + const QVector<int> &source_to_proxy, const QVector<int> &source_items, + int &proxy_low, int &proxy_high) const +{ + proxy_low = INT_MAX; + proxy_high = INT_MIN; + for (int i = 0; i < source_items.count(); ++i) { + int proxy_item = source_to_proxy.at(source_items.at(i)); + Q_ASSERT(proxy_item != -1); + if (proxy_item < proxy_low) + proxy_low = proxy_item; + if (proxy_item > proxy_high) + proxy_high = proxy_item; + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::build_source_to_proxy_mapping( + const QVector<int> &proxy_to_source, QVector<int> &source_to_proxy) const +{ + source_to_proxy.fill(-1); + int proxy_count = proxy_to_source.size(); + for (int i = 0; i < proxy_count; ++i) + source_to_proxy[proxy_to_source.at(i)] = i; +} + +/*! + \internal + + Maps the persistent proxy indexes to source indexes and + returns the list of source indexes. +*/ +QModelIndexPairList QSortFilterProxyModelPrivate::store_persistent_indexes() +{ + Q_Q(QSortFilterProxyModel); + QModelIndexPairList source_indexes; + foreach (QPersistentModelIndexData *data, persistent.indexes) { + QModelIndex proxy_index = data->index; + QModelIndex source_index = q->mapToSource(proxy_index); + source_indexes.append(qMakePair(proxy_index, QPersistentModelIndex(source_index))); + } + return source_indexes; +} + +/*! + \internal + + Maps \a source_indexes to proxy indexes and stores those + as persistent indexes. +*/ +void QSortFilterProxyModelPrivate::update_persistent_indexes( + const QModelIndexPairList &source_indexes) +{ + Q_Q(QSortFilterProxyModel); + QModelIndexList from, to; + for (int i = 0; i < source_indexes.count(); ++i) { + QModelIndex source_index = source_indexes.at(i).second; + QModelIndex old_proxy_index = source_indexes.at(i).first; + create_mapping(source_index.parent()); + QModelIndex proxy_index = q->mapFromSource(source_index); + from << old_proxy_index; + to << proxy_index; + } + q->changePersistentIndexList(from, to); +} + + +/*! + \internal + + Updates the proxy model (adds/removes rows) based on the + new filter. +*/ +void QSortFilterProxyModelPrivate::filter_changed(const QModelIndex &source_parent) +{ + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) + return; + Mapping *m = it.value(); + QSet<int> rows_removed = handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical); + QSet<int> columns_removed = handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal); + QVector<QModelIndex>::iterator it2 = m->mapped_children.end(); + while (it2 != m->mapped_children.begin()) { + --it2; + const QModelIndex source_child_index = *it2; + if (rows_removed.contains(source_child_index.row()) || columns_removed.contains(source_child_index.column())) { + it2 = m->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + filter_changed(source_child_index); + } + } +} + +/*! + \internal + returns the removed items indexes +*/ +QSet<int> QSortFilterProxyModelPrivate::handle_filter_changed( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + // Figure out which mapped items to remove + QVector<int> source_items_remove; + for (int i = 0; i < proxy_to_source.count(); ++i) { + const int source_item = proxy_to_source.at(i); + if ((orient == Qt::Vertical) + ? !q->filterAcceptsRow(source_item, source_parent) + : !q->filterAcceptsColumn(source_item, source_parent)) { + // This source item does not satisfy the filter, so it must be removed + source_items_remove.append(source_item); + } + } + // Figure out which non-mapped items to insert + QVector<int> source_items_insert; + int source_count = source_to_proxy.size(); + for (int source_item = 0; source_item < source_count; ++source_item) { + if (source_to_proxy.at(source_item) == -1) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(source_item, source_parent) + : q->filterAcceptsColumn(source_item, source_parent)) { + // This source item satisfies the filter, so it must be added + source_items_insert.append(source_item); + } + } + } + if (!source_items_remove.isEmpty() || !source_items_insert.isEmpty()) { + // Do item removal and insertion + remove_source_items(source_to_proxy, proxy_to_source, + source_items_remove, source_parent, orient); + if (orient == Qt::Vertical) + sort_source_rows(source_items_insert, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, + source_items_insert, source_parent, orient); + } + return qVectorToSet(source_items_remove); +} + +void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right) +{ + Q_Q(QSortFilterProxyModel); + if (!source_top_left.isValid() || !source_bottom_right.isValid()) + return; + QModelIndex source_parent = source_top_left.parent(); + IndexMap::const_iterator it = source_index_mapping.find(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + Mapping *m = it.value(); + + // Figure out how the source changes affect us + QVector<int> source_rows_remove; + QVector<int> source_rows_insert; + QVector<int> source_rows_change; + QVector<int> source_rows_resort; + int end = qMin(source_bottom_right.row(), m->proxy_rows.count() - 1); + for (int source_row = source_top_left.row(); source_row <= end; ++source_row) { + if (dynamic_sortfilter) { + if (m->proxy_rows.at(source_row) != -1) { + if (!q->filterAcceptsRow(source_row, source_parent)) { + // This source row no longer satisfies the filter, so it must be removed + source_rows_remove.append(source_row); + } else if (source_sort_column >= source_top_left.column() && source_sort_column <= source_bottom_right.column()) { + // This source row has changed in a way that may affect sorted order + source_rows_resort.append(source_row); + } else { + // This row has simply changed, without affecting filtering nor sorting + source_rows_change.append(source_row); + } + } else { + if (!itemsBeingRemoved.contains(source_parent, source_row) && q->filterAcceptsRow(source_row, source_parent)) { + // This source row now satisfies the filter, so it must be added + source_rows_insert.append(source_row); + } + } + } else { + if (m->proxy_rows.at(source_row) != -1) + source_rows_change.append(source_row); + } + } + + if (!source_rows_remove.isEmpty()) { + remove_source_items(m->proxy_rows, m->source_rows, + source_rows_remove, source_parent, Qt::Vertical); + QSet<int> source_rows_remove_set = qVectorToSet(source_rows_remove); + QVector<QModelIndex>::iterator it = m->mapped_children.end(); + while (it != m->mapped_children.begin()) { + --it; + const QModelIndex source_child_index = *it; + if (source_rows_remove_set.contains(source_child_index.row())) { + it = m->mapped_children.erase(it); + remove_from_mapping(source_child_index); + } + } + } + + if (!source_rows_resort.isEmpty()) { + // Re-sort the rows of this level + QList<QPersistentModelIndex> parents; + parents << q->mapFromSource(source_parent); + emit q->layoutAboutToBeChanged(parents); + QModelIndexPairList source_indexes = store_persistent_indexes(); + remove_source_items(m->proxy_rows, m->source_rows, source_rows_resort, + source_parent, Qt::Vertical, false); + sort_source_rows(source_rows_resort, source_parent); + insert_source_items(m->proxy_rows, m->source_rows, source_rows_resort, + source_parent, Qt::Vertical, false); + update_persistent_indexes(source_indexes); + emit q->layoutChanged(parents); + // Make sure we also emit dataChanged for the rows + source_rows_change += source_rows_resort; + } + + if (!source_rows_change.isEmpty()) { + // Find the proxy row range + int proxy_start_row; + int proxy_end_row; + proxy_item_range(m->proxy_rows, source_rows_change, + proxy_start_row, proxy_end_row); + // ### Find the proxy column range also + if (proxy_end_row >= 0) { + // the row was accepted, but some columns might still be filtered out + int source_left_column = source_top_left.column(); + while (source_left_column < source_bottom_right.column() + && m->proxy_columns.at(source_left_column) == -1) + ++source_left_column; + const QModelIndex proxy_top_left = create_index( + proxy_start_row, m->proxy_columns.at(source_left_column), it); + int source_right_column = source_bottom_right.column(); + while (source_right_column > source_top_left.column() + && m->proxy_columns.at(source_right_column) == -1) + --source_right_column; + const QModelIndex proxy_bottom_right = create_index( + proxy_end_row, m->proxy_columns.at(source_right_column), it); + emit q->dataChanged(proxy_top_left, proxy_bottom_right); + } + } + + if (!source_rows_insert.isEmpty()) { + sort_source_rows(source_rows_insert, source_parent); + insert_source_items(m->proxy_rows, m->source_rows, + source_rows_insert, source_parent, Qt::Vertical); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceHeaderDataChanged(Qt::Orientation orientation, + int start, int end) +{ + Q_Q(QSortFilterProxyModel); + Mapping *m = create_mapping(QModelIndex()).value(); + int proxy_start = (orientation == Qt::Vertical + ? m->proxy_rows.at(start) + : m->proxy_columns.at(start)); + int proxy_end = (orientation == Qt::Vertical + ? m->proxy_rows.at(end) + : m->proxy_columns.at(end)); + emit q->headerDataChanged(orientation, proxy_start, proxy_end); +} + +void QSortFilterProxyModelPrivate::_q_sourceAboutToBeReset() +{ + Q_Q(QSortFilterProxyModel); + q->beginResetModel(); +} + +void QSortFilterProxyModelPrivate::_q_sourceReset() +{ + Q_Q(QSortFilterProxyModel); + invalidatePersistentIndexes(); + _q_clearMapping(); + // All internal structures are deleted in clear() + q->endResetModel(); + update_source_sort_column(); + if (dynamic_sortfilter) + sort(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents) +{ + Q_Q(QSortFilterProxyModel); + saved_persistent_indexes.clear(); + + QList<QPersistentModelIndex> parents; + foreach (const QPersistentModelIndex &parent, sourceParents) { + if (!parent.isValid()) { + parents << QModelIndex(); + continue; + } + const QModelIndex mappedParent = q->mapFromSource(parent); + // Might be filtered out. + if (mappedParent.isValid()) + parents << mappedParent; + } + + // All parents filtered out. + if (!sourceParents.isEmpty() && parents.isEmpty()) + return; + + emit q->layoutAboutToBeChanged(parents); + if (persistent.indexes.isEmpty()) + return; + + saved_persistent_indexes = store_persistent_indexes(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents) +{ + Q_Q(QSortFilterProxyModel); + + // Optimize: We only actually have to clear the mapping related to the contents of + // sourceParents, not everything. + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + + update_persistent_indexes(saved_persistent_indexes); + saved_persistent_indexes.clear(); + + if (dynamic_sortfilter && update_source_sort_column()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + QList<QPersistentModelIndex> parents; + foreach (const QPersistentModelIndex &parent, sourceParents) { + if (!parent.isValid()) { + parents << QModelIndex(); + continue; + } + const QModelIndex mappedParent = q->mapFromSource(parent); + if (mappedParent.isValid()) + parents << mappedParent; + } + + if (!sourceParents.isEmpty() && parents.isEmpty()) + return; + + emit q->layoutChanged(parents); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + //Force the creation of a mapping now, even if its empty. + //We need it because the proxy can be acessed at the moment it emits rowsAboutToBeInserted in insert_source_items + if (can_create_mapping(source_parent)) + create_mapping(source_parent); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsInserted( + const QModelIndex &source_parent, int start, int end) +{ + source_items_inserted(source_parent, start, end, Qt::Vertical); + if (update_source_sort_column() && dynamic_sortfilter) //previous call to update_source_sort_column may fail if the model has no column. + sort(); // now it should succeed so we need to make sure to sort again +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved( + const QModelIndex &source_parent, int start, int end) +{ + itemsBeingRemoved = QRowsRemoval(source_parent, start, end); + source_items_about_to_be_removed(source_parent, start, end, + Qt::Vertical); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsRemoved( + const QModelIndex &source_parent, int start, int end) +{ + itemsBeingRemoved = QRowsRemoval(); + source_items_removed(source_parent, start, end, Qt::Vertical); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeMoved( + const QModelIndex &sourceParent, int /* sourceStart */, int /* sourceEnd */, const QModelIndex &destParent, int /* dest */) +{ + Q_Q(QSortFilterProxyModel); + // Because rows which are contiguous in the source model might not be contiguous + // in the proxy due to sorting, the best thing we can do here is be specific about what + // parents are having their children changed. + // Optimize: Emit move signals if the proxy is not sorted. Will need to account for rows + // being filtered out though. + + saved_persistent_indexes.clear(); + + QList<QPersistentModelIndex> parents; + parents << q->mapFromSource(sourceParent); + if (sourceParent != destParent) + parents << q->mapFromSource(destParent); + emit q->layoutAboutToBeChanged(parents); + if (persistent.indexes.isEmpty()) + return; + saved_persistent_indexes = store_persistent_indexes(); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsMoved( + const QModelIndex &sourceParent, int /* sourceStart */, int /* sourceEnd */, const QModelIndex &destParent, int /* dest */) +{ + Q_Q(QSortFilterProxyModel); + + // Optimize: We only need to clear and update the persistent indexes which are children of + // sourceParent or destParent + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + + update_persistent_indexes(saved_persistent_indexes); + saved_persistent_indexes.clear(); + + if (dynamic_sortfilter && update_source_sort_column()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + QList<QPersistentModelIndex> parents; + parents << q->mapFromSource(sourceParent); + if (sourceParent != destParent) + parents << q->mapFromSource(destParent); + emit q->layoutChanged(parents); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + //Force the creation of a mapping now, even if its empty. + //We need it because the proxy can be acessed at the moment it emits columnsAboutToBeInserted in insert_source_items + if (can_create_mapping(source_parent)) + create_mapping(source_parent); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_Q(const QSortFilterProxyModel); + source_items_inserted(source_parent, start, end, Qt::Horizontal); + + if (source_parent.isValid()) + return; //we sort according to the root column only + if (source_sort_column == -1) { + //we update the source_sort_column depending on the proxy_sort_column + if (update_source_sort_column() && dynamic_sortfilter) + sort(); + } else { + if (start <= source_sort_column) + source_sort_column += end - start + 1; + + proxy_sort_column = q->mapFromSource(model->index(0,source_sort_column, source_parent)).column(); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved( + const QModelIndex &source_parent, int start, int end) +{ + source_items_about_to_be_removed(source_parent, start, end, + Qt::Horizontal); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsRemoved( + const QModelIndex &source_parent, int start, int end) +{ + Q_Q(const QSortFilterProxyModel); + source_items_removed(source_parent, start, end, Qt::Horizontal); + + if (source_parent.isValid()) + return; //we sort according to the root column only + if (start <= source_sort_column) { + if (end < source_sort_column) + source_sort_column -= end - start + 1; + else + source_sort_column = -1; + } + + proxy_sort_column = q->mapFromSource(model->index(0,source_sort_column, source_parent)).column(); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeMoved( + const QModelIndex &sourceParent, int /* sourceStart */, int /* sourceEnd */, const QModelIndex &destParent, int /* dest */) +{ + Q_Q(QSortFilterProxyModel); + + saved_persistent_indexes.clear(); + + QList<QPersistentModelIndex> parents; + parents << q->mapFromSource(sourceParent); + if (sourceParent != destParent) + parents << q->mapFromSource(destParent); + emit q->layoutAboutToBeChanged(parents); + + if (persistent.indexes.isEmpty()) + return; + saved_persistent_indexes = store_persistent_indexes(); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsMoved( + const QModelIndex &sourceParent, int /* sourceStart */, int /* sourceEnd */, const QModelIndex &destParent, int /* dest */) +{ + Q_Q(QSortFilterProxyModel); + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + + update_persistent_indexes(saved_persistent_indexes); + saved_persistent_indexes.clear(); + + if (dynamic_sortfilter && update_source_sort_column()) { + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + QList<QPersistentModelIndex> parents; + parents << q->mapFromSource(sourceParent); + if (sourceParent != destParent) + parents << q->mapFromSource(destParent); + emit q->layoutChanged(parents); +} + +/*! + \since 4.1 + \class QSortFilterProxyModel + \brief The QSortFilterProxyModel class provides support for sorting and + filtering data passed between another model and a view. + + \ingroup model-view + \inmodule QtCore + + QSortFilterProxyModel can be used for sorting items, filtering out items, + or both. The model transforms the structure of a source model by mapping + the model indexes it supplies to new indexes, corresponding to different + locations, for views to use. This approach allows a given source model to + be restructured as far as views are concerned without requiring any + transformations on the underlying data, and without duplicating the data in + memory. + + Let's assume that we want to sort and filter the items provided by a custom + model. The code to set up the model and the view, \e without sorting and + filtering, would look like this: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 1 + + To add sorting and filtering support to \c MyItemModel, we need to create + a QSortFilterProxyModel, call setSourceModel() with the \c MyItemModel as + argument, and install the QSortFilterProxyModel on the view: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 0 + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 2 + + At this point, neither sorting nor filtering is enabled; the original data + is displayed in the view. Any changes made through the + QSortFilterProxyModel are applied to the original model. + + The QSortFilterProxyModel acts as a wrapper for the original model. If you + need to convert source \l{QModelIndex}es to sorted/filtered model indexes + or vice versa, use mapToSource(), mapFromSource(), mapSelectionToSource(), + and mapSelectionFromSource(). + + \note By default, the model dynamically re-sorts and re-filters data + whenever the original model changes. This behavior can be changed by + setting the \l{QSortFilterProxyModel::dynamicSortFilter}{dynamicSortFilter} + property. + + The \l{itemviews/basicsortfiltermodel}{Basic Sort/Filter Model} and + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} examples + illustrate how to use QSortFilterProxyModel to perform basic sorting and + filtering and how to subclass it to implement custom behavior. + + \section1 Sorting + + QTableView and QTreeView have a + \l{QTreeView::sortingEnabled}{sortingEnabled} property that controls + whether the user can sort the view by clicking the view's horizontal + header. For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 3 + + When this feature is on (the default is off), clicking on a header section + sorts the items according to that column. By clicking repeatedly, the user + can alternate between ascending and descending order. + + \image qsortfilterproxymodel-sorting.png A sorted QTreeView + + Behind the scene, the view calls the sort() virtual function on the model + to reorder the data in the model. To make your data sortable, you can + either implement sort() in your model, or use a QSortFilterProxyModel to + wrap your model -- QSortFilterProxyModel provides a generic sort() + reimplementation that operates on the sortRole() (Qt::DisplayRole by + default) of the items and that understands several data types, including + \c int, QString, and QDateTime. For hierarchical models, sorting is applied + recursively to all child items. String comparisons are case sensitive by + default; this can be changed by setting the \l{QSortFilterProxyModel::} + {sortCaseSensitivity} property. + + Custom sorting behavior is achieved by subclassing + QSortFilterProxyModel and reimplementing lessThan(), which is + used to compare items. For example: + + \snippet examples/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 5 + + (This code snippet comes from the + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + example.) + + An alternative approach to sorting is to disable sorting on the view and to + impose a certain order to the user. This is done by explicitly calling + sort() with the desired column and order as arguments on the + QSortFilterProxyModel (or on the original model if it implements sort()). + For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 4 + + QSortFilterProxyModel can be sorted by column -1, in which case it returns + to the sort order of the underlying source model. + + \section1 Filtering + + In addition to sorting, QSortFilterProxyModel can be used to hide items + that do not match a certain filter. The filter is specified using a QRegExp + object and is applied to the filterRole() (Qt::DisplayRole by default) of + each item, for a given column. The QRegExp object can be used to match a + regular expression, a wildcard pattern, or a fixed string. For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 5 + + For hierarchical models, the filter is applied recursively to all children. + If a parent item doesn't match the filter, none of its children will be + shown. + + A common use case is to let the user specify the filter regexp, wildcard + pattern, or fixed string in a QLineEdit and to connect the + \l{QLineEdit::textChanged()}{textChanged()} signal to setFilterRegExp(), + setFilterWildcard(), or setFilterFixedString() to reapply the filter. + + Custom filtering behavior can be achieved by reimplementing the + filterAcceptsRow() and filterAcceptsColumn() functions. For + example (from the \l{itemviews/customsortfiltermodel} + {Custom Sort/Filter Model} example), the following implementation ignores + the \l{QSortFilterProxyModel::filterKeyColumn}{filterKeyColumn} property + and performs filtering on columns 0, 1, and 2: + + \snippet examples/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 3 + + (This code snippet comes from the + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + example.) + + If you are working with large amounts of filtering and have to invoke + invalidateFilter() repeatedly, using reset() may be more efficient, + depending on the implementation of your model. However, reset() returns the + proxy model to its original state, losing selection information, and will + cause the proxy model to be repopulated. + + \section1 Subclassing + + Since QAbstractProxyModel and its subclasses are derived from + QAbstractItemModel, much of the same advice about subclassing normal models + also applies to proxy models. In addition, it is worth noting that many of + the default implementations of functions in this class are written so that + they call the equivalent functions in the relevant source model. This + simple proxying mechanism may need to be overridden for source models with + more complex behavior; for example, if the source model provides a custom + hasChildren() implementation, you should also provide one in the proxy + model. + + \note Some general guidelines for subclassing models are available in the + \l{Model Subclassing Reference}. + + \sa QAbstractProxyModel, QAbstractItemModel, {Model/View Programming}, + {Basic Sort/Filter Model Example}, {Custom Sort/Filter Model Example}, QIdentityProxyModel +*/ + +/*! + Constructs a sorting filter model with the given \a parent. +*/ + +QSortFilterProxyModel::QSortFilterProxyModel(QObject *parent) + : QAbstractProxyModel(*new QSortFilterProxyModelPrivate, parent) +{ + Q_D(QSortFilterProxyModel); + d->proxy_sort_column = d->source_sort_column = -1; + d->sort_order = Qt::AscendingOrder; + d->sort_casesensitivity = Qt::CaseSensitive; + d->sort_role = Qt::DisplayRole; + d->sort_localeaware = false; + d->filter_column = 0; + d->filter_role = Qt::DisplayRole; + d->dynamic_sortfilter = true; + connect(this, SIGNAL(modelReset()), this, SLOT(_q_clearMapping())); +} + +/*! + Destroys this sorting filter model. +*/ +QSortFilterProxyModel::~QSortFilterProxyModel() +{ + Q_D(QSortFilterProxyModel); + qDeleteAll(d->source_index_mapping); + d->source_index_mapping.clear(); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + Q_D(QSortFilterProxyModel); + + beginResetModel(); + + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + + disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); + + disconnect(d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); + + disconnect(d->model, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int))); + + disconnect(d->model, SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>)), + this, SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>))); + + disconnect(d->model, SIGNAL(layoutChanged(QList<QPersistentModelIndex>)), + this, SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>))); + + disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + QAbstractProxyModel::setSourceModel(sourceModel); + + connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + + connect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); + + connect(d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); + + connect(d->model, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); + + connect(d->model, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int))); + + connect(d->model, SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>)), + this, SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>))); + + connect(d->model, SIGNAL(layoutChanged(QList<QPersistentModelIndex>)), + this, SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>))); + + connect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); + connect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + d->_q_clearMapping(); + endResetModel(); + if (d->update_source_sort_column() && d->dynamic_sortfilter) + d->sort(); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + if (row < 0 || column < 0) + return QModelIndex(); + + QModelIndex source_parent = mapToSource(parent); // parent is already mapped at this point + IndexMap::const_iterator it = d->create_mapping(source_parent); // but make sure that the children are mapped + if (it.value()->source_rows.count() <= row || it.value()->source_columns.count() <= column) + return QModelIndex(); + + return d->create_index(row, column, it); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::parent(const QModelIndex &child) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(child)) + return QModelIndex(); + IndexMap::const_iterator it = d->index_to_iterator(child); + Q_ASSERT(it != d->source_index_mapping.constEnd()); + QModelIndex source_parent = it.key(); + QModelIndex proxy_parent = mapFromSource(source_parent); + return proxy_parent; +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_rows.count(); +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_columns.count(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + if (!d->model->hasChildren(source_parent)) + return false; + + if (d->model->canFetchMore(source_parent)) + return true; //we assume we might have children that can be fetched + + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + return m->source_rows.count() != 0 && m->source_columns.count() != 0; +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QVariant(); + return d->model->data(source_index, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return false; + return d->model->setData(source_index, value, role); +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::headerData(section, orientation, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return QVariant(); + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return QVariant(); + source_section = it.value()->source_columns.at(section); + } + return d->model->headerData(source_section, orientation, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::setHeaderData(section, orientation, value, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return false; + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return false; + source_section = it.value()->source_columns.at(section); + } + return d->model->setHeaderData(source_section, orientation, value, role); +} + +/*! + \reimp +*/ +QMimeData *QSortFilterProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndexList source_indexes; + for (int i = 0; i < indexes.count(); ++i) + source_indexes << mapToSource(indexes.at(i)); + return d->model->mimeData(source_indexes); +} + +/*! + \reimp +*/ +QStringList QSortFilterProxyModel::mimeTypes() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->mimeTypes(); +} + +/*! + \reimp +*/ +Qt::DropActions QSortFilterProxyModel::supportedDropActions() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->supportedDropActions(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if ((row == -1) && (column == -1)) + return d->model->dropMimeData(data, action, -1, -1, mapToSource(parent)); + int source_destination_row = -1; + int source_destination_column = -1; + QModelIndex source_parent; + if (row == rowCount(parent)) { + source_parent = mapToSource(parent); + source_destination_row = d->model->rowCount(source_parent); + } else { + QModelIndex proxy_index = index(row, column, parent); + QModelIndex source_index = mapToSource(proxy_index); + source_destination_row = source_index.row(); + source_destination_column = source_index.column(); + source_parent = source_index.parent(); + } + return d->model->dropMimeData(data, action, source_destination_row, + source_destination_column, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row > m->source_rows.count()) + return false; + int source_row = (row >= m->source_rows.count() + ? m->source_rows.count() + : m->source_rows.at(row)); + return d->model->insertRows(source_row, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0|| count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column > m->source_columns.count()) + return false; + int source_column = (column >= m->source_columns.count() + ? m->source_columns.count() + : m->source_columns.at(column)); + return d->model->insertColumns(source_column, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row + count > m->source_rows.count()) + return false; + if ((count == 1) + || ((d->source_sort_column < 0) && (m->proxy_rows.count() == m->source_rows.count()))) { + int source_row = m->source_rows.at(row); + return d->model->removeRows(source_row, count, source_parent); + } + // remove corresponding source intervals + // ### if this proves to be slow, we can switch to single-row removal + QVector<int> rows; + for (int i = row; i < row + count; ++i) + rows.append(m->source_rows.at(i)); + qSort(rows.begin(), rows.end()); + + int pos = rows.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = rows.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (rows.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeRows(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column + count > m->source_columns.count()) + return false; + if ((count == 1) || (m->proxy_columns.count() == m->source_columns.count())) { + int source_column = m->source_columns.at(column); + return d->model->removeColumns(source_column, count, source_parent); + } + // remove corresponding source intervals + QVector<int> columns; + for (int i = column; i < column + count; ++i) + columns.append(m->source_columns.at(i)); + + int pos = columns.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = columns.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (columns.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeColumns(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + d->model->fetchMore(source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + return d->model->canFetchMore(source_parent); +} + +/*! + \reimp +*/ +Qt::ItemFlags QSortFilterProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index; + if (d->indexValid(index)) + source_index = mapToSource(index); + return d->model->flags(source_index); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::buddy(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(index)) + return QModelIndex(); + QModelIndex source_index = mapToSource(index); + QModelIndex source_buddy = d->model->buddy(source_index); + if (source_index == source_buddy) + return index; + return mapFromSource(source_buddy); +} + +/*! + \reimp +*/ +QModelIndexList QSortFilterProxyModel::match(const QModelIndex &start, int role, + const QVariant &value, int hits, + Qt::MatchFlags flags) const +{ + return QAbstractProxyModel::match(start, role, value, hits, flags); +} + +/*! + \reimp +*/ +QSize QSortFilterProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QSize(); + return d->model->span(source_index); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QSortFilterProxyModel); + if (d->dynamic_sortfilter && d->proxy_sort_column == column && d->sort_order == order) + return; + d->sort_order = order; + d->proxy_sort_column = column; + d->update_source_sort_column(); + d->sort(); +} + +/*! + \since 4.5 + \brief the column currently used for sorting + + This returns the most recently used sort column. +*/ +int QSortFilterProxyModel::sortColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_sort_column; +} + +/*! + \since 4.5 + \brief the order currently used for sorting + + This returns the most recently used sort order. +*/ +Qt::SortOrder QSortFilterProxyModel::sortOrder() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_order; +} + +/*! + \property QSortFilterProxyModel::filterRegExp + \brief the QRegExp used to filter the contents of the source model + + Setting this property overwrites the current + \l{QSortFilterProxyModel::filterCaseSensitivity}{filterCaseSensitivity}. + By default, the QRegExp is an empty string matching all contents. + + If no QRegExp or an empty string is set, everything in the source model + will be accepted. + + \sa filterCaseSensitivity, setFilterWildcard(), setFilterFixedString() +*/ +QRegExp QSortFilterProxyModel::filterRegExp() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp; +} + +void QSortFilterProxyModel::setFilterRegExp(const QRegExp ®Exp) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp = regExp; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterKeyColumn + \brief the column where the key used to filter the contents of the + source model is read from. + + The default value is 0. If the value is -1, the keys will be read + from all columns. +*/ +int QSortFilterProxyModel::filterKeyColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_column; +} + +void QSortFilterProxyModel::setFilterKeyColumn(int column) +{ + Q_D(QSortFilterProxyModel); + d->filter_column = column; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterCaseSensitivity + + \brief the case sensitivity of the QRegExp pattern used to filter the + contents of the source model + + By default, the filter is case sensitive. + + \sa filterRegExp, sortCaseSensitivity +*/ +Qt::CaseSensitivity QSortFilterProxyModel::filterCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp.caseSensitivity(); +} + +void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (cs == d->filter_regexp.caseSensitivity()) + return; + d->filter_regexp.setCaseSensitivity(cs); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortCaseSensitivity + \brief the case sensitivity setting used for comparing strings when sorting + + By default, sorting is case sensitive. + + \sa filterCaseSensitivity, lessThan() +*/ +Qt::CaseSensitivity QSortFilterProxyModel::sortCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_casesensitivity; +} + +void QSortFilterProxyModel::setSortCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_casesensitivity == cs) + return; + + d->sort_casesensitivity = cs; + d->sort(); +} + +/*! + \since 4.3 + \property QSortFilterProxyModel::isSortLocaleAware + \brief the local aware setting used for comparing strings when sorting + + By default, sorting is not local aware. + + \sa sortCaseSensitivity, lessThan() +*/ +bool QSortFilterProxyModel::isSortLocaleAware() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_localeaware; +} + +void QSortFilterProxyModel::setSortLocaleAware(bool on) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_localeaware == on) + return; + + d->sort_localeaware = on; + d->sort(); +} + +/*! + \overload + + Sets the regular expression used to filter the contents + of the source model to \a pattern. + + \sa setFilterCaseSensitivity(), setFilterWildcard(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterRegExp(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::RegExp); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the wildcard expression used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterWildcard(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::Wildcard); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the fixed string used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterWildcard(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterFixedString(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::FixedString); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::dynamicSortFilter + \brief whether the proxy model is dynamically sorted and filtered + whenever the contents of the source model change + + Note that you should not update the source model through the proxy + model when dynamicSortFilter is true. For instance, if you set the + proxy model on a QComboBox, then using functions that update the + model, e.g., \l{QComboBox::}{addItem()}, will not work as + expected. An alternative is to set dynamicSortFilter to false and + call \l{QSortFilterProxyModel::}{sort()} after adding items to the + QComboBox. + + The default value is true. +*/ +bool QSortFilterProxyModel::dynamicSortFilter() const +{ + Q_D(const QSortFilterProxyModel); + return d->dynamic_sortfilter; +} + +void QSortFilterProxyModel::setDynamicSortFilter(bool enable) +{ + Q_D(QSortFilterProxyModel); + d->dynamic_sortfilter = enable; + if (enable) + d->sort(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortRole + \brief the item role that is used to query the source model's data when sorting items + + The default value is Qt::DisplayRole. + + \sa lessThan() +*/ +int QSortFilterProxyModel::sortRole() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_role; +} + +void QSortFilterProxyModel::setSortRole(int role) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_role == role) + return; + d->sort_role = role; + d->sort(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::filterRole + \brief the item role that is used to query the source model's data when filtering items + + The default value is Qt::DisplayRole. + + \sa filterAcceptsRow() +*/ +int QSortFilterProxyModel::filterRole() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_role; +} + +void QSortFilterProxyModel::setFilterRole(int role) +{ + Q_D(QSortFilterProxyModel); + if (d->filter_role == role) + return; + d->filter_role = role; + d->filter_changed(); +} + +/*! + \obsolete + + This function is obsolete. Use invalidate() instead. +*/ +void QSortFilterProxyModel::clear() +{ + Q_D(QSortFilterProxyModel); + emit layoutAboutToBeChanged(); + d->_q_clearMapping(); + emit layoutChanged(); +} + +/*! + \since 4.3 + + Invalidates the current sorting and filtering. + + \sa invalidateFilter() +*/ +void QSortFilterProxyModel::invalidate() +{ + Q_D(QSortFilterProxyModel); + emit layoutAboutToBeChanged(); + d->_q_clearMapping(); + emit layoutChanged(); +} + +/*! + \obsolete + + This function is obsolete. Use invalidateFilter() instead. +*/ +void QSortFilterProxyModel::filterChanged() +{ + Q_D(QSortFilterProxyModel); + d->filter_changed(); +} + +/*! + \since 4.3 + + Invalidates the current filtering. + + This function should be called if you are implementing custom filtering + (e.g. filterAcceptsRow()), and your filter parameters have changed. + + \sa invalidate() +*/ +void QSortFilterProxyModel::invalidateFilter() +{ + Q_D(QSortFilterProxyModel); + d->filter_changed(); +} + +/*! + Returns true if the value of the item referred to by the given + index \a left is less than the value of the item referred to by + the given index \a right, otherwise returns false. + + This function is used as the < operator when sorting, and handles + the following QVariant types: + + \list + \o QVariant::Int + \o QVariant::UInt + \o QVariant::LongLong + \o QVariant::ULongLong + \o QVariant::Double + \o QVariant::Char + \o QVariant::Date + \o QVariant::Time + \o QVariant::DateTime + \o QVariant::String + \endlist + + Any other type will be converted to a QString using + QVariant::toString(). + + Comparison of \l{QString}s is case sensitive by default; this can + be changed using the \l {QSortFilterProxyModel::sortCaseSensitivity} + {sortCaseSensitivity} property. + + By default, the Qt::DisplayRole associated with the + \l{QModelIndex}es is used for comparisons. This can be changed by + setting the \l {QSortFilterProxyModel::sortRole} {sortRole} property. + + \note The indices passed in correspond to the source model. + + \sa sortRole, sortCaseSensitivity, dynamicSortFilter +*/ +bool QSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + Q_D(const QSortFilterProxyModel); + QVariant l = (left.model() ? left.model()->data(left, d->sort_role) : QVariant()); + QVariant r = (right.model() ? right.model()->data(right, d->sort_role) : QVariant()); + switch (l.userType()) { + case QVariant::Invalid: + return (r.type() != QVariant::Invalid); + case QVariant::Int: + return l.toInt() < r.toInt(); + case QVariant::UInt: + return l.toUInt() < r.toUInt(); + case QVariant::LongLong: + return l.toLongLong() < r.toLongLong(); + case QVariant::ULongLong: + return l.toULongLong() < r.toULongLong(); + case QMetaType::Float: + return l.toFloat() < r.toFloat(); + case QVariant::Double: + return l.toDouble() < r.toDouble(); + case QVariant::Char: + return l.toChar() < r.toChar(); + case QVariant::Date: + return l.toDate() < r.toDate(); + case QVariant::Time: + return l.toTime() < r.toTime(); + case QVariant::DateTime: + return l.toDateTime() < r.toDateTime(); + case QVariant::String: + default: + if (d->sort_localeaware) + return l.toString().localeAwareCompare(r.toString()) < 0; + else + return l.toString().compare(r.toString(), d->sort_casesensitivity) < 0; + } + return false; +} + +/*! + Returns true if the item in the row indicated by the given \a source_row + and \a source_parent should be included in the model; otherwise returns + false. + + The default implementation returns true if the value held by the relevant item + matches the filter string, wildcard string or regular expression. + + \note By default, the Qt::DisplayRole is used to determine if the row + should be accepted or not. This can be changed by setting the + \l{QSortFilterProxyModel::filterRole}{filterRole} property. + + \sa filterAcceptsColumn(), setFilterFixedString(), setFilterRegExp(), setFilterWildcard() +*/ +bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_D(const QSortFilterProxyModel); + if (d->filter_regexp.isEmpty()) + return true; + if (d->filter_column == -1) { + int column_count = d->model->columnCount(source_parent); + for (int column = 0; column < column_count; ++column) { + QModelIndex source_index = d->model->index(source_row, column, source_parent); + QString key = d->model->data(source_index, d->filter_role).toString(); + if (key.contains(d->filter_regexp)) + return true; + } + return false; + } + QModelIndex source_index = d->model->index(source_row, d->filter_column, source_parent); + if (!source_index.isValid()) // the column may not exist + return true; + QString key = d->model->data(source_index, d->filter_role).toString(); + return key.contains(d->filter_regexp); +} + +/*! + Returns true if the item in the column indicated by the given \a source_column + and \a source_parent should be included in the model; otherwise returns false. + + The default implementation returns true if the value held by the relevant item + matches the filter string, wildcard string or regular expression. + + \note By default, the Qt::DisplayRole is used to determine if the row + should be accepted or not. This can be changed by setting the \l + filterRole property. + + \sa filterAcceptsRow(), setFilterFixedString(), setFilterRegExp(), setFilterWildcard() +*/ +bool QSortFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_column); + Q_UNUSED(source_parent); + return true; +} + +/*! + Returns the source model index corresponding to the given \a + proxyIndex from the sorting filter model. + + \sa mapFromSource() +*/ +QModelIndex QSortFilterProxyModel::mapToSource(const QModelIndex &proxyIndex) const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_to_source(proxyIndex); +} + +/*! + Returns the model index in the QSortFilterProxyModel given the \a + sourceIndex from the source model. + + \sa mapToSource() +*/ +QModelIndex QSortFilterProxyModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + Q_D(const QSortFilterProxyModel); + return d->source_to_proxy(sourceIndex); +} + +/*! + \reimp +*/ +QItemSelection QSortFilterProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const +{ + return QAbstractProxyModel::mapSelectionToSource(proxySelection); +} + +/*! + \reimp +*/ +QItemSelection QSortFilterProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const +{ + return QAbstractProxyModel::mapSelectionFromSource(sourceSelection); +} + +/*! + \fn QObject *QSortFilterProxyModel::parent() const + \internal +*/ + +QT_END_NAMESPACE + +#include "moc_qsortfilterproxymodel.cpp" + +#endif // QT_NO_SORTFILTERPROXYMODEL diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.h b/src/corelib/itemmodels/qsortfilterproxymodel.h new file mode 100644 index 0000000000..c8cd581420 --- /dev/null +++ b/src/corelib/itemmodels/qsortfilterproxymodel.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSORTFILTERPROXYMODEL_H +#define QSORTFILTERPROXYMODEL_H + +#include <QtCore/qabstractproxymodel.h> + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include <QtCore/qregexp.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +class QSortFilterProxyModelPrivate; +class QSortFilterProxyModelLessThan; +class QSortFilterProxyModelGreaterThan; + +class Q_CORE_EXPORT QSortFilterProxyModel : public QAbstractProxyModel +{ + friend class QSortFilterProxyModelLessThan; + friend class QSortFilterProxyModelGreaterThan; + + Q_OBJECT + Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp) + Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn) + Q_PROPERTY(bool dynamicSortFilter READ dynamicSortFilter WRITE setDynamicSortFilter) + Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity) + Q_PROPERTY(Qt::CaseSensitivity sortCaseSensitivity READ sortCaseSensitivity WRITE setSortCaseSensitivity) + Q_PROPERTY(bool isSortLocaleAware READ isSortLocaleAware WRITE setSortLocaleAware) + Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole) + Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole) + +public: + QSortFilterProxyModel(QObject *parent = 0); + ~QSortFilterProxyModel(); + + void setSourceModel(QAbstractItemModel *sourceModel); + + QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; + + QItemSelection mapSelectionToSource(const QItemSelection &proxySelection) const; + QItemSelection mapSelectionFromSource(const QItemSelection &sourceSelection) const; + + QRegExp filterRegExp() const; + void setFilterRegExp(const QRegExp ®Exp); + + int filterKeyColumn() const; + void setFilterKeyColumn(int column); + + Qt::CaseSensitivity filterCaseSensitivity() const; + void setFilterCaseSensitivity(Qt::CaseSensitivity cs); + + Qt::CaseSensitivity sortCaseSensitivity() const; + void setSortCaseSensitivity(Qt::CaseSensitivity cs); + + bool isSortLocaleAware() const; + void setSortLocaleAware(bool on); + + int sortColumn() const; + Qt::SortOrder sortOrder() const; + + bool dynamicSortFilter() const; + void setDynamicSortFilter(bool enable); + + int sortRole() const; + void setSortRole(int role); + + int filterRole() const; + void setFilterRole(int role); + +public Q_SLOTS: + void setFilterRegExp(const QString &pattern); + void setFilterWildcard(const QString &pattern); + void setFilterFixedString(const QString &pattern); + void clear(); + void invalidate(); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + + void filterChanged(); + void invalidateFilter(); + +public: +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role = Qt::EditRole); + + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + void fetchMore(const QModelIndex &parent); + bool canFetchMore(const QModelIndex &parent) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + QModelIndex buddy(const QModelIndex &index) const; + QModelIndexList match(const QModelIndex &start, int role, + const QVariant &value, int hits = 1, + Qt::MatchFlags flags = + Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const; + QSize span(const QModelIndex &index) const; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; +private: + Q_DECLARE_PRIVATE(QSortFilterProxyModel) + Q_DISABLE_COPY(QSortFilterProxyModel) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceAboutToBeReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int)) + Q_PRIVATE_SLOT(d_func(), void _q_clearMapping()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SORTFILTERPROXYMODEL + +#endif // QSORTFILTERPROXYMODEL_H diff --git a/src/corelib/itemmodels/qstringlistmodel.cpp b/src/corelib/itemmodels/qstringlistmodel.cpp new file mode 100644 index 0000000000..5e72977d20 --- /dev/null +++ b/src/corelib/itemmodels/qstringlistmodel.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + A simple model that uses a QStringList as its data source. +*/ + +#include "qstringlistmodel.h" + +#include <QtCore/qvector.h> + +#ifndef QT_NO_STRINGLISTMODEL + +QT_BEGIN_NAMESPACE + +/*! + \class QStringListModel + \brief The QStringListModel class provides a model that supplies strings to views. + + \ingroup model-view + \inmodule QtCore + + QStringListModel is an editable model that can be used for simple + cases where you need to display a number of strings in a view + widget, such as a QListView or a QComboBox. + + The model provides all the standard functions of an editable + model, representing the data in the string list as a model with + one column and a number of rows equal to the number of items in + the list. + + Model indexes corresponding to items are obtained with the + \l{QAbstractListModel::index()}{index()} function, and item flags + are obtained with flags(). Item data is read with the data() + function and written with setData(). The number of rows (and + number of items in the string list) can be found with the + rowCount() function. + + The model can be constructed with an existing string list, or + strings can be set later with the setStringList() convenience + function. Strings can also be inserted in the usual way with the + insertRows() function, and removed with removeRows(). The contents + of the string list can be retrieved with the stringList() + convenience function. + + An example usage of QStringListModel: + + \snippet doc/src/snippets/qstringlistmodel/main.cpp 0 + + \sa QAbstractListModel, QAbstractItemModel, {Model Classes} +*/ + +/*! + Constructs a string list model with the given \a parent. +*/ + +QStringListModel::QStringListModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +/*! + Constructs a string list model containing the specified \a strings + with the given \a parent. +*/ + +QStringListModel::QStringListModel(const QStringList &strings, QObject *parent) + : QAbstractListModel(parent), lst(strings) +{ +} + +/*! + Returns the number of rows in the model. This value corresponds to the + number of items in the model's internal string list. + + The optional \a parent argument is in most models used to specify + the parent of the rows to be counted. Because this is a list if a + valid parent is specified, the result will always be 0. + + \sa insertRows(), removeRows(), QAbstractItemModel::rowCount() +*/ + +int QStringListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return lst.count(); +} + +/*! + Returns data for the specified \a role, from the item with the + given \a index. + + If the view requests an invalid index, an invalid variant is returned. + + \sa setData() +*/ + +QVariant QStringListModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= lst.size()) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) + return lst.at(index.row()); + + return QVariant(); +} + +/*! + Returns the flags for the item with the given \a index. + + Valid items are enabled, selectable, editable, drag enabled and drop enabled. + + \sa QAbstractItemModel::flags() +*/ + +Qt::ItemFlags QStringListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; +} + +/*! + Sets the data for the specified \a role in the item with the given + \a index in the model, to the provided \a value. + + The dataChanged() signal is emitted if the item is changed. + + \sa Qt::ItemDataRole, data() +*/ + +bool QStringListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() >= 0 && index.row() < lst.size() + && (role == Qt::EditRole || role == Qt::DisplayRole)) { + lst.replace(index.row(), value.toString()); + emit dataChanged(index, index); + return true; + } + return false; +} + +/*! + Inserts \a count rows into the model, beginning at the given \a row. + + The \a parent index of the rows is optional and is only used for + consistency with QAbstractItemModel. By default, a null index is + specified, indicating that the rows are inserted in the top level of + the model. + + \sa QAbstractItemModel::insertRows() +*/ + +bool QStringListModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || row > rowCount(parent)) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + + for (int r = 0; r < count; ++r) + lst.insert(row, QString()); + + endInsertRows(); + + return true; +} + +/*! + Removes \a count rows from the model, beginning at the given \a row. + + The \a parent index of the rows is optional and is only used for + consistency with QAbstractItemModel. By default, a null index is + specified, indicating that the rows are removed in the top level of + the model. + + \sa QAbstractItemModel::removeRows() +*/ + +bool QStringListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (count <= 0 || row < 0 || (row + count) > rowCount(parent)) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + + for (int r = 0; r < count; ++r) + lst.removeAt(row); + + endRemoveRows(); + + return true; +} + +static bool ascendingLessThan(const QPair<QString, int> &s1, const QPair<QString, int> &s2) +{ + return s1.first < s2.first; +} + +static bool decendingLessThan(const QPair<QString, int> &s1, const QPair<QString, int> &s2) +{ + return s1.first > s2.first; +} + +/*! + \reimp +*/ +void QStringListModel::sort(int, Qt::SortOrder order) +{ + emit layoutAboutToBeChanged(); + + QList<QPair<QString, int> > list; + for (int i = 0; i < lst.count(); ++i) + list.append(QPair<QString, int>(lst.at(i), i)); + + if (order == Qt::AscendingOrder) + qSort(list.begin(), list.end(), ascendingLessThan); + else + qSort(list.begin(), list.end(), decendingLessThan); + + lst.clear(); + QVector<int> forwarding(list.count()); + for (int i = 0; i < list.count(); ++i) { + lst.append(list.at(i).first); + forwarding[list.at(i).second] = i; + } + + QModelIndexList oldList = persistentIndexList(); + QModelIndexList newList; + for (int i = 0; i < oldList.count(); ++i) + newList.append(index(forwarding.at(oldList.at(i).row()), 0)); + changePersistentIndexList(oldList, newList); + + emit layoutChanged(); +} + +/*! + Returns the string list used by the model to store data. +*/ +QStringList QStringListModel::stringList() const +{ + return lst; +} + +/*! + Sets the model's internal string list to \a strings. The model will + notify any attached views that its underlying data has changed. + + \sa dataChanged() +*/ +void QStringListModel::setStringList(const QStringList &strings) +{ + emit beginResetModel(); + lst = strings; + emit endResetModel(); +} + +/*! + \reimp +*/ +Qt::DropActions QStringListModel::supportedDropActions() const +{ + return QAbstractItemModel::supportedDropActions() | Qt::MoveAction; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STRINGLISTMODEL diff --git a/src/corelib/itemmodels/qstringlistmodel.h b/src/corelib/itemmodels/qstringlistmodel.h new file mode 100644 index 0000000000..c70072de9e --- /dev/null +++ b/src/corelib/itemmodels/qstringlistmodel.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTRINGLISTMODEL_H +#define QSTRINGLISTMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +#ifndef QT_NO_STRINGLISTMODEL + +class Q_CORE_EXPORT QStringListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit QStringListModel(QObject *parent = 0); + QStringListModel(const QStringList &strings, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList stringList() const; + void setStringList(const QStringList &strings); + + Qt::DropActions supportedDropActions() const; + +private: + Q_DISABLE_COPY(QStringListModel) + QStringList lst; +}; + +#endif // QT_NO_STRINGLISTMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTRINGLISTMODEL_H |