2014-11-19 02:23:05 +00:00
|
|
|
// 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"
|
2014-11-19 02:23:05 +00:00
|
|
|
|
|
|
|
// Qt
|
|
|
|
|
|
|
|
// KDE
|
|
|
|
#include <KDebug>
|
|
|
|
#include <KDirLister>
|
|
|
|
#include <KFilePlacesModel>
|
|
|
|
|
|
|
|
// Local
|
2014-11-19 19:44:19 +00:00
|
|
|
#include <lib/sorteddirmodel.h>
|
2014-11-19 02:23:05 +00:00
|
|
|
|
|
|
|
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
|