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