File indexing completed on 2024-12-01 12:36:34
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2006-2019 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kdirmodel.h" 0009 #include "kdirlister.h" 0010 #include "kfileitem.h" 0011 0012 #include "joburlcache_p.h" 0013 #include <KIconUtils> 0014 #include <KJobUiDelegate> 0015 #include <KLocalizedString> 0016 #include <KUrlMimeData> 0017 #include <kio/fileundomanager.h> 0018 #include <kio/simplejob.h> 0019 #include <kio/statjob.h> 0020 0021 #include <QBitArray> 0022 #include <QDebug> 0023 #include <QDir> 0024 #include <QDirIterator> 0025 #include <QFile> 0026 #include <QFileInfo> 0027 #include <QIcon> 0028 #include <QLocale> 0029 #include <QLoggingCategory> 0030 #include <QMimeData> 0031 #include <qplatformdefs.h> 0032 0033 #include <algorithm> 0034 0035 #ifdef Q_OS_WIN 0036 #include <qt_windows.h> 0037 #endif 0038 0039 Q_LOGGING_CATEGORY(category, "kf.kio.widgets.kdirmodel", QtInfoMsg) 0040 0041 class KDirModelNode; 0042 class KDirModelDirNode; 0043 0044 static QUrl cleanupUrl(const QUrl &url) 0045 { 0046 QUrl u = url; 0047 u.setPath(QDir::cleanPath(u.path())); // remove double slashes in the path, simplify "foo/." to "foo/", etc. 0048 u = u.adjusted(QUrl::StripTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url. 0049 if (u.scheme().startsWith(QStringLiteral("ksvn")) || u.scheme().startsWith(QStringLiteral("svn"))) { 0050 u.setQuery(QString()); 0051 u.setFragment(QString()); 0052 } 0053 return u; 0054 } 0055 0056 // We create our own tree behind the scenes to have fast lookup from an item to its parent, 0057 // and also to get the children of an item fast. 0058 class KDirModelNode 0059 { 0060 public: 0061 KDirModelNode(KDirModelDirNode *parent, const KFileItem &item) 0062 : m_item(item) 0063 , m_parent(parent) 0064 { 0065 } 0066 0067 virtual ~KDirModelNode() = default; // Required, code will delete ptrs to this or a subclass. 0068 0069 // m_item is KFileItem() for the root item 0070 const KFileItem &item() const 0071 { 0072 return m_item; 0073 } 0074 0075 virtual void setItem(const KFileItem &item) 0076 { 0077 m_item = item; 0078 } 0079 0080 KDirModelDirNode *parent() const 0081 { 0082 return m_parent; 0083 } 0084 0085 // linear search 0086 int rowNumber() const; // O(n) 0087 0088 QIcon preview() const 0089 { 0090 return m_preview; 0091 } 0092 0093 void setPreview(const QPixmap &pix) 0094 { 0095 m_preview = QIcon(); 0096 m_preview.addPixmap(pix); 0097 } 0098 0099 void setPreview(const QIcon &icn) 0100 { 0101 m_preview = icn; 0102 } 0103 0104 private: 0105 KFileItem m_item; 0106 KDirModelDirNode *const m_parent; 0107 QIcon m_preview; 0108 }; 0109 0110 // Specialization for directory nodes 0111 class KDirModelDirNode : public KDirModelNode 0112 { 0113 public: 0114 KDirModelDirNode(KDirModelDirNode *parent, const KFileItem &item) 0115 : KDirModelNode(parent, item) 0116 , m_childCount(KDirModel::ChildCountUnknown) 0117 , m_populated(false) 0118 , m_fsType(FsTypeUnknown) 0119 { 0120 // If the parent node is on the network, all children are too. Opposite is not always true. 0121 if (parent && parent->isOnNetwork()) { 0122 m_fsType = NetworkFs; 0123 } 0124 } 0125 ~KDirModelDirNode() override 0126 { 0127 qDeleteAll(m_childNodes); 0128 } 0129 QList<KDirModelNode *> m_childNodes; // owns the nodes 0130 0131 void setItem(const KFileItem &item) override 0132 { 0133 KDirModelNode::setItem(item); 0134 if (item.isNull() || !item.url().isValid()) { 0135 m_fsType = LocalFs; 0136 } else { 0137 m_fsType = FsTypeUnknown; 0138 } 0139 } 0140 0141 // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount. 0142 int childCount() const 0143 { 0144 return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); 0145 } 0146 0147 void setChildCount(int count) 0148 { 0149 m_childCount = count; 0150 } 0151 0152 bool isPopulated() const 0153 { 0154 return m_populated; 0155 } 0156 0157 void setPopulated(bool populated) 0158 { 0159 m_populated = populated; 0160 } 0161 0162 bool isOnNetwork() const 0163 { 0164 if (!item().isNull() && m_fsType == FsTypeUnknown) { 0165 m_fsType = item().isSlow() ? NetworkFs : LocalFs; 0166 } 0167 return m_fsType == NetworkFs; 0168 } 0169 0170 // For removing all child urls from the global hash. 0171 QList<QUrl> collectAllChildUrls() const 0172 { 0173 QList<QUrl> urls; 0174 urls.reserve(urls.size() + m_childNodes.size()); 0175 for (KDirModelNode *node : m_childNodes) { 0176 const KFileItem &item = node->item(); 0177 urls.append(cleanupUrl(item.url())); 0178 if (item.isDir()) { 0179 urls += static_cast<KDirModelDirNode *>(node)->collectAllChildUrls(); 0180 } 0181 } 0182 return urls; 0183 } 0184 0185 private: 0186 int m_childCount : 31; 0187 bool m_populated : 1; 0188 // Network file system? (nfs/smb/ssh) 0189 mutable enum { FsTypeUnknown, LocalFs, NetworkFs } m_fsType : 3; 0190 }; 0191 0192 int KDirModelNode::rowNumber() const 0193 { 0194 if (!m_parent) { 0195 return 0; 0196 } 0197 return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode *>(this)); 0198 } 0199 0200 //// 0201 0202 class KDirModelPrivate 0203 { 0204 public: 0205 explicit KDirModelPrivate(KDirModel *qq) 0206 : q(qq) 0207 , m_rootNode(new KDirModelDirNode(nullptr, KFileItem())) 0208 { 0209 } 0210 ~KDirModelPrivate() 0211 { 0212 delete m_rootNode; 0213 } 0214 0215 void _k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &); 0216 void _k_slotCompleted(const QUrl &directoryUrl); 0217 void _k_slotDeleteItems(const KFileItemList &); 0218 void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &); 0219 void _k_slotClear(); 0220 void _k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl); 0221 void _k_slotJobUrlsChanged(const QStringList &urlList); 0222 0223 void clear() 0224 { 0225 delete m_rootNode; 0226 m_rootNode = new KDirModelDirNode(nullptr, KFileItem()); 0227 m_showNodeForListedUrl = false; 0228 m_rootNode->setItem(KFileItem(m_dirLister->url())); 0229 } 0230 0231 // Emit expand for each parent and then return the 0232 // last known parent if there is no node for this url 0233 KDirModelNode *expandAllParentsUntil(const QUrl &url) const; 0234 0235 // Return the node for a given url, using the hash. 0236 KDirModelNode *nodeForUrl(const QUrl &url) const; 0237 KDirModelNode *nodeForIndex(const QModelIndex &index) const; 0238 QModelIndex indexForNode(KDirModelNode *node, int rowNumber = -1 /*unknown*/) const; 0239 0240 static QUrl rootParentOf(const QUrl &url) 0241 { 0242 // <url> is what we listed, and which is visible at the root of the tree 0243 // Here we want the (invisible) parent of that url 0244 QUrl parent(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); 0245 if (url.path() == QLatin1String("/")) { 0246 parent.setPath(QString()); 0247 } 0248 return parent; 0249 } 0250 0251 bool isDir(KDirModelNode *node) const 0252 { 0253 return (node == m_rootNode) || node->item().isDir(); 0254 } 0255 0256 QUrl urlForNode(KDirModelNode *node) const 0257 { 0258 /** 0259 * Queries and fragments are removed from the URL, so that the URL of 0260 * child items really starts with the URL of the parent. 0261 * 0262 * For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100 0263 * so we have to remove the query in both to be able to compare the URLs 0264 */ 0265 QUrl url; 0266 if (node == m_rootNode && !m_showNodeForListedUrl) { 0267 url = m_dirLister->url(); 0268 } else { 0269 url = node->item().url(); 0270 } 0271 if (url.scheme().startsWith(QStringLiteral("ksvn")) || url.scheme().startsWith(QStringLiteral("svn"))) { 0272 if (url.hasQuery() || url.hasFragment()) { // avoid detach if not necessary. 0273 url.setQuery(QString()); 0274 url.setFragment(QString()); // kill ref (#171117) 0275 } 0276 } 0277 return url; 0278 } 0279 0280 void removeFromNodeHash(KDirModelNode *node, const QUrl &url); 0281 void clearAllPreviews(KDirModelDirNode *node); 0282 #ifndef NDEBUG 0283 void dump(); 0284 #endif 0285 Q_DISABLE_COPY(KDirModelPrivate) 0286 0287 KDirModel *const q; 0288 KDirLister *m_dirLister = nullptr; 0289 KDirModelDirNode *m_rootNode = nullptr; 0290 KDirModel::DropsAllowed m_dropsAllowed = KDirModel::NoDrops; 0291 bool m_jobTransfersVisible = false; 0292 bool m_showNodeForListedUrl = false; 0293 // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient), 0294 // value = final url[s] being fetched 0295 QMap<KDirModelNode *, QList<QUrl>> m_urlsBeingFetched; 0296 QHash<QUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node 0297 QStringList m_allCurrentDestUrls; // list of all dest urls that have jobs on them (e.g. copy, download) 0298 }; 0299 0300 KDirModelNode *KDirModelPrivate::nodeForUrl(const QUrl &_url) const // O(1), well, O(length of url as a string) 0301 { 0302 QUrl url = cleanupUrl(_url); 0303 if (url == urlForNode(m_rootNode)) { 0304 return m_rootNode; 0305 } 0306 return m_nodeHash.value(url); 0307 } 0308 0309 void KDirModelPrivate::removeFromNodeHash(KDirModelNode *node, const QUrl &url) 0310 { 0311 if (node->item().isDir()) { 0312 const QList<QUrl> urls = static_cast<KDirModelDirNode *>(node)->collectAllChildUrls(); 0313 for (const QUrl &u : urls) { 0314 m_nodeHash.remove(u); 0315 } 0316 } 0317 m_nodeHash.remove(cleanupUrl(url)); 0318 } 0319 0320 KDirModelNode *KDirModelPrivate::expandAllParentsUntil(const QUrl &_url) const // O(depth) 0321 { 0322 QUrl url = cleanupUrl(_url); 0323 0324 // qDebug() << url; 0325 QUrl nodeUrl = urlForNode(m_rootNode); 0326 KDirModelDirNode *dirNode = m_rootNode; 0327 if (m_showNodeForListedUrl && !m_rootNode->m_childNodes.isEmpty()) { 0328 dirNode = static_cast<KDirModelDirNode *>(m_rootNode->m_childNodes.at(0)); // ### will be incorrect if we list drives on Windows 0329 nodeUrl = dirNode->item().url(); 0330 qCDebug(category) << "listed URL is visible, adjusted starting point to" << nodeUrl; 0331 } 0332 if (url == nodeUrl) { 0333 return dirNode; 0334 } 0335 0336 // Protocol mismatch? Don't even start comparing paths then. #171721 0337 if (url.scheme() != nodeUrl.scheme()) { 0338 qCWarning(category) << "protocol mismatch:" << url.scheme() << "vs" << nodeUrl.scheme(); 0339 return nullptr; 0340 } 0341 0342 const QString pathStr = url.path(); // no trailing slash 0343 0344 if (!pathStr.startsWith(nodeUrl.path())) { 0345 qCDebug(category) << pathStr << "does not start with" << nodeUrl.path(); 0346 return nullptr; 0347 } 0348 0349 for (;;) { 0350 QString nodePath = nodeUrl.path(); 0351 if (!nodePath.endsWith(QLatin1Char('/'))) { 0352 nodePath += QLatin1Char('/'); 0353 } 0354 if (!pathStr.startsWith(nodePath)) { 0355 qCWarning(category) << "The KIO worker for" << url.scheme() << "violates the hierarchy structure:" 0356 << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path."; 0357 return nullptr; 0358 } 0359 0360 // E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b 0361 const int nextSlash = pathStr.indexOf(QLatin1Char('/'), nodePath.length()); 0362 const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1 0363 nodeUrl.setPath(newPath); 0364 nodeUrl = nodeUrl.adjusted(QUrl::StripTrailingSlash); // #172508 0365 KDirModelNode *node = nodeForUrl(nodeUrl); 0366 if (!node) { 0367 qCDebug(category) << nodeUrl << "not found, needs to be listed"; 0368 // return last parent found: 0369 return dirNode; 0370 } 0371 0372 Q_EMIT q->expand(indexForNode(node)); 0373 0374 // qDebug() << " nodeUrl=" << nodeUrl; 0375 if (nodeUrl == url) { 0376 qCDebug(category) << "Found node" << node << "for" << url; 0377 return node; 0378 } 0379 qCDebug(category) << "going into" << node->item().url(); 0380 Q_ASSERT(isDir(node)); 0381 dirNode = static_cast<KDirModelDirNode *>(node); 0382 } 0383 // NOTREACHED 0384 // return 0; 0385 } 0386 0387 #ifndef NDEBUG 0388 void KDirModelPrivate::dump() 0389 { 0390 qCDebug(category) << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url(); 0391 QHashIterator<QUrl, KDirModelNode *> it(m_nodeHash); 0392 while (it.hasNext()) { 0393 it.next(); 0394 qCDebug(category) << it.key() << it.value(); 0395 } 0396 } 0397 #endif 0398 0399 // node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n). 0400 QModelIndex KDirModelPrivate::indexForNode(KDirModelNode *node, int rowNumber) const 0401 { 0402 if (node == m_rootNode) { 0403 return QModelIndex(); 0404 } 0405 0406 Q_ASSERT(node->parent()); 0407 return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node); 0408 } 0409 0410 // index -> node. O(1) 0411 KDirModelNode *KDirModelPrivate::nodeForIndex(const QModelIndex &index) const 0412 { 0413 return index.isValid() ? static_cast<KDirModelNode *>(index.internalPointer()) : m_rootNode; 0414 } 0415 0416 /* 0417 * This model wraps the data held by KDirLister. 0418 * 0419 * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree. 0420 * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root> 0421 * 0422 * Invalid parent index means root of the tree, m_rootNode 0423 */ 0424 0425 static QString debugIndex(const QModelIndex &index) 0426 { 0427 QString str; 0428 if (!index.isValid()) { 0429 str = QStringLiteral("[invalid index, i.e. root]"); 0430 } else { 0431 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer()); 0432 str = QLatin1String("[index for ") + node->item().url().toString(); 0433 if (index.column() > 0) { 0434 str += QLatin1String(", column ") + QString::number(index.column()); 0435 } 0436 str += QLatin1Char(']'); 0437 } 0438 return str; 0439 } 0440 0441 KDirModel::KDirModel(QObject *parent) 0442 : QAbstractItemModel(parent) 0443 , d(new KDirModelPrivate(this)) 0444 { 0445 setDirLister(new KDirLister(this)); 0446 } 0447 0448 KDirModel::~KDirModel() = default; 0449 0450 void KDirModel::setDirLister(KDirLister *dirLister) 0451 { 0452 if (d->m_dirLister) { 0453 d->clear(); 0454 delete d->m_dirLister; 0455 } 0456 d->m_dirLister = dirLister; 0457 d->m_dirLister->setParent(this); 0458 connect(d->m_dirLister, &KCoreDirLister::itemsAdded, this, [this](const QUrl &dirUrl, const KFileItemList &items) { 0459 d->_k_slotNewItems(dirUrl, items); 0460 }); 0461 connect(d->m_dirLister, &KCoreDirLister::listingDirCompleted, this, [this](const QUrl &dirUrl) { 0462 d->_k_slotCompleted(dirUrl); 0463 }); 0464 connect(d->m_dirLister, &KCoreDirLister::itemsDeleted, this, [this](const KFileItemList &items) { 0465 d->_k_slotDeleteItems(items); 0466 }); 0467 connect(d->m_dirLister, &KCoreDirLister::refreshItems, this, [this](const QList<QPair<KFileItem, KFileItem>> &items) { 0468 d->_k_slotRefreshItems(items); 0469 }); 0470 connect(d->m_dirLister, qOverload<>(&KCoreDirLister::clear), this, [this]() { 0471 d->_k_slotClear(); 0472 }); 0473 connect(d->m_dirLister, qOverload<const QUrl &, const QUrl &>(&KCoreDirLister::redirection), this, [this](const QUrl &oldUrl, const QUrl &newUrl) { 0474 d->_k_slotRedirection(oldUrl, newUrl); 0475 }); 0476 } 0477 0478 void KDirModel::openUrl(const QUrl &inputUrl, OpenUrlFlags flags) 0479 { 0480 Q_ASSERT(d->m_dirLister); 0481 const QUrl url = cleanupUrl(inputUrl); 0482 if (flags & ShowRoot) { 0483 d->_k_slotClear(); 0484 d->m_showNodeForListedUrl = true; 0485 // Store the parent URL into the invisible root node 0486 const QUrl parentUrl = d->rootParentOf(url); 0487 d->m_rootNode->setItem(KFileItem(parentUrl)); 0488 // Stat the requested url, to create the visible node 0489 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 0490 connect(statJob, &KJob::result, this, [statJob, parentUrl, url, this]() { 0491 if (!statJob->error()) { 0492 const KIO::UDSEntry entry = statJob->statResult(); 0493 KFileItem visibleRootItem(entry, url); 0494 visibleRootItem.setName(url.path() == QLatin1String("/") ? QStringLiteral("/") : url.fileName()); 0495 d->_k_slotNewItems(parentUrl, QList<KFileItem>{visibleRootItem}); 0496 Q_ASSERT(d->m_rootNode->m_childNodes.count() == 1); 0497 expandToUrl(url); 0498 } else { 0499 qWarning() << statJob->errorString(); 0500 } 0501 }); 0502 } else { 0503 d->m_dirLister->openUrl(url, (flags & Reload) ? KDirLister::Reload : KDirLister::NoFlags); 0504 } 0505 } 0506 0507 Qt::DropActions KDirModel::supportedDropActions() const 0508 { 0509 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction | Qt::IgnoreAction; 0510 } 0511 0512 KDirLister *KDirModel::dirLister() const 0513 { 0514 return d->m_dirLister; 0515 } 0516 0517 void KDirModelPrivate::_k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &items) 0518 { 0519 // qDebug() << "directoryUrl=" << directoryUrl; 0520 0521 KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth) 0522 // If the directory containing the items wasn't found, then we have a big problem. 0523 // Are you calling KDirLister::openUrl(url,Keep)? Please use expandToUrl() instead. 0524 if (!result) { 0525 qCWarning(category) << "Items emitted in directory" << directoryUrl << "but that directory isn't in KDirModel!" 0526 << "Root directory:" << urlForNode(m_rootNode); 0527 for (const KFileItem &item : items) { 0528 qDebug() << "Item:" << item.url(); 0529 } 0530 #ifndef NDEBUG 0531 dump(); 0532 #endif 0533 Q_ASSERT(result); 0534 return; 0535 } 0536 Q_ASSERT(isDir(result)); 0537 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(result); 0538 0539 const QModelIndex index = indexForNode(dirNode); // O(n) 0540 const int newItemsCount = items.count(); 0541 const int newRowCount = dirNode->m_childNodes.count() + newItemsCount; 0542 0543 qCDebug(category) << items.count() << "in" << directoryUrl << "index=" << debugIndex(index) << "newRowCount=" << newRowCount; 0544 0545 q->beginInsertRows(index, newRowCount - newItemsCount, newRowCount - 1); // parent, first, last 0546 0547 const QList<QUrl> urlsBeingFetched = m_urlsBeingFetched.value(dirNode); 0548 if (!urlsBeingFetched.isEmpty()) { 0549 qCDebug(category) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched; 0550 } 0551 0552 QList<QModelIndex> emitExpandFor; 0553 0554 dirNode->m_childNodes.reserve(newRowCount); 0555 for (const auto &item : items) { 0556 const bool isDir = item.isDir(); 0557 KDirModelNode *node = isDir ? new KDirModelDirNode(dirNode, item) : new KDirModelNode(dirNode, item); 0558 #ifndef NDEBUG 0559 // Test code for possible duplication of items in the childnodes list, 0560 // not sure if/how it ever happened. 0561 // if (dirNode->m_childNodes.count() && 0562 // dirNode->m_childNodes.last()->item().name() == item.name()) { 0563 // qCWarning(category) << "Already having" << item.name() << "in" << directoryUrl 0564 // << "url=" << dirNode->m_childNodes.last()->item().url(); 0565 // abort(); 0566 //} 0567 #endif 0568 dirNode->m_childNodes.append(node); 0569 const QUrl url = item.url(); 0570 m_nodeHash.insert(cleanupUrl(url), node); 0571 0572 if (!urlsBeingFetched.isEmpty()) { 0573 const QUrl &dirUrl = url; 0574 for (const QUrl &urlFetched : std::as_const(urlsBeingFetched)) { 0575 if (dirUrl.matches(urlFetched, QUrl::StripTrailingSlash) || dirUrl.isParentOf(urlFetched)) { 0576 // qDebug() << "Listing found" << dirUrl.url() << "which is a parent of fetched url" << urlFetched; 0577 const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count() - 1); 0578 Q_ASSERT(parentIndex.isValid()); 0579 emitExpandFor.append(parentIndex); 0580 if (isDir && dirUrl != urlFetched) { 0581 q->fetchMore(parentIndex); 0582 m_urlsBeingFetched[node].append(urlFetched); 0583 } 0584 } 0585 } 0586 } 0587 } 0588 0589 q->endInsertRows(); 0590 0591 // Emit expand signal after rowsInserted signal has been emitted, 0592 // so that any proxy model will have updated its mapping already 0593 for (const QModelIndex &idx : std::as_const(emitExpandFor)) { 0594 Q_EMIT q->expand(idx); 0595 } 0596 } 0597 0598 void KDirModelPrivate::_k_slotCompleted(const QUrl &directoryUrl) 0599 { 0600 KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth) 0601 Q_ASSERT(isDir(result)); 0602 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(result); 0603 m_urlsBeingFetched.remove(dirNode); 0604 } 0605 0606 void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList &items) 0607 { 0608 qCDebug(category) << items.count() << "items"; 0609 0610 // I assume all items are from the same directory. 0611 // From KDirLister's code, this should be the case, except maybe emitChanges? 0612 const KFileItem item = items.first(); 0613 Q_ASSERT(!item.isNull()); 0614 QUrl url = item.url(); 0615 KDirModelNode *node = nodeForUrl(url); // O(depth) 0616 if (!node) { 0617 qCWarning(category) << "No node found for item that was just removed:" << url; 0618 return; 0619 } 0620 0621 KDirModelDirNode *dirNode = node->parent(); 0622 if (!dirNode) { 0623 return; 0624 } 0625 0626 QModelIndex parentIndex = indexForNode(dirNode); // O(n) 0627 0628 // Short path for deleting a single item 0629 if (items.count() == 1) { 0630 const int r = node->rowNumber(); 0631 q->beginRemoveRows(parentIndex, r, r); 0632 removeFromNodeHash(node, url); 0633 delete dirNode->m_childNodes.takeAt(r); 0634 q->endRemoveRows(); 0635 return; 0636 } 0637 0638 // We need to make lists of consecutive row numbers, for the beginRemoveRows call. 0639 // Let's use a bit array where each bit represents a given child node. 0640 const int childCount = dirNode->m_childNodes.count(); 0641 QBitArray rowNumbers(childCount, false); 0642 for (const KFileItem &item : items) { 0643 if (!node) { // don't lookup the first item twice 0644 url = item.url(); 0645 node = nodeForUrl(url); 0646 if (!node) { 0647 qCWarning(category) << "No node found for item that was just removed:" << url; 0648 continue; 0649 } 0650 if (!node->parent()) { 0651 // The root node has been deleted, but it was not first in the list 'items'. 0652 // see https://bugs.kde.org/show_bug.cgi?id=196695 0653 return; 0654 } 0655 } 0656 rowNumbers.setBit(node->rowNumber(), 1); // O(n) 0657 removeFromNodeHash(node, url); 0658 node = nullptr; 0659 } 0660 0661 int start = -1; 0662 int end = -1; 0663 bool lastVal = false; 0664 // Start from the end, otherwise all the row numbers are offset while we go 0665 for (int i = childCount - 1; i >= 0; --i) { 0666 const bool val = rowNumbers.testBit(i); 0667 if (!lastVal && val) { 0668 end = i; 0669 // qDebug() << "end=" << end; 0670 } 0671 if ((lastVal && !val) || (i == 0 && val)) { 0672 start = val ? i : i + 1; 0673 // qDebug() << "beginRemoveRows" << start << end; 0674 q->beginRemoveRows(parentIndex, start, end); 0675 for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;) 0676 // qDebug() << "Removing from m_childNodes at" << r; 0677 delete dirNode->m_childNodes.takeAt(r); 0678 } 0679 q->endRemoveRows(); 0680 } 0681 lastVal = val; 0682 } 0683 } 0684 0685 void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items) 0686 { 0687 QModelIndex topLeft; 0688 QModelIndex bottomRight; 0689 0690 // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows 0691 // Solution 2: more fine-grained, actually figure out the beginning and end rows. 0692 for (const auto &[oldItem, newItem] : items) { 0693 Q_ASSERT(!oldItem.isNull()); 0694 Q_ASSERT(!newItem.isNull()); 0695 const QUrl oldUrl = oldItem.url(); 0696 const QUrl newUrl = newItem.url(); 0697 KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once 0698 // qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node; 0699 if (!node) { // not found [can happen when renaming a dir, redirection was emitted already] 0700 continue; 0701 } 0702 if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead. 0703 bool hasNewNode = false; 0704 // A file became directory (well, it was overwritten) 0705 if (oldItem.isDir() != newItem.isDir()) { 0706 // qDebug() << "DIR/FILE STATUS CHANGE"; 0707 const int r = node->rowNumber(); 0708 removeFromNodeHash(node, oldUrl); 0709 KDirModelDirNode *dirNode = node->parent(); 0710 delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node" 0711 node = newItem.isDir() ? new KDirModelDirNode(dirNode, newItem) : new KDirModelNode(dirNode, newItem); 0712 dirNode->m_childNodes.insert(r, node); // same position! 0713 hasNewNode = true; 0714 } else { 0715 node->setItem(newItem); 0716 } 0717 0718 if (oldUrl != newUrl || hasNewNode) { 0719 // What if a renamed dir had children? -> kdirlister takes care of emitting for each item 0720 // qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash"; 0721 m_nodeHash.remove(cleanupUrl(oldUrl)); 0722 m_nodeHash.insert(cleanupUrl(newUrl), node); 0723 } 0724 // MIME type changed -> forget cached icon (e.g. from "cut", #164185 comment #13) 0725 if (oldItem.determineMimeType().name() != newItem.determineMimeType().name()) { 0726 node->setPreview(QIcon()); 0727 } 0728 0729 const QModelIndex index = indexForNode(node); 0730 if (!topLeft.isValid() || index.row() < topLeft.row()) { 0731 topLeft = index; 0732 } 0733 if (!bottomRight.isValid() || index.row() > bottomRight.row()) { 0734 bottomRight = index; 0735 } 0736 } 0737 } 0738 // qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight); 0739 bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1); 0740 Q_EMIT q->dataChanged(topLeft, bottomRight); 0741 } 0742 0743 // Called when a KIO worker redirects (e.g. smb:/Workgroup -> smb://workgroup) 0744 // and when renaming a directory. 0745 void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl) 0746 { 0747 KDirModelNode *node = nodeForUrl(oldUrl); 0748 if (!node) { 0749 return; 0750 } 0751 m_nodeHash.remove(cleanupUrl(oldUrl)); 0752 m_nodeHash.insert(cleanupUrl(newUrl), node); 0753 0754 // Ensure the node's URL is updated. In case of a listjob redirection 0755 // we won't get a refreshItem, and in case of renaming a directory 0756 // we'll get it too late (so the hash won't find the old url anymore). 0757 KFileItem item = node->item(); 0758 if (!item.isNull()) { // null if root item, #180156 0759 item.setUrl(newUrl); 0760 node->setItem(item); 0761 } 0762 0763 // The items inside the renamed directory have been handled before, 0764 // KDirLister took care of emitting refreshItem for each of them. 0765 } 0766 0767 void KDirModelPrivate::_k_slotClear() 0768 { 0769 const int numRows = m_rootNode->m_childNodes.count(); 0770 if (numRows > 0) { 0771 q->beginRemoveRows(QModelIndex(), 0, numRows - 1); 0772 } 0773 m_nodeHash.clear(); 0774 clear(); 0775 if (numRows > 0) { 0776 q->endRemoveRows(); 0777 } 0778 } 0779 0780 void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList) 0781 { 0782 QStringList dirtyUrls; 0783 0784 std::set_symmetric_difference(urlList.begin(), 0785 urlList.end(), 0786 m_allCurrentDestUrls.constBegin(), 0787 m_allCurrentDestUrls.constEnd(), 0788 std::back_inserter(dirtyUrls)); 0789 0790 m_allCurrentDestUrls = urlList; 0791 0792 for (const QString &dirtyUrl : std::as_const(dirtyUrls)) { 0793 if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) { 0794 const QModelIndex idx = indexForNode(node); 0795 Q_EMIT q->dataChanged(idx, idx, {KDirModel::HasJobRole}); 0796 } 0797 } 0798 } 0799 0800 void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode) 0801 { 0802 const int numRows = dirNode->m_childNodes.count(); 0803 if (numRows > 0) { 0804 KDirModelNode *lastNode = nullptr; 0805 for (KDirModelNode *node : std::as_const(dirNode->m_childNodes)) { 0806 node->setPreview(QIcon()); 0807 // node->setPreview(QIcon::fromTheme(node->item().iconName())); 0808 if (isDir(node)) { 0809 // recurse into child dirs 0810 clearAllPreviews(static_cast<KDirModelDirNode *>(node)); 0811 } 0812 lastNode = node; 0813 } 0814 Q_EMIT q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1) 0815 indexForNode(lastNode, numRows - 1)); // O(1) 0816 } 0817 } 0818 0819 void KDirModel::clearAllPreviews() 0820 { 0821 d->clearAllPreviews(d->m_rootNode); 0822 } 0823 0824 void KDirModel::itemChanged(const QModelIndex &index) 0825 { 0826 // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator. 0827 // When the MIME type is determined, clear the old "preview" (could be 0828 // MIME type dependent like when cutting files, #164185) 0829 KDirModelNode *node = d->nodeForIndex(index); 0830 if (node) { 0831 node->setPreview(QIcon()); 0832 } 0833 0834 qCDebug(category) << "dataChanged(" << debugIndex(index) << ")"; 0835 Q_EMIT dataChanged(index, index); 0836 } 0837 0838 int KDirModel::columnCount(const QModelIndex &) const 0839 { 0840 return ColumnCount; 0841 } 0842 0843 QVariant KDirModel::data(const QModelIndex &index, int role) const 0844 { 0845 if (index.isValid()) { 0846 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer()); 0847 const KFileItem &item(node->item()); 0848 switch (role) { 0849 case Qt::DisplayRole: 0850 switch (index.column()) { 0851 case Name: 0852 return item.text(); 0853 case Size: 0854 return KIO::convertSize(item.size()); // size formatted as QString 0855 case ModifiedTime: { 0856 QDateTime dt = item.time(KFileItem::ModificationTime); 0857 return QLocale().toString(dt, QLocale::ShortFormat); 0858 } 0859 case Permissions: 0860 return item.permissionsString(); 0861 case Owner: 0862 return item.user(); 0863 case Group: 0864 return item.group(); 0865 case Type: 0866 return item.mimeComment(); 0867 } 0868 break; 0869 case Qt::EditRole: 0870 switch (index.column()) { 0871 case Name: 0872 return item.text(); 0873 } 0874 break; 0875 case Qt::DecorationRole: 0876 if (index.column() == Name) { 0877 if (!node->preview().isNull()) { 0878 // qDebug() << item->url() << " preview found"; 0879 return node->preview(); 0880 } 0881 Q_ASSERT(!item.isNull()); 0882 // qDebug() << item->url() << " overlays=" << item->overlays(); 0883 static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown")); 0884 0885 const QString iconName(item.iconName()); 0886 QIcon icon; 0887 0888 if (QDir::isAbsolutePath(iconName)) { 0889 icon = QIcon(iconName); 0890 } 0891 if (icon.isNull() 0892 || (!(iconName.endsWith(QLatin1String(".svg")) || iconName.endsWith(QLatin1String(".svgz"))) && icon.availableSizes().isEmpty())) { 0893 icon = QIcon::fromTheme(iconName, fallbackIcon); 0894 } 0895 0896 const auto parentNode = node->parent(); 0897 if (parentNode->isOnNetwork()) { 0898 return icon; 0899 } else { 0900 return KIconUtils::addOverlays(icon, item.overlays()); 0901 } 0902 } 0903 break; 0904 case Qt::TextAlignmentRole: 0905 if (index.column() == Size) { 0906 // use a right alignment for L2R and R2L languages 0907 const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter; 0908 return int(alignment); 0909 } 0910 break; 0911 case Qt::ToolTipRole: 0912 return item.text(); 0913 case FileItemRole: 0914 return QVariant::fromValue(item); 0915 case ChildCountRole: 0916 if (!item.isDir()) { 0917 return ChildCountUnknown; 0918 } else { 0919 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(node); 0920 int count = dirNode->childCount(); 0921 if (count == ChildCountUnknown && !dirNode->isOnNetwork() && item.isReadable()) { 0922 const QString path = item.localPath(); 0923 if (!path.isEmpty()) { 0924 // slow 0925 // QDir dir(path); 0926 // count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count(); 0927 #ifdef Q_OS_WIN 0928 QString s = path + QLatin1String("\\*.*"); 0929 s.replace(QLatin1Char('/'), QLatin1Char('\\')); 0930 count = 0; 0931 WIN32_FIND_DATA findData; 0932 HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData); 0933 if (hFile != INVALID_HANDLE_VALUE) { 0934 do { 0935 if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0') 0936 && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) { 0937 ++count; 0938 } 0939 } while (FindNextFile(hFile, &findData) != 0); 0940 FindClose(hFile); 0941 } 0942 #else 0943 DIR *dir = QT_OPENDIR(QFile::encodeName(path).constData()); 0944 if (dir) { 0945 count = 0; 0946 QT_DIRENT *dirEntry = nullptr; 0947 while ((dirEntry = QT_READDIR(dir))) { 0948 if (dirEntry->d_name[0] == '.') { 0949 if (dirEntry->d_name[1] == '\0') { // skip "." 0950 continue; 0951 } 0952 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".." 0953 continue; 0954 } 0955 } 0956 ++count; 0957 } 0958 QT_CLOSEDIR(dir); 0959 } 0960 #endif 0961 // qDebug() << "child count for " << path << ":" << count; 0962 dirNode->setChildCount(count); 0963 } 0964 } 0965 return count; 0966 } 0967 case HasJobRole: 0968 if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) { 0969 KDirModelNode *node = d->nodeForIndex(index); 0970 const QString url = node->item().url().toString(); 0971 // return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint. 0972 return QVariant(d->m_allCurrentDestUrls.contains(url)); 0973 } 0974 } 0975 } 0976 return QVariant(); 0977 } 0978 0979 void KDirModel::sort(int column, Qt::SortOrder order) 0980 { 0981 // Not implemented - we should probably use QSortFilterProxyModel instead. 0982 QAbstractItemModel::sort(column, order); 0983 } 0984 0985 bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role) 0986 { 0987 switch (role) { 0988 case Qt::EditRole: 0989 if (index.column() == Name && value.type() == QVariant::String) { 0990 Q_ASSERT(index.isValid()); 0991 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer()); 0992 const KFileItem &item = node->item(); 0993 const QString newName = value.toString(); 0994 if (newName.isEmpty() || newName == item.text() || (newName == QLatin1Char('.')) || (newName == QLatin1String(".."))) { 0995 return true; 0996 } 0997 QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename); 0998 newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); 0999 KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags); 1000 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 1001 // undo handling 1002 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList<QUrl>() << item.url(), newUrl, job); 1003 return true; 1004 } 1005 break; 1006 case Qt::DecorationRole: 1007 if (index.column() == Name) { 1008 Q_ASSERT(index.isValid()); 1009 // Set new pixmap - e.g. preview 1010 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer()); 1011 // qDebug() << "setting icon for " << node->item()->url(); 1012 Q_ASSERT(node); 1013 if (value.type() == QVariant::Icon) { 1014 const QIcon icon(qvariant_cast<QIcon>(value)); 1015 node->setPreview(icon); 1016 } else if (value.type() == QVariant::Pixmap) { 1017 node->setPreview(qvariant_cast<QPixmap>(value)); 1018 } 1019 Q_EMIT dataChanged(index, index); 1020 return true; 1021 } 1022 break; 1023 default: 1024 break; 1025 } 1026 return false; 1027 } 1028 1029 int KDirModel::rowCount(const QModelIndex &parent) const 1030 { 1031 if (parent.column() > 0) { // for QAbstractItemModelTester 1032 return 0; 1033 } 1034 KDirModelNode *node = d->nodeForIndex(parent); 1035 if (!node || !d->isDir(node)) { // #176555 1036 return 0; 1037 } 1038 1039 KDirModelDirNode *parentNode = static_cast<KDirModelDirNode *>(node); 1040 Q_ASSERT(parentNode); 1041 const int count = parentNode->m_childNodes.count(); 1042 #if 0 1043 QStringList filenames; 1044 for (int i = 0; i < count; ++i) { 1045 filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName(); 1046 } 1047 qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames; 1048 #endif 1049 return count; 1050 } 1051 1052 QModelIndex KDirModel::parent(const QModelIndex &index) const 1053 { 1054 if (!index.isValid()) { 1055 return QModelIndex(); 1056 } 1057 KDirModelNode *childNode = static_cast<KDirModelNode *>(index.internalPointer()); 1058 Q_ASSERT(childNode); 1059 KDirModelNode *parentNode = childNode->parent(); 1060 Q_ASSERT(parentNode); 1061 return d->indexForNode(parentNode); // O(n) 1062 } 1063 1064 // Reimplemented to avoid the default implementation which calls parent 1065 // (O(n) for finding the parent's row number for nothing). This implementation is O(1). 1066 QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const 1067 { 1068 if (!index.isValid()) { 1069 return QModelIndex(); 1070 } 1071 KDirModelNode *oldChildNode = static_cast<KDirModelNode *>(index.internalPointer()); 1072 Q_ASSERT(oldChildNode); 1073 KDirModelNode *parentNode = oldChildNode->parent(); 1074 Q_ASSERT(parentNode); 1075 Q_ASSERT(d->isDir(parentNode)); 1076 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1) 1077 if (childNode) { 1078 return createIndex(row, column, childNode); 1079 } 1080 return QModelIndex(); 1081 } 1082 1083 void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex) 1084 { 1085 Q_EMIT needSequenceIcon(index, sequenceIndex); 1086 } 1087 1088 void KDirModel::setJobTransfersVisible(bool show) 1089 { 1090 if (d->m_jobTransfersVisible == show) { 1091 return; 1092 } 1093 1094 d->m_jobTransfersVisible = show; 1095 if (show) { 1096 connect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, [this](const QStringList &urlList) { 1097 d->_k_slotJobUrlsChanged(urlList); 1098 }); 1099 1100 JobUrlCache::instance().requestJobUrlsChanged(); 1101 } else { 1102 disconnect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, nullptr); 1103 } 1104 } 1105 1106 bool KDirModel::jobTransfersVisible() const 1107 { 1108 return d->m_jobTransfersVisible; 1109 } 1110 1111 QList<QUrl> KDirModel::simplifiedUrlList(const QList<QUrl> &urls) 1112 { 1113 if (urls.isEmpty()) { 1114 return urls; 1115 } 1116 1117 QList<QUrl> ret(urls); 1118 std::sort(ret.begin(), ret.end()); 1119 1120 QUrl url; 1121 1122 auto filterFunc = [&url](const QUrl &u) { 1123 if (url == u || url.isParentOf(u)) { 1124 return true; 1125 } else { 1126 url = u; 1127 return false; 1128 } 1129 }; 1130 1131 auto beginIt = ret.begin(); 1132 url = *beginIt; 1133 ++beginIt; 1134 auto it = std::remove_if(beginIt, ret.end(), filterFunc); 1135 ret.erase(it, ret.end()); 1136 1137 return ret; 1138 } 1139 1140 QStringList KDirModel::mimeTypes() const 1141 { 1142 return KUrlMimeData::mimeDataTypes(); 1143 } 1144 1145 QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const 1146 { 1147 QList<QUrl> urls; 1148 QList<QUrl> mostLocalUrls; 1149 urls.reserve(indexes.size()); 1150 mostLocalUrls.reserve(indexes.size()); 1151 bool canUseMostLocalUrls = true; 1152 for (const QModelIndex &index : indexes) { 1153 const KFileItem &item = d->nodeForIndex(index)->item(); 1154 urls.append(item.url()); 1155 const auto [url, isLocal] = item.isMostLocalUrl(); 1156 mostLocalUrls.append(url); 1157 if (!isLocal) { 1158 canUseMostLocalUrls = false; 1159 } 1160 } 1161 QMimeData *data = new QMimeData(); 1162 const bool different = canUseMostLocalUrls && (mostLocalUrls != urls); 1163 urls = simplifiedUrlList(urls); 1164 if (different) { 1165 mostLocalUrls = simplifiedUrlList(mostLocalUrls); 1166 KUrlMimeData::setUrls(urls, mostLocalUrls, data); 1167 } else { 1168 data->setUrls(urls); 1169 } 1170 1171 return data; 1172 } 1173 1174 // Public API; not much point in calling it internally 1175 KFileItem KDirModel::itemForIndex(const QModelIndex &index) const 1176 { 1177 if (!index.isValid()) { 1178 if (d->m_showNodeForListedUrl) { 1179 return {}; 1180 } 1181 return d->m_dirLister->rootItem(); 1182 } else { 1183 return static_cast<KDirModelNode *>(index.internalPointer())->item(); 1184 } 1185 } 1186 1187 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(4, 0) 1188 QModelIndex KDirModel::indexForItem(const KFileItem *item) const 1189 { 1190 // Note that we can only use the URL here, not the pointer. 1191 // KFileItems can be copied. 1192 return indexForUrl(item->url()); // O(n) 1193 } 1194 #endif 1195 1196 QModelIndex KDirModel::indexForItem(const KFileItem &item) const 1197 { 1198 return indexForUrl(item.url()); // O(n) 1199 } 1200 1201 // url -> index. O(n) 1202 QModelIndex KDirModel::indexForUrl(const QUrl &url) const 1203 { 1204 KDirModelNode *node = d->nodeForUrl(url); // O(depth) 1205 if (!node) { 1206 // qDebug() << url << "not found"; 1207 return QModelIndex(); 1208 } 1209 return d->indexForNode(node); // O(n) 1210 } 1211 1212 QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const 1213 { 1214 KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1) 1215 Q_ASSERT(parentNode); 1216 if (d->isDir(parentNode)) { 1217 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1) 1218 if (childNode) { 1219 return createIndex(row, column, childNode); 1220 } 1221 } 1222 return QModelIndex(); 1223 } 1224 1225 QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const 1226 { 1227 if (orientation == Qt::Horizontal) { 1228 switch (role) { 1229 case Qt::DisplayRole: 1230 switch (section) { 1231 case Name: 1232 return i18nc("@title:column", "Name"); 1233 case Size: 1234 return i18nc("@title:column", "Size"); 1235 case ModifiedTime: 1236 return i18nc("@title:column", "Date"); 1237 case Permissions: 1238 return i18nc("@title:column", "Permissions"); 1239 case Owner: 1240 return i18nc("@title:column", "Owner"); 1241 case Group: 1242 return i18nc("@title:column", "Group"); 1243 case Type: 1244 return i18nc("@title:column", "Type"); 1245 } 1246 } 1247 } 1248 return QVariant(); 1249 } 1250 1251 bool KDirModel::hasChildren(const QModelIndex &parent) const 1252 { 1253 if (!parent.isValid()) { 1254 return true; 1255 } 1256 1257 const KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer()); 1258 const KFileItem &parentItem = parentNode->item(); 1259 Q_ASSERT(!parentItem.isNull()); 1260 if (!parentItem.isDir()) { 1261 return false; 1262 } 1263 if (static_cast<const KDirModelDirNode *>(parentNode)->isPopulated()) { 1264 return !static_cast<const KDirModelDirNode *>(parentNode)->m_childNodes.isEmpty(); 1265 } 1266 if (parentItem.isLocalFile() && !static_cast<const KDirModelDirNode *>(parentNode)->isOnNetwork()) { 1267 QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot; 1268 1269 if (d->m_dirLister->dirOnlyMode()) { 1270 filters |= QDir::NoSymLinks; 1271 } else { 1272 filters |= QDir::Files | QDir::System; 1273 } 1274 1275 if (d->m_dirLister->showHiddenFiles()) { 1276 filters |= QDir::Hidden; 1277 } 1278 1279 QDirIterator it(parentItem.localPath(), filters, QDirIterator::Subdirectories); 1280 return it.hasNext(); 1281 } 1282 // Remote and not listed yet, we can't know; let the user click on it so we'll find out 1283 return true; 1284 } 1285 1286 Qt::ItemFlags KDirModel::flags(const QModelIndex &index) const 1287 { 1288 Qt::ItemFlags f; 1289 if (index.isValid()) { 1290 f |= Qt::ItemIsEnabled; 1291 if (index.column() == Name) { 1292 f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; 1293 } 1294 } 1295 1296 // Allow dropping onto this item? 1297 if (d->m_dropsAllowed != NoDrops) { 1298 if (!index.isValid()) { 1299 if (d->m_dropsAllowed & DropOnDirectory) { 1300 f |= Qt::ItemIsDropEnabled; 1301 } 1302 } else { 1303 KFileItem item = itemForIndex(index); 1304 if (item.isNull()) { 1305 qCWarning(category) << "Invalid item returned for index"; 1306 } else if (item.isDir()) { 1307 if (d->m_dropsAllowed & DropOnDirectory) { 1308 f |= Qt::ItemIsDropEnabled; 1309 } 1310 } else { // regular file item 1311 if (d->m_dropsAllowed & DropOnAnyFile) { 1312 f |= Qt::ItemIsDropEnabled; 1313 } else if (d->m_dropsAllowed & DropOnLocalExecutable) { 1314 if (!item.localPath().isEmpty()) { 1315 // Desktop file? 1316 if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) { 1317 f |= Qt::ItemIsDropEnabled; 1318 } 1319 // Executable, shell script ... ? 1320 else if (QFileInfo(item.localPath()).isExecutable()) { 1321 f |= Qt::ItemIsDropEnabled; 1322 } 1323 } 1324 } 1325 } 1326 } 1327 } 1328 1329 return f; 1330 } 1331 1332 bool KDirModel::canFetchMore(const QModelIndex &parent) const 1333 { 1334 if (!parent.isValid()) { 1335 return false; 1336 } 1337 1338 // We now have a bool KDirModelNode::m_populated, 1339 // to avoid calling fetchMore more than once on empty dirs. 1340 // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview? 1341 // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade) 1342 1343 KDirModelNode *node = static_cast<KDirModelNode *>(parent.internalPointer()); 1344 const KFileItem &item = node->item(); 1345 return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated() && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty(); 1346 } 1347 1348 void KDirModel::fetchMore(const QModelIndex &parent) 1349 { 1350 if (!parent.isValid()) { 1351 return; 1352 } 1353 1354 KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer()); 1355 1356 KFileItem parentItem = parentNode->item(); 1357 Q_ASSERT(!parentItem.isNull()); 1358 if (!parentItem.isDir()) { 1359 return; 1360 } 1361 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(parentNode); 1362 if (dirNode->isPopulated()) { 1363 return; 1364 } 1365 dirNode->setPopulated(true); 1366 1367 const QUrl parentUrl = parentItem.url(); 1368 d->m_dirLister->openUrl(parentUrl, KDirLister::Keep); 1369 } 1370 1371 bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) 1372 { 1373 // Not sure we want to implement any drop handling at this level, 1374 // but for sure the default QAbstractItemModel implementation makes no sense for a dir model. 1375 Q_UNUSED(data); 1376 Q_UNUSED(action); 1377 Q_UNUSED(row); 1378 Q_UNUSED(column); 1379 Q_UNUSED(parent); 1380 return false; 1381 } 1382 1383 void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed) 1384 { 1385 d->m_dropsAllowed = dropsAllowed; 1386 } 1387 1388 void KDirModel::expandToUrl(const QUrl &url) 1389 { 1390 // emit expand for each parent and return last parent 1391 KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth) 1392 1393 if (!result) { // doesn't seem related to our base url? 1394 qCDebug(category) << url << "does not seem related to our base URL, aborting"; 1395 return; 1396 } 1397 if (!result->item().isNull() && result->item().url() == url) { 1398 // We have it already, nothing to do 1399 qCDebug(category) << "we have it already:" << url; 1400 return; 1401 } 1402 1403 d->m_urlsBeingFetched[result].append(url); 1404 1405 if (result == d->m_rootNode) { 1406 qCDebug(category) << "Remembering to emit expand after listing the root url"; 1407 // the root is fetched by default, so it must be currently being fetched 1408 return; 1409 } 1410 1411 qCDebug(category) << "Remembering to emit expand after listing" << result->item().url(); 1412 1413 // start a new fetch to look for the next level down the URL 1414 const QModelIndex parentIndex = d->indexForNode(result); // O(n) 1415 Q_ASSERT(parentIndex.isValid()); 1416 fetchMore(parentIndex); 1417 } 1418 1419 bool KDirModel::insertRows(int, int, const QModelIndex &) 1420 { 1421 return false; 1422 } 1423 1424 bool KDirModel::insertColumns(int, int, const QModelIndex &) 1425 { 1426 return false; 1427 } 1428 1429 bool KDirModel::removeRows(int, int, const QModelIndex &) 1430 { 1431 return false; 1432 } 1433 1434 bool KDirModel::removeColumns(int, int, const QModelIndex &) 1435 { 1436 return false; 1437 } 1438 1439 QHash<int, QByteArray> KDirModel::roleNames() const 1440 { 1441 auto super = QAbstractItemModel::roleNames(); 1442 1443 super[AdditionalRoles::FileItemRole] = "fileItem"; 1444 super[AdditionalRoles::ChildCountRole] = "childCount"; 1445 super[AdditionalRoles::HasJobRole] = "hasJob"; 1446 1447 return super; 1448 } 1449 1450 #include "moc_kdirmodel.cpp"