From 211e434a05c666e172b2f1e2f72c7695adac52a1 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Sat, 10 Dec 2011 19:13:51 +0100 Subject: Move proxy and selection models to QtCore. Change-Id: I71097855cb9e28105238e496778f29f99f7fc84e Reviewed-by: Thiago Macieira Reviewed-by: Lars Knoll --- src/corelib/itemmodels/itemmodels.pri | 16 +- src/corelib/itemmodels/qabstractproxymodel.cpp | 388 ++++ src/corelib/itemmodels/qabstractproxymodel.h | 113 + src/corelib/itemmodels/qabstractproxymodel_p.h | 76 + src/corelib/itemmodels/qidentityproxymodel.cpp | 578 +++++ src/corelib/itemmodels/qidentityproxymodel.h | 120 + src/corelib/itemmodels/qitemselectionmodel.cpp | 1641 +++++++++++++ src/corelib/itemmodels/qitemselectionmodel.h | 256 ++ src/corelib/itemmodels/qitemselectionmodel_p.h | 113 + src/corelib/itemmodels/qsortfilterproxymodel.cpp | 2703 ++++++++++++++++++++++ src/corelib/itemmodels/qsortfilterproxymodel.h | 205 ++ src/corelib/itemmodels/qstringlistmodel.cpp | 310 +++ src/corelib/itemmodels/qstringlistmodel.h | 91 + 13 files changed, 6608 insertions(+), 2 deletions(-) create mode 100644 src/corelib/itemmodels/qabstractproxymodel.cpp create mode 100644 src/corelib/itemmodels/qabstractproxymodel.h create mode 100644 src/corelib/itemmodels/qabstractproxymodel_p.h create mode 100644 src/corelib/itemmodels/qidentityproxymodel.cpp create mode 100644 src/corelib/itemmodels/qidentityproxymodel.h create mode 100644 src/corelib/itemmodels/qitemselectionmodel.cpp create mode 100644 src/corelib/itemmodels/qitemselectionmodel.h create mode 100644 src/corelib/itemmodels/qitemselectionmodel_p.h create mode 100644 src/corelib/itemmodels/qsortfilterproxymodel.cpp create mode 100644 src/corelib/itemmodels/qsortfilterproxymodel.h create mode 100644 src/corelib/itemmodels/qstringlistmodel.cpp create mode 100644 src/corelib/itemmodels/qstringlistmodel.h (limited to 'src/corelib') diff --git a/src/corelib/itemmodels/itemmodels.pri b/src/corelib/itemmodels/itemmodels.pri index 618c748612..83ec4c5dbf 100644 --- a/src/corelib/itemmodels/itemmodels.pri +++ b/src/corelib/itemmodels/itemmodels.pri @@ -2,7 +2,19 @@ HEADERS += \ itemmodels/qabstractitemmodel.h \ - itemmodels/qabstractitemmodel_p.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/qabstractitemmodel.cpp \ + itemmodels/qabstractproxymodel.cpp \ + itemmodels/qitemselectionmodel.cpp \ + itemmodels/qidentityproxymodel.cpp \ + itemmodels/qsortfilterproxymodel.cpp \ + itemmodels/qstringlistmodel.cpp diff --git a/src/corelib/itemmodels/qabstractproxymodel.cpp b/src/corelib/itemmodels/qabstractproxymodel.cpp new file mode 100644 index 0000000000..4beea7b5d9 --- /dev/null +++ b/src/corelib/itemmodels/qabstractproxymodel.cpp @@ -0,0 +1,388 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 +#include +#include + + +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 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..655ea1db02 --- /dev/null +++ b/src/corelib/itemmodels/qabstractproxymodel.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 + +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 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 &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..cb798e0439 --- /dev/null +++ b/src/corelib/itemmodels/qabstractproxymodel_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 +** 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 + +QT_BEGIN_NAMESPACE + +class QIdentityProxyModelPrivate : public QAbstractProxyModelPrivate +{ + QIdentityProxyModelPrivate() + { + + } + + Q_DECLARE_PUBLIC(QIdentityProxyModel) + + QList 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 +** 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 + +#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..d4a2efd35f --- /dev/null +++ b/src/corelib/itemmodels/qitemselectionmodel.cpp @@ -0,0 +1,1641 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 +#include + +#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::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::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 split; + QList::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 split; + QList::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 &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(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::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; icurrentSelection.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; icurrentSelection.count(); ++i) + if (d->currentSelection.at(i).top() <= row && + d->currentSelection.at(i).bottom() >= row) + for (int j=0; jranges.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::const_iterator it; + QList 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::const_iterator it; + QList 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 > 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 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 > 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 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 (imodel; +} + +/*! + 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..be1f34f9a2 --- /dev/null +++ b/src/corelib/itemmodels/qitemselectionmodel.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 +#include +#include +#include + +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::toSet() with MSVC +inline uint qHash(const QItemSelectionRange &) { return 0; } + +class Q_CORE_EXPORT QItemSelection : public QList +{ +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..5eb9ecccda --- /dev/null +++ b/src/corelib/itemmodels/qitemselectionmodel_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 &r) + { + QList::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 model; + QItemSelection ranges; + QItemSelection currentSelection; + QPersistentModelIndex currentIndex; + QItemSelectionModel::SelectionFlags currentCommand; + QList savedPersistentIndexes; + QList 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..71f68f2409 --- /dev/null +++ b/src/corelib/itemmodels/qsortfilterproxymodel.cpp @@ -0,0 +1,2703 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +typedef QList > QModelIndexPairList; + +static inline QSet qVectorToSet(const QVector &vector) +{ + QSet 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 source_rows; + QVector source_columns; + QVector proxy_rows; + QVector proxy_columns; + QVector mapped_children; + QHash::const_iterator map_iter; + }; + + mutable QHash 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::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::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::const_iterator it = + static_cast(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::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 &sourceParents); + void _q_sourceLayoutChanged(const QList &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 &source_rows, + const QModelIndex &source_parent) const; + QVector > > proxy_intervals_for_source_items_to_add( + const QVector &proxy_to_source, const QVector &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const; + QVector > proxy_intervals_for_source_items( + const QVector &source_to_proxy, const QVector &source_items) const; + void insert_source_items( + QVector &source_to_proxy, QVector &proxy_to_source, + const QVector &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_source_items( + QVector &source_to_proxy, QVector &proxy_to_source, + const QVector &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_proxy_interval( + QVector &source_to_proxy, QVector &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 &proxy_to_source, QVector &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 &source_to_proxy, const QVector &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 handle_filter_changed( + QVector &source_to_proxy, QVector &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 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 &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 > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items( + const QVector &source_to_proxy, const QVector &source_items) const +{ + QVector > 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(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 &source_to_proxy, QVector &proxy_to_source, + const QVector &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 > 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 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 &source_to_proxy, QVector &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 > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items_to_add( + const QVector &proxy_to_source, const QVector &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const +{ + Q_Q(const QSortFilterProxyModel); + QVector > > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int proxy_low = 0; + int proxy_item = 0; + int source_items_index = 0; + QVector 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 >(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 &source_to_proxy, QVector &proxy_to_source, + const QVector &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 > > 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 > interval = proxy_intervals.at(i); + int proxy_start = interval.first; + QVector 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 &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector &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 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 &orthogonal_proxy_to_source = (orient == Qt::Horizontal) ? m->source_rows : m->source_columns; + QVector &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 &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + // figure out which items to remove + QVector 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 &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector &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 > moved_source_index_mappings; + QVector::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(new_index, cm)); + } + } + + // reinsert moved, mapped indexes + QVector >::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 &source_to_proxy, const QVector &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 &proxy_to_source, QVector &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 rows_removed = handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical); + QSet columns_removed = handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal); + QVector::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 QSortFilterProxyModelPrivate::handle_filter_changed( + QVector &source_to_proxy, QVector &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + // Figure out which mapped items to remove + QVector 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 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 source_rows_remove; + QVector source_rows_insert; + QVector source_rows_change; + QVector 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 source_rows_remove_set = qVectorToSet(source_rows_remove); + QVector::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 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 &sourceParents) +{ + Q_Q(QSortFilterProxyModel); + saved_persistent_indexes.clear(); + + QList 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 &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 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 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 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 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 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 does not dynamically re-sort and re-filter 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 = false; + 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)), + this, SLOT(_q_sourceLayoutAboutToBeChanged(QList))); + + disconnect(d->model, SIGNAL(layoutChanged(QList)), + this, SLOT(_q_sourceLayoutChanged(QList))); + + 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)), + this, SLOT(_q_sourceLayoutAboutToBeChanged(QList))); + + connect(d->model, SIGNAL(layoutChanged(QList)), + this, SLOT(_q_sourceLayoutChanged(QList))); + + 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 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 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 false. +*/ +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..80f49e405a --- /dev/null +++ b/src/corelib/itemmodels/qsortfilterproxymodel.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include + +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 &sourceParents)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutChanged(const QList &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..434f790890 --- /dev/null +++ b/src/corelib/itemmodels/qstringlistmodel.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 + +#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 &s1, const QPair &s2) +{ + return s1.first < s2.first; +} + +static bool decendingLessThan(const QPair &s1, const QPair &s2) +{ + return s1.first > s2.first; +} + +/*! + \reimp +*/ +void QStringListModel::sort(int, Qt::SortOrder order) +{ + emit layoutAboutToBeChanged(); + + QList > list; + for (int i = 0; i < lst.count(); ++i) + list.append(QPair(lst.at(i), i)); + + if (order == Qt::AscendingOrder) + qSort(list.begin(), list.end(), ascendingLessThan); + else + qSort(list.begin(), list.end(), decendingLessThan); + + lst.clear(); + QVector 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..3d1331403f --- /dev/null +++ b/src/corelib/itemmodels/qstringlistmodel.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2011 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 +#include + +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 -- cgit v1.2.3