File indexing completed on 2024-05-05 08:25:41
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"