File indexing completed on 2024-04-28 04:18:52

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2009 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "placetreemodel.h"
0023 
0024 // Qt
0025 #include <QUrl>
0026 
0027 // KF
0028 #include <KDirLister>
0029 #include <KFilePlacesModel>
0030 
0031 // Local
0032 #include <lib/semanticinfo/sorteddirmodel.h>
0033 
0034 namespace Gwenview
0035 {
0036 /**
0037  * Here is how the mapping work:
0038  *
0039  * Place1     Node(dirModel1, QUrl())
0040  *   Photos   Node(dirModel1, place1Url)
0041  *     2008   Node(dirModel1, place1Url/Photos)
0042  *     2009   Node(dirModel1, place1Url/Photos)
0043  *
0044  * Place2     Node(dirModel2, QUrl())
0045  * ...
0046  * Node contains the parent url, not the url of the node itself, because
0047  * for some unknown reason when accessing rows from a slot connected to
0048  * rowsInserted(), the rows are unsorted, they appear in KDirModel natural
0049  * order. Further access to the rows are correctly sorted! This confuses
0050  * QTreeView a lot (symptoms are mixed tooltips, filled nodes appearing
0051  * empty on first expand)
0052  *
0053  * I could not determine whether it's a bug or not, and if it's in my model
0054  * code, in QSortFilterProxyModel or somewhere else.
0055  */
0056 
0057 struct Node {
0058     Node()
0059     {
0060     }
0061 
0062     Node(SortedDirModel *_model, const QUrl &_parentUrl)
0063         : model(_model)
0064         , parentUrl(_parentUrl)
0065     {
0066     }
0067 
0068     SortedDirModel *model = nullptr;
0069     QUrl parentUrl;
0070 
0071     bool isPlace() const
0072     {
0073         return !parentUrl.isValid();
0074     }
0075 };
0076 
0077 using NodeHash = QHash<QUrl, Node *>;
0078 using NodeHashMap = QMap<SortedDirModel *, NodeHash *>;
0079 
0080 struct PlaceTreeModelPrivate {
0081     PlaceTreeModel *q = nullptr;
0082     KFilePlacesModel *mPlacesModel = nullptr;
0083     QList<SortedDirModel *> mDirModels;
0084     mutable NodeHashMap mNodes;
0085 
0086     Node nodeForIndex(const QModelIndex &index) const
0087     {
0088         Q_ASSERT(index.isValid());
0089         Q_ASSERT(index.internalPointer());
0090         return *static_cast<Node *>(index.internalPointer());
0091     }
0092 
0093     Node *createNode(SortedDirModel *dirModel, const QUrl &parentUrl) const
0094     {
0095         NodeHashMap::iterator nhmIt = mNodes.find(dirModel);
0096         if (nhmIt == mNodes.end()) {
0097             nhmIt = mNodes.insert(dirModel, new NodeHash);
0098         }
0099         NodeHash *nodeHash = nhmIt.value();
0100 
0101         NodeHash::iterator nhIt = nodeHash->find(parentUrl);
0102         if (nhIt == nodeHash->end()) {
0103             nhIt = nodeHash->insert(parentUrl, new Node(dirModel, parentUrl));
0104         }
0105         return nhIt.value();
0106     }
0107 
0108     QModelIndex createIndexForDir(SortedDirModel *dirModel, const QUrl &url) const
0109     {
0110         const QModelIndex dirIndex = dirModel->indexForUrl(url);
0111         const QModelIndex parentDirIndex = dirIndex.parent();
0112         QUrl parentUrl;
0113         if (parentDirIndex.isValid()) {
0114             parentUrl = dirModel->urlForIndex(parentDirIndex);
0115         } else {
0116             parentUrl = dirModel->dirLister()->url();
0117         }
0118         return createIndexForDirChild(dirModel, parentUrl, dirIndex.row(), dirIndex.column());
0119     }
0120 
0121     QModelIndex createIndexForDirChild(SortedDirModel *dirModel, const QUrl &parentUrl, int row, int column) const
0122     {
0123         Q_ASSERT(parentUrl.isValid());
0124         Node *node = createNode(dirModel, parentUrl);
0125         return q->createIndex(row, column, node);
0126     }
0127 
0128     QModelIndex createIndexForPlace(SortedDirModel *dirModel) const
0129     {
0130         int row = mDirModels.indexOf(dirModel);
0131         Q_ASSERT(row != -1);
0132         Node *node = createNode(dirModel, QUrl());
0133         return q->createIndex(row, 0, node);
0134     }
0135 
0136     QModelIndex dirIndexForNode(const Node &node, const QModelIndex &index) const
0137     {
0138         if (node.isPlace()) {
0139             return {};
0140         }
0141         Q_ASSERT(node.parentUrl.isValid());
0142         const QModelIndex parentDirIndex = node.model->indexForUrl(node.parentUrl);
0143         return node.model->index(index.row(), index.column(), parentDirIndex);
0144     }
0145 };
0146 
0147 PlaceTreeModel::PlaceTreeModel(QObject *parent)
0148     : QAbstractItemModel(parent)
0149     , d(new PlaceTreeModelPrivate)
0150 {
0151     d->q = this;
0152 
0153     d->mPlacesModel = new KFilePlacesModel(this);
0154     connect(d->mPlacesModel, &KFilePlacesModel::rowsInserted, this, &PlaceTreeModel::slotPlacesRowsInserted);
0155     connect(d->mPlacesModel, &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlaceTreeModel::slotPlacesRowsAboutToBeRemoved);
0156 
0157     // Bootstrap
0158     if (d->mPlacesModel->rowCount() > 0) {
0159         slotPlacesRowsInserted(QModelIndex(), 0, d->mPlacesModel->rowCount() - 1);
0160     }
0161 }
0162 
0163 PlaceTreeModel::~PlaceTreeModel()
0164 {
0165     for (NodeHash *nodeHash : qAsConst(d->mNodes)) {
0166         qDeleteAll(*nodeHash);
0167     }
0168     qDeleteAll(d->mNodes);
0169     delete d;
0170 }
0171 
0172 int PlaceTreeModel::columnCount(const QModelIndex &) const
0173 {
0174     return 1;
0175 }
0176 
0177 QVariant PlaceTreeModel::data(const QModelIndex &index, int role) const
0178 {
0179     if (!index.isValid()) {
0180         return {};
0181     }
0182     QVariant value;
0183     const Node node = d->nodeForIndex(index);
0184     if (node.isPlace()) {
0185         const QModelIndex placesIndex = d->mPlacesModel->index(index.row(), index.column());
0186         value = d->mPlacesModel->data(placesIndex, role);
0187     } else {
0188         const QModelIndex dirIndex = d->dirIndexForNode(node, index);
0189         value = node.model->data(dirIndex, role);
0190     }
0191     return value;
0192 }
0193 
0194 QModelIndex PlaceTreeModel::index(int row, int column, const QModelIndex &parent) const
0195 {
0196     if (column != 0) {
0197         return {};
0198     }
0199     if (!parent.isValid()) {
0200         // User wants to create a places index
0201         if (0 <= row && row < d->mDirModels.size()) {
0202             SortedDirModel *dirModel = d->mDirModels[row];
0203             return d->createIndexForPlace(dirModel);
0204         } else {
0205             return {};
0206         }
0207     }
0208 
0209     Node parentNode = d->nodeForIndex(parent);
0210     const QModelIndex parentDirIndex = d->dirIndexForNode(parentNode, parent);
0211 
0212     SortedDirModel *dirModel = parentNode.model;
0213     QUrl parentUrl = dirModel->urlForIndex(parentDirIndex);
0214     if (!parentUrl.isValid()) {
0215         // parent is a place
0216         parentUrl = dirModel->dirLister()->url();
0217         if (!parentUrl.isValid()) {
0218             return {};
0219         }
0220     }
0221     return d->createIndexForDirChild(dirModel, parentUrl, row, column);
0222 }
0223 
0224 QModelIndex PlaceTreeModel::parent(const QModelIndex &index) const
0225 {
0226     if (!index.isValid()) {
0227         return {};
0228     }
0229     const Node node = d->nodeForIndex(index);
0230     if (node.isPlace()) {
0231         return {};
0232     }
0233     if (node.parentUrl == node.model->dirLister()->url()) {
0234         // index is a direct child of a place
0235         return d->createIndexForPlace(node.model);
0236     }
0237     return d->createIndexForDir(node.model, node.parentUrl);
0238 }
0239 
0240 int PlaceTreeModel::rowCount(const QModelIndex &index) const
0241 {
0242     if (!index.isValid()) {
0243         // index is the invisible root item
0244         return d->mDirModels.size();
0245     }
0246 
0247     // index is a place or a dir
0248     const Node node = d->nodeForIndex(index);
0249     const QModelIndex dirIndex = d->dirIndexForNode(node, index);
0250     return node.model->rowCount(dirIndex);
0251 }
0252 
0253 bool PlaceTreeModel::hasChildren(const QModelIndex &index) const
0254 {
0255     if (!index.isValid()) {
0256         return true;
0257     }
0258     const Node node = d->nodeForIndex(index);
0259     if (node.isPlace()) {
0260         return true;
0261     }
0262     const QModelIndex dirIndex = d->dirIndexForNode(node, index);
0263     return node.model->hasChildren(dirIndex);
0264 }
0265 
0266 bool PlaceTreeModel::canFetchMore(const QModelIndex &parent) const
0267 {
0268     if (!parent.isValid()) {
0269         return d->mPlacesModel->canFetchMore(QModelIndex());
0270     }
0271     const Node node = d->nodeForIndex(parent);
0272     if (!node.model->dirLister()->url().isValid()) {
0273         // Special case to avoid calling openUrl on all places at startup
0274         return true;
0275     }
0276     const QModelIndex dirIndex = d->dirIndexForNode(node, parent);
0277     return node.model->canFetchMore(dirIndex);
0278 }
0279 
0280 void PlaceTreeModel::fetchMore(const QModelIndex &parent)
0281 {
0282     if (!parent.isValid()) {
0283         d->mPlacesModel->fetchMore(QModelIndex());
0284         return;
0285     }
0286     const Node node = d->nodeForIndex(parent);
0287     if (!node.model->dirLister()->url().isValid()) {
0288         const QModelIndex placeIndex = d->mPlacesModel->index(parent.row(), parent.column());
0289         const QUrl url = KFilePlacesModel::convertedUrl(d->mPlacesModel->url(placeIndex));
0290         node.model->dirLister()->openUrl(url, KDirLister::Keep);
0291         return;
0292     }
0293     const QModelIndex dirIndex = d->dirIndexForNode(node, parent);
0294     node.model->fetchMore(dirIndex);
0295 }
0296 
0297 void PlaceTreeModel::slotPlacesRowsInserted(const QModelIndex & /*parent*/, int start, int end)
0298 {
0299     beginInsertRows(QModelIndex(), start, end);
0300     for (int row = start; row <= end; ++row) {
0301         auto dirModel = new SortedDirModel(this);
0302         connect(dirModel, &SortedDirModel::rowsAboutToBeInserted, this, &PlaceTreeModel::slotDirRowsAboutToBeInserted);
0303         connect(dirModel, &SortedDirModel::rowsInserted, this, &PlaceTreeModel::slotDirRowsInserted);
0304         connect(dirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &PlaceTreeModel::slotDirRowsAboutToBeRemoved);
0305         connect(dirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &PlaceTreeModel::slotDirRowsRemoved);
0306 
0307         d->mDirModels.insert(row, dirModel);
0308         KDirLister *lister = dirModel->dirLister();
0309         lister->setDirOnlyMode(true);
0310     }
0311     endInsertRows();
0312 }
0313 
0314 void PlaceTreeModel::slotPlacesRowsAboutToBeRemoved(const QModelIndex &, int start, int end)
0315 {
0316     beginRemoveRows(QModelIndex(), start, end);
0317     for (int row = end; row >= start; --row) {
0318         SortedDirModel *dirModel = d->mDirModels.takeAt(row);
0319         delete d->mNodes.take(dirModel);
0320         delete dirModel;
0321     }
0322     endRemoveRows();
0323 }
0324 
0325 void PlaceTreeModel::slotDirRowsAboutToBeInserted(const QModelIndex &parentDirIndex, int start, int end)
0326 {
0327     auto dirModel = static_cast<SortedDirModel *>(sender());
0328     QModelIndex parentIndex;
0329     if (parentDirIndex.isValid()) {
0330         const QUrl url = dirModel->urlForIndex(parentDirIndex);
0331         parentIndex = d->createIndexForDir(dirModel, url);
0332     } else {
0333         parentIndex = d->createIndexForPlace(dirModel);
0334     }
0335     beginInsertRows(parentIndex, start, end);
0336 }
0337 
0338 void PlaceTreeModel::slotDirRowsInserted(const QModelIndex &, int, int)
0339 {
0340     endInsertRows();
0341 }
0342 
0343 void PlaceTreeModel::slotDirRowsAboutToBeRemoved(const QModelIndex &parentDirIndex, int start, int end)
0344 {
0345     auto dirModel = static_cast<SortedDirModel *>(sender());
0346     QModelIndex parentIndex;
0347     if (parentDirIndex.isValid()) {
0348         const QUrl url = dirModel->urlForIndex(parentDirIndex);
0349         parentIndex = d->createIndexForDir(dirModel, url);
0350     } else {
0351         parentIndex = d->createIndexForPlace(dirModel);
0352     }
0353     beginRemoveRows(parentIndex, start, end);
0354 }
0355 
0356 void PlaceTreeModel::slotDirRowsRemoved(const QModelIndex &, int, int)
0357 {
0358     endRemoveRows();
0359 }
0360 
0361 QUrl PlaceTreeModel::urlForIndex(const QModelIndex &index) const
0362 {
0363     const Node node = d->nodeForIndex(index);
0364     if (node.isPlace()) {
0365         QModelIndex placeIndex = d->mPlacesModel->index(index.row(), 0);
0366         return KFilePlacesModel::convertedUrl(d->mPlacesModel->url(placeIndex));
0367     }
0368 
0369     const QModelIndex parentDirIndex = node.model->indexForUrl(node.parentUrl);
0370     const QModelIndex dirIndex = node.model->index(index.row(), index.column(), parentDirIndex);
0371     return node.model->urlForIndex(dirIndex);
0372 }
0373 
0374 } // namespace
0375 
0376 #include "moc_placetreemodel.cpp"