Clementine-audio-player-Mac.../src/core/mergedproxymodel.cpp

452 lines
15 KiB
C++

/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mergedproxymodel.h"
#include <QStringList>
#include <QtDebug>
#include <limits>
std::size_t hash_value(const QModelIndex& index) {
return qHash(index);
}
MergedProxyModel::MergedProxyModel(QObject* parent)
: QAbstractProxyModel(parent),
resetting_model_(NULL)
{
}
MergedProxyModel::~MergedProxyModel() {
DeleteAllMappings();
}
void MergedProxyModel::DeleteAllMappings() {
MappingContainer::index<tag_by_pointer>::type::iterator begin =
mappings_.get<tag_by_pointer>().begin();
MappingContainer::index<tag_by_pointer>::type::iterator end =
mappings_.get<tag_by_pointer>().end();
qDeleteAll(begin, end);
}
void MergedProxyModel::AddSubModel(const QModelIndex& source_parent,
QAbstractItemModel* submodel) {
connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset()));
connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
connect(submodel, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(RowsInserted(QModelIndex,int,int)));
connect(submodel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(RowsRemoved(QModelIndex,int,int)));
QModelIndex proxy_parent = mapFromSource(source_parent);
const int rows = submodel->rowCount();
if (rows)
beginInsertRows(proxy_parent, 0, rows-1);
merge_points_.insert(submodel, source_parent);
if (rows)
endInsertRows();
}
void MergedProxyModel::RemoveSubModel(const QModelIndex &source_parent) {
// Find the submodel that the parent corresponded to
QAbstractItemModel* submodel = merge_points_.key(source_parent);
merge_points_.remove(submodel);
// The submodel might have been deleted already so we must be careful not
// to dereference it.
// Remove all the children of the item that got deleted
QModelIndex proxy_parent = mapFromSource(source_parent);
// We can't know how many children it had, since we can't dereference it
resetting_model_ = submodel;
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
endRemoveRows();
resetting_model_ = NULL;
// Delete all the mappings that reference the submodel
MappingContainer::index<tag_by_pointer>::type::iterator it =
mappings_.get<tag_by_pointer>().begin();
MappingContainer::index<tag_by_pointer>::type::iterator end =
mappings_.get<tag_by_pointer>().end();
while (it != end) {
if ((*it)->source_index.model() == submodel) {
delete *it;
it = mappings_.get<tag_by_pointer>().erase(it);
} else {
++it;
}
}
}
void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) {
if (sourceModel()) {
disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(RowsInserted(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(RowsRemoved(QModelIndex,int,int)));
disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(DataChanged(QModelIndex,QModelIndex)));
disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()),
this, SLOT(LayoutAboutToBeChanged()));
disconnect(sourceModel(), SIGNAL(layoutChanged()),
this, SLOT(LayoutChanged()));
}
QAbstractProxyModel::setSourceModel(source_model);
connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(RowsInserted(QModelIndex,int,int)));
connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(RowsRemoved(QModelIndex,int,int)));
connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(DataChanged(QModelIndex,QModelIndex)));
connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()),
this, SLOT(LayoutAboutToBeChanged()));
connect(sourceModel(), SIGNAL(layoutChanged()),
this, SLOT(LayoutChanged()));
}
void MergedProxyModel::SourceModelReset() {
// Delete all mappings
DeleteAllMappings();
// Clear the containers
mappings_.clear();
merge_points_.clear();
// Reset the proxy
reset();
}
void MergedProxyModel::SubModelReset() {
QAbstractItemModel* submodel = static_cast<QAbstractItemModel*>(sender());
// TODO: When we require Qt 4.6, use beginResetModel() and endResetModel()
// in LibraryModel and catch those here - that will let us do away with this
// std::numeric_limits<int>::max() hack.
// Remove all the children of the item that got deleted
QModelIndex source_parent = merge_points_.value(submodel);
QModelIndex proxy_parent = mapFromSource(source_parent);
// We can't know how many children it had, since it's already disappeared...
resetting_model_ = submodel;
beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
endRemoveRows();
resetting_model_ = NULL;
// Delete all the mappings that reference the submodel
MappingContainer::index<tag_by_pointer>::type::iterator it =
mappings_.get<tag_by_pointer>().begin();
MappingContainer::index<tag_by_pointer>::type::iterator end =
mappings_.get<tag_by_pointer>().end();
while (it != end) {
if ((*it)->source_index.model() == submodel) {
delete *it;
it = mappings_.get<tag_by_pointer>().erase(it);
} else {
++it;
}
}
// "Insert" items from the newly reset submodel
int count = submodel->rowCount();
if (count) {
beginInsertRows(proxy_parent, 0, count-1);
endInsertRows();
}
emit SubModelReset(proxy_parent, submodel);
}
QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_parent,
QAbstractItemModel* model) const {
if (!source_parent.isValid() && model != sourceModel())
return merge_points_.value(model);
return source_parent;
}
void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent,
int start, int end) {
beginInsertRows(mapFromSource(GetActualSourceParent(
source_parent, static_cast<QAbstractItemModel*>(sender()))),
start, end);
}
void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) {
endInsertRows();
}
void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent,
int start, int end) {
beginRemoveRows(mapFromSource(GetActualSourceParent(
source_parent, static_cast<QAbstractItemModel*>(sender()))),
start, end);
}
void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) {
endRemoveRows();
}
QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index) const {
if (!proxy_index.isValid())
return QModelIndex();
Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer());
if (mappings_.get<tag_by_pointer>().find(mapping) ==
mappings_.get<tag_by_pointer>().end())
return QModelIndex();
if (mapping->source_index.model() == resetting_model_)
return QModelIndex();
return mapping->source_index;
}
QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) const {
if (!source_index.isValid())
return QModelIndex();
if (source_index.model() == resetting_model_)
return QModelIndex();
// Add a mapping if we don't have one already
MappingContainer::index<tag_by_source>::type::iterator it =
mappings_.get<tag_by_source>().find(source_index);
Mapping* mapping;
if (it != mappings_.get<tag_by_source>().end()) {
mapping = *it;
} else {
mapping = new Mapping(source_index);
const_cast<MergedProxyModel*>(this)->mappings_.insert(mapping);
}
return createIndex(source_index.row(), source_index.column(), mapping);
}
QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &parent) const {
QModelIndex source_index;
if (!parent.isValid()) {
source_index = sourceModel()->index(row, column, QModelIndex());
} else {
QModelIndex source_parent = mapToSource(parent);
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model)
source_index = child_model->index(row, column, QModelIndex());
else
source_index = source_parent.model()->index(row, column, source_parent);
}
return mapFromSource(source_index);
}
QModelIndex MergedProxyModel::parent(const QModelIndex &child) const {
QModelIndex source_child = mapToSource(child);
if (source_child.model() == sourceModel())
return mapFromSource(source_child.parent());
if (!source_child.parent().isValid())
return mapFromSource(merge_points_.value(GetModel(source_child)));
return mapFromSource(source_child.parent());
}
int MergedProxyModel::rowCount(const QModelIndex &parent) const {
if (!parent.isValid())
return sourceModel()->rowCount(QModelIndex());
QModelIndex source_parent = mapToSource(parent);
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model) {
// Query the source model but disregard what it says, so it gets a chance
// to lazy load
source_parent.model()->rowCount(source_parent);
return child_model->rowCount(QModelIndex());
}
return source_parent.model()->rowCount(source_parent);
}
int MergedProxyModel::columnCount(const QModelIndex &parent) const {
if (!parent.isValid())
return sourceModel()->columnCount(QModelIndex());
QModelIndex source_parent = mapToSource(parent);
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model)
return child_model->columnCount(QModelIndex());
return source_parent.model()->columnCount(source_parent);
}
bool MergedProxyModel::hasChildren(const QModelIndex &parent) const {
if (!parent.isValid())
return sourceModel()->hasChildren(QModelIndex());
QModelIndex source_parent = mapToSource(parent);
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
if (child_model)
return child_model->hasChildren(QModelIndex()) ||
source_parent.model()->hasChildren(source_parent);
return source_parent.model()->hasChildren(source_parent);
}
QVariant MergedProxyModel::data(const QModelIndex &proxyIndex, int role) const {
QModelIndex source_index = mapToSource(proxyIndex);
return source_index.model()->data(source_index, role);
}
QMap<int, QVariant> MergedProxyModel::itemData(const QModelIndex& proxy_index) const {
QModelIndex source_index = mapToSource(proxy_index);
if (!source_index.isValid())
return sourceModel()->itemData(QModelIndex());
return source_index.model()->itemData(source_index);
}
Qt::ItemFlags MergedProxyModel::flags(const QModelIndex &index) const {
QModelIndex source_index = mapToSource(index);
if (!source_index.isValid())
return sourceModel()->flags(QModelIndex());
return source_index.model()->flags(source_index);
}
bool MergedProxyModel::setData(const QModelIndex &index, const QVariant &value,
int role) {
QModelIndex source_index = mapToSource(index);
if (!source_index.isValid())
return sourceModel()->setData(index, value, role);
return GetModel(index)->setData(index, value, role);
}
QStringList MergedProxyModel::mimeTypes() const {
QStringList ret;
ret << sourceModel()->mimeTypes();
foreach (const QAbstractItemModel* model, merge_points_.keys()) {
ret << model->mimeTypes();
}
return ret;
}
QMimeData* MergedProxyModel::mimeData(const QModelIndexList &indexes) const {
if (indexes.isEmpty())
return 0;
// Only ask the first index's model
const QAbstractItemModel* model = mapToSource(indexes[0]).model();
// Only ask about the indexes that are actually in that model
QModelIndexList indexes_in_model;
foreach (const QModelIndex& proxy_index, indexes) {
QModelIndex source_index = mapToSource(proxy_index);
if (source_index.model() != model)
continue;
indexes_in_model << source_index;
}
return model->mimeData(indexes_in_model);
}
QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index) const {
if (!proxy_index.isValid())
return QModelIndex();
QModelIndex source_index = mapToSource(proxy_index);
if (source_index.model() == sourceModel())
return source_index;
return merge_points_.value(GetModel(source_index));
}
bool MergedProxyModel::canFetchMore(const QModelIndex &parent) const {
QModelIndex source_index = mapToSource(parent);
if (!source_index.isValid())
return sourceModel()->canFetchMore(QModelIndex());
return source_index.model()->canFetchMore(source_index);
}
void MergedProxyModel::fetchMore(const QModelIndex& parent) {
QModelIndex source_index = mapToSource(parent);
if (!source_index.isValid())
sourceModel()->fetchMore(QModelIndex());
else
GetModel(source_index)->fetchMore(source_index);
}
QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index) const {
// This is essentially const_cast<QAbstractItemModel*>(source_index.model()),
// but without the const_cast
const QAbstractItemModel* const_model = source_index.model();
if (const_model == sourceModel())
return sourceModel();
foreach (QAbstractItemModel* submodel, merge_points_.keys()) {
if (submodel == const_model)
return submodel;
}
return NULL;
}
void MergedProxyModel::DataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right) {
emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
}
void MergedProxyModel::LayoutAboutToBeChanged() {
old_merge_points_.clear();
foreach (QAbstractItemModel* key, merge_points_.keys()) {
old_merge_points_[key] = merge_points_.value(key);
}
}
void MergedProxyModel::LayoutChanged() {
foreach (QAbstractItemModel* key, merge_points_.keys()) {
if (!old_merge_points_.contains(key))
continue;
const int old_row = old_merge_points_[key].row();
const int new_row = merge_points_[key].row();
if (old_row != new_row) {
reset();
return;
}
}
}