kde-extraapps/gwenview/lib/placetreemodel.cpp

381 lines
12 KiB
C++
Raw Normal View History

// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2009 Aurélien Gâteau <agateau@kde.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
*/
// Self
2015-02-27 11:02:43 +00:00
#include "moc_placetreemodel.cpp"
// Qt
// KDE
#include <KDebug>
#include <KDirLister>
#include <KFilePlacesModel>
// Local
#include <lib/sorteddirmodel.h>
namespace Gwenview
{
/**
* Here is how the mapping work:
*
* Place1 Node(dirModel1, KUrl())
* Photos Node(dirModel1, place1Url)
* 2008 Node(dirModel1, place1Url/Photos)
* 2009 Node(dirModel1, place1Url/Photos)
*
* Place2 Node(dirModel2, KUrl())
* ...
* Node contains the parent url, not the url of the node itself, because
* for some unknown reason when accessing rows from a slot connected to
* rowsInserted(), the rows are unsorted, they appear in KDirModel natural
* order. Further access to the rows are correctly sorted! This confuses
* QTreeView a lot (symptoms are mixed tooltips, filled nodes appearing
* empty on first expand)
*
* I could not determine whether it's a bug or not, and if it's in my model
* code, in QSortFilterProxyModel or somewhere else.
*/
struct Node
{
Node()
: model(0)
{}
Node(SortedDirModel* _model, const KUrl& _parentUrl)
: model(_model)
, parentUrl(_parentUrl)
{}
SortedDirModel* model;
KUrl parentUrl;
bool isPlace() const
{
return !parentUrl.isValid();
}
};
typedef QHash<KUrl, Node*> NodeHash;
typedef QMap<SortedDirModel*, NodeHash*> NodeHashMap;
struct PlaceTreeModelPrivate
{
PlaceTreeModel* q;
KFilePlacesModel* mPlacesModel;
QList<SortedDirModel*> mDirModels;
mutable NodeHashMap mNodes;
Node nodeForIndex(const QModelIndex& index) const
{
Q_ASSERT(index.isValid());
Q_ASSERT(index.internalPointer());
return *static_cast<Node*>(index.internalPointer());
}
Node* createNode(SortedDirModel* dirModel, const KUrl& parentUrl) const
{
NodeHashMap::iterator nhmIt = mNodes.find(dirModel);
if (nhmIt == mNodes.end()) {
nhmIt = mNodes.insert(dirModel, new NodeHash);
}
NodeHash* nodeHash = nhmIt.value();
NodeHash::iterator nhIt = nodeHash->find(parentUrl);
if (nhIt == nodeHash->end()) {
nhIt = nodeHash->insert(parentUrl, new Node(dirModel, parentUrl));
}
return nhIt.value();
}
QModelIndex createIndexForDir(SortedDirModel* dirModel, const KUrl& url) const
{
QModelIndex dirIndex = dirModel->indexForUrl(url);
QModelIndex parentDirIndex = dirIndex.parent();
KUrl parentUrl;
if (parentDirIndex.isValid()) {
parentUrl = dirModel->urlForIndex(parentDirIndex);
} else {
parentUrl = dirModel->dirLister()->url();
}
return createIndexForDirChild(dirModel, parentUrl, dirIndex.row(), dirIndex.column());
}
QModelIndex createIndexForDirChild(SortedDirModel* dirModel, const KUrl& parentUrl, int row, int column) const
{
Q_ASSERT(parentUrl.isValid());
Node* node = createNode(dirModel, parentUrl);
return q->createIndex(row, column, node);
}
QModelIndex createIndexForPlace(SortedDirModel* dirModel) const
{
int row = mDirModels.indexOf(dirModel);
Q_ASSERT(row != -1);
Node* node = createNode(dirModel, KUrl());
return q->createIndex(row, 0, node);
}
QModelIndex dirIndexForNode(const Node& node, const QModelIndex& index) const
{
if (node.isPlace()) {
return QModelIndex();
}
Q_ASSERT(node.parentUrl.isValid());
const QModelIndex parentDirIndex = node.model->indexForUrl(node.parentUrl);
return node.model->index(index.row(), index.column(), parentDirIndex);
}
};
PlaceTreeModel::PlaceTreeModel(QObject* parent)
: QAbstractItemModel(parent)
, d(new PlaceTreeModelPrivate)
{
d->q = this;
d->mPlacesModel = new KFilePlacesModel(this);
connect(d->mPlacesModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
SLOT(slotPlacesRowsInserted(QModelIndex,int,int)));
connect(d->mPlacesModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
SLOT(slotPlacesRowsAboutToBeRemoved(QModelIndex,int,int)));
// Bootstrap
slotPlacesRowsInserted(QModelIndex(), 0, d->mPlacesModel->rowCount() - 1);
}
PlaceTreeModel::~PlaceTreeModel()
{
Q_FOREACH(NodeHash * nodeHash, d->mNodes) {
qDeleteAll(*nodeHash);
}
qDeleteAll(d->mNodes);
delete d;
}
int PlaceTreeModel::columnCount(const QModelIndex&) const
{
return 1;
}
QVariant PlaceTreeModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
QVariant value;
const Node node = d->nodeForIndex(index);
if (node.isPlace()) {
const QModelIndex placesIndex = d->mPlacesModel->index(index.row(), index.column());
value = d->mPlacesModel->data(placesIndex, role);
} else {
const QModelIndex dirIndex = d->dirIndexForNode(node, index);
value = node.model->data(dirIndex, role);
}
return value;
}
QModelIndex PlaceTreeModel::index(int row, int column, const QModelIndex& parent) const
{
if (column != 0) {
return QModelIndex();
}
if (!parent.isValid()) {
// User wants to create a places index
if (0 <= row && row < d->mDirModels.size()) {
SortedDirModel* dirModel = d->mDirModels[row];
return d->createIndexForPlace(dirModel);
} else {
return QModelIndex();
}
}
Node parentNode = d->nodeForIndex(parent);
QModelIndex parentDirIndex = d->dirIndexForNode(parentNode, parent);
SortedDirModel* dirModel = parentNode.model;
KUrl parentUrl = dirModel->urlForIndex(parentDirIndex);
if (!parentUrl.isValid()) {
// parent is a place
parentUrl = dirModel->dirLister()->url();
if (!parentUrl.isValid()) {
return QModelIndex();
}
}
return d->createIndexForDirChild(dirModel, parentUrl, row, column);
}
QModelIndex PlaceTreeModel::parent(const QModelIndex& index) const
{
if (!index.isValid()) {
return QModelIndex();
}
const Node node = d->nodeForIndex(index);
if (node.isPlace()) {
return QModelIndex();
}
if (node.parentUrl == node.model->dirLister()->url()) {
// index is a direct child of a place
return d->createIndexForPlace(node.model);
}
return d->createIndexForDir(node.model, node.parentUrl);
}
int PlaceTreeModel::rowCount(const QModelIndex& index) const
{
if (!index.isValid()) {
// index is the invisible root item
return d->mDirModels.size();
}
// index is a place or a dir
const Node node = d->nodeForIndex(index);
const QModelIndex dirIndex = d->dirIndexForNode(node, index);
return node.model->rowCount(dirIndex);
}
bool PlaceTreeModel::hasChildren(const QModelIndex& index) const
{
if (!index.isValid()) {
return true;
}
const Node node = d->nodeForIndex(index);
if (node.isPlace()) {
return true;
}
const QModelIndex dirIndex = d->dirIndexForNode(node, index);
return node.model->hasChildren(dirIndex);
}
bool PlaceTreeModel::canFetchMore(const QModelIndex& parent) const
{
if (!parent.isValid()) {
return d->mPlacesModel->canFetchMore(QModelIndex());
}
const Node node = d->nodeForIndex(parent);
if (!node.model->dirLister()->url().isValid()) {
// Special case to avoid calling openUrl on all places at startup
return true;
}
const QModelIndex dirIndex = d->dirIndexForNode(node, parent);
return node.model->canFetchMore(dirIndex);
}
void PlaceTreeModel::fetchMore(const QModelIndex& parent)
{
if (!parent.isValid()) {
d->mPlacesModel->fetchMore(QModelIndex());
return;
}
const Node node = d->nodeForIndex(parent);
if (!node.model->dirLister()->url().isValid()) {
QModelIndex placeIndex = d->mPlacesModel->index(parent.row(), parent.column());
KUrl url = d->mPlacesModel->url(placeIndex);
node.model->dirLister()->openUrl(url, KDirLister::Keep);
return;
}
const QModelIndex dirIndex = d->dirIndexForNode(node, parent);
node.model->fetchMore(dirIndex);
}
void PlaceTreeModel::slotPlacesRowsInserted(const QModelIndex& /*parent*/, int start, int end)
{
beginInsertRows(QModelIndex(), start, end);
for (int row = start; row <= end; ++row) {
SortedDirModel* dirModel = new SortedDirModel(this);
connect(dirModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
SLOT(slotDirRowsAboutToBeInserted(QModelIndex,int,int)));
connect(dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
SLOT(slotDirRowsInserted(QModelIndex,int,int)));
connect(dirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
SLOT(slotDirRowsAboutToBeRemoved(QModelIndex,int,int)));
connect(dirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
SLOT(slotDirRowsRemoved(QModelIndex,int,int)));
d->mDirModels.insert(row, dirModel);
KDirLister* lister = dirModel->dirLister();
lister->setDirOnlyMode(true);
}
endInsertRows();
}
void PlaceTreeModel::slotPlacesRowsAboutToBeRemoved(const QModelIndex&, int start, int end)
{
beginRemoveRows(QModelIndex(), start, end);
for (int row = end; row >= start; --row) {
SortedDirModel* dirModel = d->mDirModels.takeAt(row);
delete d->mNodes.take(dirModel);
delete dirModel;
}
endRemoveRows();
}
void PlaceTreeModel::slotDirRowsAboutToBeInserted(const QModelIndex& parentDirIndex, int start, int end)
{
SortedDirModel* dirModel = static_cast<SortedDirModel*>(sender());
QModelIndex parentIndex;
if (parentDirIndex.isValid()) {
KUrl url = dirModel->urlForIndex(parentDirIndex);
parentIndex = d->createIndexForDir(dirModel, url);
} else {
parentIndex = d->createIndexForPlace(dirModel);
}
beginInsertRows(parentIndex, start, end);
}
void PlaceTreeModel::slotDirRowsInserted(const QModelIndex&, int, int)
{
endInsertRows();
}
void PlaceTreeModel::slotDirRowsAboutToBeRemoved(const QModelIndex& parentDirIndex, int start, int end)
{
SortedDirModel* dirModel = static_cast<SortedDirModel*>(sender());
QModelIndex parentIndex;
if (parentDirIndex.isValid()) {
KUrl url = dirModel->urlForIndex(parentDirIndex);
parentIndex = d->createIndexForDir(dirModel, url);
} else {
parentIndex = d->createIndexForPlace(dirModel);
}
beginRemoveRows(parentIndex, start, end);
}
void PlaceTreeModel::slotDirRowsRemoved(const QModelIndex&, int, int)
{
endRemoveRows();
}
KUrl PlaceTreeModel::urlForIndex(const QModelIndex& index) const
{
const Node node = d->nodeForIndex(index);
if (node.isPlace()) {
QModelIndex placeIndex = d->mPlacesModel->index(index.row(), 0);
return d->mPlacesModel->url(placeIndex);
}
const QModelIndex parentDirIndex = node.model->indexForUrl(node.parentUrl);
const QModelIndex dirIndex = node.model->index(index.row(), index.column(), parentDirIndex);
return node.model->urlForIndex(dirIndex);
}
} // namespace