diff options
Diffstat (limited to 'src/gui/itemviews/qsortfilterproxymodel.cpp')
-rw-r--r-- | src/gui/itemviews/qsortfilterproxymodel.cpp | 2542 |
1 files changed, 2542 insertions, 0 deletions
diff --git a/src/gui/itemviews/qsortfilterproxymodel.cpp b/src/gui/itemviews/qsortfilterproxymodel.cpp new file mode 100644 index 0000000000..eb56f56ed4 --- /dev/null +++ b/src/gui/itemviews/qsortfilterproxymodel.cpp @@ -0,0 +1,2542 @@ +/**************************************************************************** +** +** 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsortfilterproxymodel.h" + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include "qitemselectionmodel.h" +#include <qsize.h> +#include <qdebug.h> +#include <qdatetime.h> +#include <qpair.h> +#include <qstringlist.h> +#include <private/qabstractitemmodel_p.h> +#include <private/qabstractproxymodel_p.h> + +QT_BEGIN_NAMESPACE + +typedef QList<QPair<QModelIndex, QPersistentModelIndex> > QModelIndexPairList; + +static inline QSet<int> qVectorToSet(const QVector<int> &vector) +{ + QSet<int> set; + set.reserve(vector.size()); + for(int i=0; i < vector.size(); ++i) + set << vector.at(i); + return set; +} + +class QSortFilterProxyModelLessThan +{ +public: + inline QSortFilterProxyModelLessThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i1, i2); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + +class QSortFilterProxyModelGreaterThan +{ +public: + inline QSortFilterProxyModelGreaterThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), + source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i2, i1); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + + +//this struct is used to store what are the rows that are removed +//between a call to rowsAboutToBeRemoved and rowsRemoved +//it avoids readding rows to the mapping that are currently being removed +struct QRowsRemoval +{ + QRowsRemoval(const QModelIndex &parent_source, int start, int end) : parent_source(parent_source), start(start), end(end) + { + } + + QRowsRemoval() : start(-1), end(-1) + { + } + + bool contains(QModelIndex parent, int row) + { + do { + if (parent == parent_source) + return row >= start && row <= end; + row = parent.row(); + parent = parent.parent(); + } while (row >= 0); + return false; + } +private: + QModelIndex parent_source; + int start; + int end; +}; + +class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate +{ + Q_DECLARE_PUBLIC(QSortFilterProxyModel) + +public: + struct Mapping { + QVector<int> source_rows; + QVector<int> source_columns; + QVector<int> proxy_rows; + QVector<int> proxy_columns; + QVector<QModelIndex> mapped_children; + QHash<QModelIndex, Mapping *>::const_iterator map_iter; + }; + + mutable QHash<QModelIndex, Mapping*> source_index_mapping; + + int source_sort_column; + int proxy_sort_column; + Qt::SortOrder sort_order; + Qt::CaseSensitivity sort_casesensitivity; + int sort_role; + bool sort_localeaware; + + int filter_column; + QRegExp filter_regexp; + int filter_role; + + bool dynamic_sortfilter; + QRowsRemoval itemsBeingRemoved; + + QModelIndexPairList saved_persistent_indexes; + + QHash<QModelIndex, Mapping *>::const_iterator create_mapping( + const QModelIndex &source_parent) const; + QModelIndex proxy_to_source(const QModelIndex &proxyIndex) const; + QModelIndex source_to_proxy(const QModelIndex &sourceIndex) const; + bool can_create_mapping(const QModelIndex &source_parent) const; + + void remove_from_mapping(const QModelIndex &source_parent); + + inline QHash<QModelIndex, Mapping *>::const_iterator index_to_iterator( + const QModelIndex &proxy_index) const + { + Q_ASSERT(proxy_index.isValid()); + Q_ASSERT(proxy_index.model() == q_func()); + const void *p = proxy_index.internalPointer(); + Q_ASSERT(p); + QHash<QModelIndex, Mapping *>::const_iterator it = + static_cast<const Mapping*>(p)->map_iter; + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + return it; + } + + inline QModelIndex create_index(int row, int column, + QHash<QModelIndex, Mapping*>::const_iterator it) const + { + return q_func()->createIndex(row, column, *it); + } + + void _q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right); + void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end); + + void _q_sourceAboutToBeReset(); + void _q_sourceReset(); + + void _q_sourceLayoutAboutToBeChanged(); + void _q_sourceLayoutChanged(); + + 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_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_clearMapping(); + + void sort(); + bool update_source_sort_column(); + void sort_source_rows(QVector<int> &source_rows, + const QModelIndex &source_parent) const; + QVector<QPair<int, QVector<int > > > proxy_intervals_for_source_items_to_add( + const QVector<int> &proxy_to_source, const QVector<int> &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const; + QVector<QPair<int, int > > proxy_intervals_for_source_items( + const QVector<int> &source_to_proxy, const QVector<int> &source_items) const; + void insert_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_proxy_interval( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + int proxy_start, int proxy_end, const QModelIndex &proxy_parent, + Qt::Orientation orient, bool emit_signal = true); + void build_source_to_proxy_mapping( + const QVector<int> &proxy_to_source, QVector<int> &source_to_proxy) const; + void source_items_inserted(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_about_to_be_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void proxy_item_range( + const QVector<int> &source_to_proxy, const QVector<int> &source_items, + int &proxy_low, int &proxy_high) const; + + QModelIndexPairList store_persistent_indexes(); + void update_persistent_indexes(const QModelIndexPairList &source_indexes); + + void filter_changed(const QModelIndex &source_parent = QModelIndex()); + QSet<int> handle_filter_changed( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient); + + void updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove); + + virtual void _q_sourceModelDestroyed(); +}; + +typedef QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping *> IndexMap; + +void QSortFilterProxyModelPrivate::_q_sourceModelDestroyed() +{ + QAbstractProxyModelPrivate::_q_sourceModelDestroyed(); + _q_clearMapping(); +} + +void QSortFilterProxyModelPrivate::remove_from_mapping(const QModelIndex &source_parent) +{ + if (Mapping *m = source_index_mapping.take(source_parent)) { + for (int i = 0; i < m->mapped_children.size(); ++i) + remove_from_mapping(m->mapped_children.at(i)); + delete m; + } +} + +void QSortFilterProxyModelPrivate::_q_clearMapping() +{ + // store the persistent indexes + QModelIndexPairList source_indexes = store_persistent_indexes(); + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + if (dynamic_sortfilter && update_source_sort_column()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + // update the persistent indexes + update_persistent_indexes(source_indexes); +} + +IndexMap::const_iterator QSortFilterProxyModelPrivate::create_mapping( + const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it != source_index_mapping.constEnd()) // was mapped already + return it; + + Mapping *m = new Mapping; + + int source_rows = model->rowCount(source_parent); + m->source_rows.reserve(source_rows); + for (int i = 0; i < source_rows; ++i) { + if (q->filterAcceptsRow(i, source_parent)) + m->source_rows.append(i); + } + int source_cols = model->columnCount(source_parent); + m->source_columns.reserve(source_cols); + for (int i = 0; i < source_cols; ++i) { + if (q->filterAcceptsColumn(i, source_parent)) + m->source_columns.append(i); + } + + sort_source_rows(m->source_rows, source_parent); + m->proxy_rows.resize(source_rows); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + m->proxy_columns.resize(source_cols); + build_source_to_proxy_mapping(m->source_columns, m->proxy_columns); + + it = IndexMap::const_iterator(source_index_mapping.insert(source_parent, m)); + m->map_iter = it; + + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it2 = create_mapping(source_grand_parent); + Q_ASSERT(it2 != source_index_mapping.constEnd()); + it2.value()->mapped_children.append(source_parent); + } + + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + + return it; +} + +QModelIndex QSortFilterProxyModelPrivate::proxy_to_source(const QModelIndex &proxy_index) const +{ + if (!proxy_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + if (proxy_index.model() != q_func()) { + qWarning() << "QSortFilterProxyModel: index from wrong model passed to mapToSource"; + Q_ASSERT(!"QSortFilterProxyModel: index from wrong model passed to mapToSource"); + return QModelIndex(); + } + IndexMap::const_iterator it = index_to_iterator(proxy_index); + Mapping *m = it.value(); + if ((proxy_index.row() >= m->source_rows.size()) || (proxy_index.column() >= m->source_columns.size())) + return QModelIndex(); + int source_row = m->source_rows.at(proxy_index.row()); + int source_col = m->source_columns.at(proxy_index.column()); + return model->index(source_row, source_col, it.key()); +} + +QModelIndex QSortFilterProxyModelPrivate::source_to_proxy(const QModelIndex &source_index) const +{ + if (!source_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + if (source_index.model() != model) { + qWarning() << "QSortFilterProxyModel: index from wrong model passed to mapFromSource"; + Q_ASSERT(!"QSortFilterProxyModel: index from wrong model passed to mapFromSource"); + return QModelIndex(); + } + QModelIndex source_parent = source_index.parent(); + IndexMap::const_iterator it = create_mapping(source_parent); + Mapping *m = it.value(); + if ((source_index.row() >= m->proxy_rows.size()) || (source_index.column() >= m->proxy_columns.size())) + return QModelIndex(); + int proxy_row = m->proxy_rows.at(source_index.row()); + int proxy_column = m->proxy_columns.at(source_index.column()); + if (proxy_row == -1 || proxy_column == -1) + return QModelIndex(); + return create_index(proxy_row, proxy_column, it); +} + +bool QSortFilterProxyModelPrivate::can_create_mapping(const QModelIndex &source_parent) const +{ + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it = source_index_mapping.constFind(source_grand_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for the grand parent + return false; + } + Mapping *gm = it.value(); + if (gm->proxy_rows.at(source_parent.row()) == -1 || + gm->proxy_columns.at(source_parent.column()) == -1) { + // Don't care, since parent is filtered + return false; + } + } + return true; +} + +/*! + \internal + + Sorts the existing mappings. +*/ +void QSortFilterProxyModelPrivate::sort() +{ + Q_Q(QSortFilterProxyModel); + emit q->layoutAboutToBeChanged(); + QModelIndexPairList source_indexes = store_persistent_indexes(); + IndexMap::const_iterator it = source_index_mapping.constBegin(); + for (; it != source_index_mapping.constEnd(); ++it) { + QModelIndex source_parent = it.key(); + Mapping *m = it.value(); + sort_source_rows(m->source_rows, source_parent); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + } + update_persistent_indexes(source_indexes); + emit q->layoutChanged(); +} + +/*! + \internal + + update the source_sort_column according to the proxy_sort_column + return true if the column was changed +*/ +bool QSortFilterProxyModelPrivate::update_source_sort_column() +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_index = q->index(0, proxy_sort_column, QModelIndex()); + int old_source_sort_colum = source_sort_column; + source_sort_column = q->mapToSource(proxy_index).column(); + return old_source_sort_colum != source_sort_column; +} + + +/*! + \internal + + Sorts the given \a source_rows according to current sort column and order. +*/ +void QSortFilterProxyModelPrivate::sort_source_rows( + QVector<int> &source_rows, const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + if (source_sort_column >= 0) { + if (sort_order == Qt::AscendingOrder) { + QSortFilterProxyModelLessThan lt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), lt); + } else { + QSortFilterProxyModelGreaterThan gt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), gt); + } + } else { // restore the source model order + qStableSort(source_rows.begin(), source_rows.end()); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and the set of + source items \a source_items (which are part of that mapping), + determines the corresponding proxy item intervals that should + be removed from the proxy model. + + The result is a vector of pairs, where each pair represents a + (start, end) tuple, sorted in ascending order. +*/ +QVector<QPair<int, int > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items( + const QVector<int> &source_to_proxy, const QVector<int> &source_items) const +{ + QVector<QPair<int, int> > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int source_items_index = 0; + while (source_items_index < source_items.size()) { + int first_proxy_item = source_to_proxy.at(source_items.at(source_items_index)); + Q_ASSERT(first_proxy_item != -1); + int last_proxy_item = first_proxy_item; + ++source_items_index; + // Find end of interval + while ((source_items_index < source_items.size()) + && (source_to_proxy.at(source_items.at(source_items_index)) == last_proxy_item + 1)) { + ++last_proxy_item; + ++source_items_index; + } + // Add interval to result + proxy_intervals.append(QPair<int, int>(first_proxy_item, last_proxy_item)); + } + qStableSort(proxy_intervals.begin(), proxy_intervals.end()); + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a src_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes \a source_items from this proxy model. + The corresponding proxy items are removed in intervals, so that the proper + rows/columnsRemoved(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::remove_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (already removed) + + QVector<QPair<int, int> > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items(source_to_proxy, source_items); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair<int, int> interval = proxy_intervals.at(i); + int proxy_start = interval.first; + int proxy_end = interval.second; + remove_proxy_interval(source_to_proxy, proxy_to_source, proxy_start, proxy_end, + proxy_parent, orient, emit_signal); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes items from \a proxy_start to \a proxy_end + (inclusive) from this proxy model. +*/ +void QSortFilterProxyModelPrivate::remove_proxy_interval( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, int proxy_start, int proxy_end, + const QModelIndex &proxy_parent, Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginRemoveRows(proxy_parent, proxy_start, proxy_end); + else + q->beginRemoveColumns(proxy_parent, proxy_start, proxy_end); + } + + // Remove items from proxy-to-source mapping + proxy_to_source.remove(proxy_start, proxy_end - proxy_start + 1); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endRemoveRows(); + else + q->endRemoveColumns(); + } +} + +/*! + \internal + + Given proxy-to-source mapping \a proxy_to_source and a set of + unmapped source items \a source_items, determines the proxy item + intervals at which the subsets of source items should be inserted + (but does not actually add them to the mapping). + + The result is a vector of pairs, each pair representing a tuple (start, + items), where items is a vector containing the (sorted) source items that + should be inserted at that proxy model location. +*/ +QVector<QPair<int, QVector<int > > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items_to_add( + const QVector<int> &proxy_to_source, const QVector<int> &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const +{ + Q_Q(const QSortFilterProxyModel); + QVector<QPair<int, QVector<int> > > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int proxy_low = 0; + int proxy_item = 0; + int source_items_index = 0; + QVector<int> source_items_in_interval; + bool compare = (orient == Qt::Vertical && source_sort_column >= 0 && dynamic_sortfilter); + while (source_items_index < source_items.size()) { + source_items_in_interval.clear(); + int first_new_source_item = source_items.at(source_items_index); + source_items_in_interval.append(first_new_source_item); + ++source_items_index; + + // Find proxy item at which insertion should be started + int proxy_high = proxy_to_source.size() - 1; + QModelIndex i1 = compare ? model->index(first_new_source_item, source_sort_column, source_parent) : QModelIndex(); + while (proxy_low <= proxy_high) { + proxy_item = (proxy_low + proxy_high) / 2; + if (compare) { + QModelIndex i2 = model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } else { + if (first_new_source_item < proxy_to_source.at(proxy_item)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } + } + proxy_item = proxy_low; + + // Find the sequence of new source items that should be inserted here + if (proxy_item >= proxy_to_source.size()) { + for ( ; source_items_index < source_items.size(); ++source_items_index) + source_items_in_interval.append(source_items.at(source_items_index)); + } else { + i1 = compare ? model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent) : QModelIndex(); + for ( ; source_items_index < source_items.size(); ++source_items_index) { + int new_source_item = source_items.at(source_items_index); + if (compare) { + QModelIndex i2 = model->index(new_source_item, source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + break; + } else { + if (proxy_to_source.at(proxy_item) < new_source_item) + break; + } + source_items_in_interval.append(new_source_item); + } + } + + // Add interval to result + proxy_intervals.append(QPair<int, QVector<int> >(proxy_item, source_items_in_interval)); + } + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, inserts the given \a source_items into this proxy model. + The source items are inserted in intervals (based on some sorted order), so + that the proper rows/columnsInserted(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::insert_source_items( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QVector<int> &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (source_parent is not mapped) + + QVector<QPair<int, QVector<int> > > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items_to_add( + proxy_to_source, source_items, source_parent, orient); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair<int, QVector<int> > interval = proxy_intervals.at(i); + int proxy_start = interval.first; + QVector<int> source_items = interval.second; + int proxy_end = proxy_start + source_items.size() - 1; + + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginInsertRows(proxy_parent, proxy_start, proxy_end); + else + q->beginInsertColumns(proxy_parent, proxy_start, proxy_end); + } + + for (int i = 0; i < source_items.size(); ++i) + proxy_to_source.insert(proxy_start + i, source_items.at(i)); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endInsertRows(); + else + q->endInsertColumns(); + } + } +} + +/*! + \internal + + Handles source model items insertion (columnsInserted(), rowsInserted()). + Determines + 1) which of the inserted items to also insert into proxy model (filtering), + 2) where to insert the items into the proxy model (sorting), + then inserts those items. + The items are inserted into the proxy model in intervals (based on + sorted order), so that the proper rows/columnsInserted(start, end) + signals will be generated. +*/ +void QSortFilterProxyModelPrivate::source_items_inserted( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + if (!can_create_mapping(source_parent)) + return; + it = create_mapping(source_parent); + Mapping *m = it.value(); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (m->source_rows.count() > 0) { + q->beginInsertRows(proxy_parent, 0, m->source_rows.count() - 1); + q->endInsertRows(); + } + if (m->source_columns.count() > 0) { + q->beginInsertColumns(proxy_parent, 0, m->source_columns.count() - 1); + q->endInsertColumns(); + } + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + int delta_item_count = end - start + 1; + int old_item_count = source_to_proxy.size(); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, false); + + // Expand source-to-proxy mapping to account for new items + if (start < 0 || start > source_to_proxy.size()) { + qWarning("QSortFilterProxyModel: invalid inserted rows reported by source model"); + remove_from_mapping(source_parent); + return; + } + source_to_proxy.insert(start, delta_item_count, -1); + + if (start < old_item_count) { + // Adjust existing "stale" indexes in proxy-to-source mapping + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) + proxy_to_source.replace(proxy_item, source_item + delta_item_count); + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + } + + // Figure out which items to add to mapping based on filter + QVector<int> source_items; + for (int i = start; i <= end; ++i) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(i, source_parent) + : q->filterAcceptsColumn(i, source_parent)) { + source_items.append(i); + } + } + + if (model->rowCount(source_parent) == delta_item_count) { + // Items were inserted where there were none before. + // If it was new rows make sure to create mappings for columns so that a + // valid mapping can be retrieved later and vice-versa. + + QVector<int> &orthogonal_proxy_to_source = (orient == Qt::Horizontal) ? m->source_rows : m->source_columns; + QVector<int> &orthogonal_source_to_proxy = (orient == Qt::Horizontal) ? m->proxy_rows : m->proxy_columns; + + if (orthogonal_source_to_proxy.isEmpty()) { + const int ortho_end = (orient == Qt::Horizontal) ? model->rowCount(source_parent) : model->columnCount(source_parent); + + orthogonal_source_to_proxy.resize(ortho_end); + + for (int ortho_item = 0; ortho_item < ortho_end; ++ortho_item) { + if ((orient == Qt::Horizontal) ? q->filterAcceptsRow(ortho_item, source_parent) + : q->filterAcceptsColumn(ortho_item, source_parent)) { + orthogonal_proxy_to_source.append(ortho_item); + } + } + if (orient == Qt::Horizontal) { + // We're reacting to columnsInserted, but we've just inserted new rows. Sort them. + sort_source_rows(orthogonal_proxy_to_source, source_parent); + } + build_source_to_proxy_mapping(orthogonal_proxy_to_source, orthogonal_source_to_proxy); + } + } + + // Sort and insert the items + if (orient == Qt::Vertical) // Only sort rows + sort_source_rows(source_items, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, source_items, source_parent, orient); +} + +/*! + \internal + + Handles source model items removal + (columnsAboutToBeRemoved(), rowsAboutToBeRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_about_to_be_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + // figure out which items to remove + QVector<int> source_items_to_remove; + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if ((source_item >= start) && (source_item <= end)) + source_items_to_remove.append(source_item); + } + + remove_source_items(source_to_proxy, proxy_to_source, source_items_to_remove, + source_parent, orient); +} + +/*! + \internal + + Handles source model items removal (columnsRemoved(), rowsRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector<int> &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector<int> &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + if (end >= source_to_proxy.size()) + end = source_to_proxy.size() - 1; + + // Shrink the source-to-proxy mapping to reflect the new item count + int delta_item_count = end - start + 1; + source_to_proxy.remove(start, delta_item_count); + + int proxy_count = proxy_to_source.size(); + if (proxy_count > source_to_proxy.size()) { + // mapping is in an inconsistent state -- redo the whole mapping + qWarning("QSortFilterProxyModel: inconsistent changes reported by source model"); + remove_from_mapping(source_parent); + Q_Q(QSortFilterProxyModel); + q->reset(); + return; + } + + // Adjust "stale" indexes in proxy-to-source mapping + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) { + Q_ASSERT(source_item - delta_item_count >= 0); + proxy_to_source.replace(proxy_item, source_item - delta_item_count); + } + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, true); + +} + + +/*! + \internal + updates the mapping of the children when inserting or removing items +*/ +void QSortFilterProxyModelPrivate::updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove) +{ + // see if any mapped children should be (re)moved + QVector<QPair<QModelIndex, Mapping*> > moved_source_index_mappings; + QVector<QModelIndex>::iterator it2 = parent_mapping->mapped_children.begin(); + for ( ; it2 != parent_mapping->mapped_children.end();) { + const QModelIndex source_child_index = *it2; + const int pos = (orient == Qt::Vertical) + ? source_child_index.row() + : source_child_index.column(); + if (pos < start) { + // not affected + ++it2; + } else if (remove && pos <= end) { + // in the removed interval + it2 = parent_mapping->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + // below the removed items -- recompute the index + QModelIndex new_index; + const int newpos = remove ? pos - delta_item_count : pos + delta_item_count; + if (orient == Qt::Vertical) { + new_index = model->index(newpos, + source_child_index.column(), + source_parent); + } else { + new_index = model->index(source_child_index.row(), + newpos, + source_parent); + } + *it2 = new_index; + ++it2; + + // update mapping + Mapping *cm = source_index_mapping.take(source_child_index); + Q_ASSERT(cm); + // we do not reinsert right away, because the new index might be identical with another, old index + moved_source_index_mappings.append(QPair<QModelIndex, Mapping*>(new_index, cm)); + } + } + + // reinsert moved, mapped indexes + QVector<QPair<QModelIndex, Mapping*> >::iterator it = moved_source_index_mappings.begin(); + for (; it != moved_source_index_mappings.end(); ++it) { +#ifdef QT_STRICT_ITERATORS + source_index_mapping.insert((*it).first, (*it).second); + (*it).second->map_iter = source_index_mapping.constFind((*it).first); +#else + (*it).second->map_iter = source_index_mapping.insert((*it).first, (*it).second); +#endif + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::proxy_item_range( + const QVector<int> &source_to_proxy, const QVector<int> &source_items, + int &proxy_low, int &proxy_high) const +{ + proxy_low = INT_MAX; + proxy_high = INT_MIN; + for (int i = 0; i < source_items.count(); ++i) { + int proxy_item = source_to_proxy.at(source_items.at(i)); + Q_ASSERT(proxy_item != -1); + if (proxy_item < proxy_low) + proxy_low = proxy_item; + if (proxy_item > proxy_high) + proxy_high = proxy_item; + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::build_source_to_proxy_mapping( + const QVector<int> &proxy_to_source, QVector<int> &source_to_proxy) const +{ + source_to_proxy.fill(-1); + int proxy_count = proxy_to_source.size(); + for (int i = 0; i < proxy_count; ++i) + source_to_proxy[proxy_to_source.at(i)] = i; +} + +/*! + \internal + + Maps the persistent proxy indexes to source indexes and + returns the list of source indexes. +*/ +QModelIndexPairList QSortFilterProxyModelPrivate::store_persistent_indexes() +{ + Q_Q(QSortFilterProxyModel); + QModelIndexPairList source_indexes; + foreach (QPersistentModelIndexData *data, persistent.indexes) { + QModelIndex proxy_index = data->index; + QModelIndex source_index = q->mapToSource(proxy_index); + source_indexes.append(qMakePair(proxy_index, QPersistentModelIndex(source_index))); + } + return source_indexes; +} + +/*! + \internal + + Maps \a source_indexes to proxy indexes and stores those + as persistent indexes. +*/ +void QSortFilterProxyModelPrivate::update_persistent_indexes( + const QModelIndexPairList &source_indexes) +{ + Q_Q(QSortFilterProxyModel); + QModelIndexList from, to; + for (int i = 0; i < source_indexes.count(); ++i) { + QModelIndex source_index = source_indexes.at(i).second; + QModelIndex old_proxy_index = source_indexes.at(i).first; + create_mapping(source_index.parent()); + QModelIndex proxy_index = q->mapFromSource(source_index); + from << old_proxy_index; + to << proxy_index; + } + q->changePersistentIndexList(from, to); +} + + +/*! + \internal + + Updates the proxy model (adds/removes rows) based on the + new filter. +*/ +void QSortFilterProxyModelPrivate::filter_changed(const QModelIndex &source_parent) +{ + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) + return; + Mapping *m = it.value(); + QSet<int> rows_removed = handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical); + QSet<int> columns_removed = handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal); + QVector<QModelIndex>::iterator it2 = m->mapped_children.end(); + while (it2 != m->mapped_children.begin()) { + --it2; + const QModelIndex source_child_index = *it2; + if (rows_removed.contains(source_child_index.row()) || columns_removed.contains(source_child_index.column())) { + it2 = m->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + filter_changed(source_child_index); + } + } +} + +/*! + \internal + returns the removed items indexes +*/ +QSet<int> QSortFilterProxyModelPrivate::handle_filter_changed( + QVector<int> &source_to_proxy, QVector<int> &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + // Figure out which mapped items to remove + QVector<int> source_items_remove; + for (int i = 0; i < proxy_to_source.count(); ++i) { + const int source_item = proxy_to_source.at(i); + if ((orient == Qt::Vertical) + ? !q->filterAcceptsRow(source_item, source_parent) + : !q->filterAcceptsColumn(source_item, source_parent)) { + // This source item does not satisfy the filter, so it must be removed + source_items_remove.append(source_item); + } + } + // Figure out which non-mapped items to insert + QVector<int> source_items_insert; + int source_count = source_to_proxy.size(); + for (int source_item = 0; source_item < source_count; ++source_item) { + if (source_to_proxy.at(source_item) == -1) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(source_item, source_parent) + : q->filterAcceptsColumn(source_item, source_parent)) { + // This source item satisfies the filter, so it must be added + source_items_insert.append(source_item); + } + } + } + if (!source_items_remove.isEmpty() || !source_items_insert.isEmpty()) { + // Do item removal and insertion + remove_source_items(source_to_proxy, proxy_to_source, + source_items_remove, source_parent, orient); + if (orient == Qt::Vertical) + sort_source_rows(source_items_insert, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, + source_items_insert, source_parent, orient); + } + return qVectorToSet(source_items_remove); +} + +void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right) +{ + Q_Q(QSortFilterProxyModel); + if (!source_top_left.isValid() || !source_bottom_right.isValid()) + return; + QModelIndex source_parent = source_top_left.parent(); + IndexMap::const_iterator it = source_index_mapping.find(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + Mapping *m = it.value(); + + // Figure out how the source changes affect us + QVector<int> source_rows_remove; + QVector<int> source_rows_insert; + QVector<int> source_rows_change; + QVector<int> source_rows_resort; + int end = qMin(source_bottom_right.row(), m->proxy_rows.count() - 1); + for (int source_row = source_top_left.row(); source_row <= end; ++source_row) { + if (dynamic_sortfilter) { + if (m->proxy_rows.at(source_row) != -1) { + if (!q->filterAcceptsRow(source_row, source_parent)) { + // This source row no longer satisfies the filter, so it must be removed + source_rows_remove.append(source_row); + } else if (source_sort_column >= source_top_left.column() && source_sort_column <= source_bottom_right.column()) { + // This source row has changed in a way that may affect sorted order + source_rows_resort.append(source_row); + } else { + // This row has simply changed, without affecting filtering nor sorting + source_rows_change.append(source_row); + } + } else { + if (!itemsBeingRemoved.contains(source_parent, source_row) && q->filterAcceptsRow(source_row, source_parent)) { + // This source row now satisfies the filter, so it must be added + source_rows_insert.append(source_row); + } + } + } else { + if (m->proxy_rows.at(source_row) != -1) + source_rows_change.append(source_row); + } + } + + if (!source_rows_remove.isEmpty()) { + remove_source_items(m->proxy_rows, m->source_rows, + source_rows_remove, source_parent, Qt::Vertical); + QSet<int> source_rows_remove_set = qVectorToSet(source_rows_remove); + QVector<QModelIndex>::iterator it = m->mapped_children.end(); + while (it != m->mapped_children.begin()) { + --it; + const QModelIndex source_child_index = *it; + if (source_rows_remove_set.contains(source_child_index.row())) { + it = m->mapped_children.erase(it); + remove_from_mapping(source_child_index); + } + } + } + + if (!source_rows_resort.isEmpty()) { + // Re-sort the rows + emit q->layoutAboutToBeChanged(); + 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(); + // 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() +{ + Q_Q(QSortFilterProxyModel); + saved_persistent_indexes.clear(); + emit q->layoutAboutToBeChanged(); + if (persistent.indexes.isEmpty()) + return; + + saved_persistent_indexes = store_persistent_indexes(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged() +{ + 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()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + emit q->layoutChanged(); +} + +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_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(); +} + +/*! + \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 + + 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} +*/ + +/*! + 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(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + + disconnect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + + 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(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + + connect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + + connect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); + connect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + d->_q_clearMapping(); + endResetModel(); + if (d->update_source_sort_column() && d->dynamic_sortfilter) + d->sort(); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + if (row < 0 || column < 0) + return QModelIndex(); + + QModelIndex source_parent = mapToSource(parent); // parent is already mapped at this point + IndexMap::const_iterator it = d->create_mapping(source_parent); // but make sure that the children are mapped + if (it.value()->source_rows.count() <= row || it.value()->source_columns.count() <= column) + return QModelIndex(); + + return d->create_index(row, column, it); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::parent(const QModelIndex &child) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(child)) + return QModelIndex(); + IndexMap::const_iterator it = d->index_to_iterator(child); + Q_ASSERT(it != d->source_index_mapping.constEnd()); + QModelIndex source_parent = it.key(); + QModelIndex proxy_parent = mapFromSource(source_parent); + return proxy_parent; +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_rows.count(); +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_columns.count(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + if (!d->model->hasChildren(source_parent)) + return false; + + if (d->model->canFetchMore(source_parent)) + return true; //we assume we might have children that can be fetched + + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + return m->source_rows.count() != 0 && m->source_columns.count() != 0; +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QVariant(); + return d->model->data(source_index, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return false; + return d->model->setData(source_index, value, role); +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::headerData(section, orientation, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return QVariant(); + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return QVariant(); + source_section = it.value()->source_columns.at(section); + } + return d->model->headerData(source_section, orientation, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::setHeaderData(section, orientation, value, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return false; + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return false; + source_section = it.value()->source_columns.at(section); + } + return d->model->setHeaderData(source_section, orientation, value, role); +} + +/*! + \reimp +*/ +QMimeData *QSortFilterProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndexList source_indexes; + for (int i = 0; i < indexes.count(); ++i) + source_indexes << mapToSource(indexes.at(i)); + return d->model->mimeData(source_indexes); +} + +/*! + \reimp +*/ +QStringList QSortFilterProxyModel::mimeTypes() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->mimeTypes(); +} + +/*! + \reimp +*/ +Qt::DropActions QSortFilterProxyModel::supportedDropActions() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->supportedDropActions(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if ((row == -1) && (column == -1)) + return d->model->dropMimeData(data, action, -1, -1, mapToSource(parent)); + int source_destination_row = -1; + int source_destination_column = -1; + QModelIndex source_parent; + if (row == rowCount(parent)) { + source_parent = mapToSource(parent); + source_destination_row = d->model->rowCount(source_parent); + } else { + QModelIndex proxy_index = index(row, column, parent); + QModelIndex source_index = mapToSource(proxy_index); + source_destination_row = source_index.row(); + source_destination_column = source_index.column(); + source_parent = source_index.parent(); + } + return d->model->dropMimeData(data, action, source_destination_row, + source_destination_column, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row > m->source_rows.count()) + return false; + int source_row = (row >= m->source_rows.count() + ? m->source_rows.count() + : m->source_rows.at(row)); + return d->model->insertRows(source_row, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0|| count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column > m->source_columns.count()) + return false; + int source_column = (column >= m->source_columns.count() + ? m->source_columns.count() + : m->source_columns.at(column)); + return d->model->insertColumns(source_column, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row + count > m->source_rows.count()) + return false; + if ((count == 1) + || ((d->source_sort_column < 0) && (m->proxy_rows.count() == m->source_rows.count()))) { + int source_row = m->source_rows.at(row); + return d->model->removeRows(source_row, count, source_parent); + } + // remove corresponding source intervals + // ### if this proves to be slow, we can switch to single-row removal + QVector<int> rows; + for (int i = row; i < row + count; ++i) + rows.append(m->source_rows.at(i)); + qSort(rows.begin(), rows.end()); + + int pos = rows.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = rows.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (rows.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeRows(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column + count > m->source_columns.count()) + return false; + if ((count == 1) || (m->proxy_columns.count() == m->source_columns.count())) { + int source_column = m->source_columns.at(column); + return d->model->removeColumns(source_column, count, source_parent); + } + // remove corresponding source intervals + QVector<int> columns; + for (int i = column; i < column + count; ++i) + columns.append(m->source_columns.at(i)); + + int pos = columns.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = columns.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (columns.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeColumns(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + d->model->fetchMore(source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + return d->model->canFetchMore(source_parent); +} + +/*! + \reimp +*/ +Qt::ItemFlags QSortFilterProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index; + if (d->indexValid(index)) + source_index = mapToSource(index); + return d->model->flags(source_index); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::buddy(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(index)) + return QModelIndex(); + QModelIndex source_index = mapToSource(index); + QModelIndex source_buddy = d->model->buddy(source_index); + if (source_index == source_buddy) + return index; + return mapFromSource(source_buddy); +} + +/*! + \reimp +*/ +QModelIndexList QSortFilterProxyModel::match(const QModelIndex &start, int role, + const QVariant &value, int hits, + Qt::MatchFlags flags) const +{ + return QAbstractProxyModel::match(start, role, value, hits, flags); +} + +/*! + \reimp +*/ +QSize QSortFilterProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QSize(); + return d->model->span(source_index); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QSortFilterProxyModel); + if (d->dynamic_sortfilter && d->proxy_sort_column == column && d->sort_order == order) + return; + d->sort_order = order; + d->proxy_sort_column = column; + d->update_source_sort_column(); + d->sort(); +} + +/*! + \since 4.5 + \brief the column currently used for sorting + + This returns the most recently used sort column. +*/ +int QSortFilterProxyModel::sortColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_sort_column; +} + +/*! + \since 4.5 + \brief the order currently used for sorting + + This returns the most recently used sort order. +*/ +Qt::SortOrder QSortFilterProxyModel::sortOrder() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_order; +} + +/*! + \property QSortFilterProxyModel::filterRegExp + \brief the QRegExp used to filter the contents of the source model + + Setting this property overwrites the current + \l{QSortFilterProxyModel::filterCaseSensitivity}{filterCaseSensitivity}. + By default, the QRegExp is an empty string matching all contents. + + If no QRegExp or an empty string is set, everything in the source model + will be accepted. + + \sa filterCaseSensitivity, setFilterWildcard(), setFilterFixedString() +*/ +QRegExp QSortFilterProxyModel::filterRegExp() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp; +} + +void QSortFilterProxyModel::setFilterRegExp(const QRegExp ®Exp) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp = regExp; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterKeyColumn + \brief the column where the key used to filter the contents of the + source model is read from. + + The default value is 0. If the value is -1, the keys will be read + from all columns. +*/ +int QSortFilterProxyModel::filterKeyColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_column; +} + +void QSortFilterProxyModel::setFilterKeyColumn(int column) +{ + Q_D(QSortFilterProxyModel); + d->filter_column = column; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterCaseSensitivity + + \brief the case sensitivity of the QRegExp pattern used to filter the + contents of the source model + + By default, the filter is case sensitive. + + \sa filterRegExp, sortCaseSensitivity +*/ +Qt::CaseSensitivity QSortFilterProxyModel::filterCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp.caseSensitivity(); +} + +void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (cs == d->filter_regexp.caseSensitivity()) + return; + d->filter_regexp.setCaseSensitivity(cs); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortCaseSensitivity + \brief the case sensitivity setting used for comparing strings when sorting + + By default, sorting is case sensitive. + + \sa filterCaseSensitivity, lessThan() +*/ +Qt::CaseSensitivity QSortFilterProxyModel::sortCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_casesensitivity; +} + +void QSortFilterProxyModel::setSortCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_casesensitivity == cs) + return; + + d->sort_casesensitivity = cs; + d->sort(); +} + +/*! + \since 4.3 + \property QSortFilterProxyModel::isSortLocaleAware + \brief the local aware setting used for comparing strings when sorting + + By default, sorting is not local aware. + + \sa sortCaseSensitivity, lessThan() +*/ +bool QSortFilterProxyModel::isSortLocaleAware() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_localeaware; +} + +void QSortFilterProxyModel::setSortLocaleAware(bool on) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_localeaware == on) + return; + + d->sort_localeaware = on; + d->sort(); +} + +/*! + \overload + + Sets the regular expression used to filter the contents + of the source model to \a pattern. + + \sa setFilterCaseSensitivity(), setFilterWildcard(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterRegExp(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::RegExp); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the wildcard expression used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterWildcard(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::Wildcard); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the fixed string used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterWildcard(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterFixedString(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::FixedString); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::dynamicSortFilter + \brief whether the proxy model is dynamically sorted and filtered + whenever the contents of the source model change + + Note that you should not update the source model through the proxy + model when dynamicSortFilter is true. For instance, if you set the + proxy model on a QComboBox, then using functions that update the + model, e.g., \l{QComboBox::}{addItem()}, will not work as + expected. An alternative is to set dynamicSortFilter to false and + call \l{QSortFilterProxyModel::}{sort()} after adding items to the + QComboBox. + + The default value is 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 |