// vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau 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 #include "placetreemodel.h" // Qt #include // KF #include #include // Local #include namespace Gwenview { /** * Here is how the mapping work: * * Place1 Node(dirModel1, QUrl()) * Photos Node(dirModel1, place1Url) * 2008 Node(dirModel1, place1Url/Photos) * 2009 Node(dirModel1, place1Url/Photos) * * Place2 Node(dirModel2, QUrl()) * ... * 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() { } Node(SortedDirModel *_model, const QUrl &_parentUrl) : model(_model) , parentUrl(_parentUrl) { } SortedDirModel *model = nullptr; QUrl parentUrl; bool isPlace() const { return !parentUrl.isValid(); } }; using NodeHash = QHash; using NodeHashMap = QMap; struct PlaceTreeModelPrivate { PlaceTreeModel *q = nullptr; KFilePlacesModel *mPlacesModel = nullptr; QList mDirModels; mutable NodeHashMap mNodes; Node nodeForIndex(const QModelIndex &index) const { Q_ASSERT(index.isValid()); Q_ASSERT(index.internalPointer()); return *static_cast(index.internalPointer()); } Node *createNode(SortedDirModel *dirModel, const QUrl &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 QUrl &url) const { const QModelIndex dirIndex = dirModel->indexForUrl(url); const QModelIndex parentDirIndex = dirIndex.parent(); QUrl 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 QUrl &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, QUrl()); return q->createIndex(row, 0, node); } QModelIndex dirIndexForNode(const Node &node, const QModelIndex &index) const { if (node.isPlace()) { return {}; } 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, &KFilePlacesModel::rowsInserted, this, &PlaceTreeModel::slotPlacesRowsInserted); connect(d->mPlacesModel, &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlaceTreeModel::slotPlacesRowsAboutToBeRemoved); // Bootstrap if (d->mPlacesModel->rowCount() > 0) { slotPlacesRowsInserted(QModelIndex(), 0, d->mPlacesModel->rowCount() - 1); } } PlaceTreeModel::~PlaceTreeModel() { for (NodeHash *nodeHash : qAsConst(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 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 {}; } 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 {}; } } Node parentNode = d->nodeForIndex(parent); const QModelIndex parentDirIndex = d->dirIndexForNode(parentNode, parent); SortedDirModel *dirModel = parentNode.model; QUrl parentUrl = dirModel->urlForIndex(parentDirIndex); if (!parentUrl.isValid()) { // parent is a place parentUrl = dirModel->dirLister()->url(); if (!parentUrl.isValid()) { return {}; } } return d->createIndexForDirChild(dirModel, parentUrl, row, column); } QModelIndex PlaceTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return {}; } const Node node = d->nodeForIndex(index); if (node.isPlace()) { return {}; } 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()) { const QModelIndex placeIndex = d->mPlacesModel->index(parent.row(), parent.column()); const QUrl url = KFilePlacesModel::convertedUrl(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) { auto dirModel = new SortedDirModel(this); connect(dirModel, &SortedDirModel::rowsAboutToBeInserted, this, &PlaceTreeModel::slotDirRowsAboutToBeInserted); connect(dirModel, &SortedDirModel::rowsInserted, this, &PlaceTreeModel::slotDirRowsInserted); connect(dirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &PlaceTreeModel::slotDirRowsAboutToBeRemoved); connect(dirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &PlaceTreeModel::slotDirRowsRemoved); 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) { auto dirModel = static_cast(sender()); QModelIndex parentIndex; if (parentDirIndex.isValid()) { const QUrl 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) { auto dirModel = static_cast(sender()); QModelIndex parentIndex; if (parentDirIndex.isValid()) { const QUrl 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(); } QUrl PlaceTreeModel::urlForIndex(const QModelIndex &index) const { const Node node = d->nodeForIndex(index); if (node.isPlace()) { QModelIndex placeIndex = d->mPlacesModel->index(index.row(), 0); return KFilePlacesModel::convertedUrl(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 #include "moc_placetreemodel.cpp"