KIO

kdirmodel.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2006-2019 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kdirmodel.h"
9#include "kdirlister.h"
10#include "kfileitem.h"
11
12#include "joburlcache_p.h"
13#include <KIconUtils>
14#include <KJobUiDelegate>
15#include <KLocalizedString>
16#include <KUrlMimeData>
17#include <kio/fileundomanager.h>
18#include <kio/simplejob.h>
19#include <kio/statjob.h>
20
21#include <QBitArray>
22#include <QDebug>
23#include <QDir>
24#include <QDirIterator>
25#include <QFile>
26#include <QFileInfo>
27#include <QIcon>
28#include <QLocale>
29#include <QLoggingCategory>
30#include <QMimeData>
31#include <qplatformdefs.h>
32
33#include <algorithm>
34
35#ifdef Q_OS_WIN
36#include <qt_windows.h>
37#endif
38
39Q_LOGGING_CATEGORY(category, "kf.kio.widgets.kdirmodel", QtInfoMsg)
40
41class KDirModelNode;
42class KDirModelDirNode;
43
44static QUrl cleanupUrl(const QUrl &url)
45{
46 QUrl u = url;
47 u.setPath(QDir::cleanPath(u.path())); // remove double slashes in the path, simplify "foo/." to "foo/", etc.
48 u = u.adjusted(QUrl::StripTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url.
49 if (u.scheme().startsWith(QStringLiteral("ksvn")) || u.scheme().startsWith(QStringLiteral("svn"))) {
50 u.setQuery(QString());
52 }
53 return u;
54}
55
56// We create our own tree behind the scenes to have fast lookup from an item to its parent,
57// and also to get the children of an item fast.
58class KDirModelNode
59{
60public:
61 KDirModelNode(KDirModelDirNode *parent, const KFileItem &item)
62 : m_item(item)
63 , m_parent(parent)
64 {
65 }
66
67 virtual ~KDirModelNode() = default; // Required, code will delete ptrs to this or a subclass.
68
69 // m_item is KFileItem() for the root item
70 const KFileItem &item() const
71 {
72 return m_item;
73 }
74
75 virtual void setItem(const KFileItem &item)
76 {
77 m_item = item;
78 }
79
80 KDirModelDirNode *parent() const
81 {
82 return m_parent;
83 }
84
85 // linear search
86 int rowNumber() const; // O(n)
87
88 QIcon preview() const
89 {
90 return m_preview;
91 }
92
93 void setPreview(const QPixmap &pix)
94 {
95 m_preview = QIcon();
96 m_preview.addPixmap(pix);
97 }
98
99 void setPreview(const QIcon &icn)
100 {
101 m_preview = icn;
102 }
103
104private:
105 KFileItem m_item;
106 KDirModelDirNode *const m_parent;
107 QIcon m_preview;
108};
109
110// Specialization for directory nodes
111class KDirModelDirNode : public KDirModelNode
112{
113public:
114 KDirModelDirNode(KDirModelDirNode *parent, const KFileItem &item)
115 : KDirModelNode(parent, item)
116 , m_childCount(KDirModel::ChildCountUnknown)
117 , m_populated(false)
118 , m_fsType(FsTypeUnknown)
119 {
120 // If the parent node is on the network, all children are too. Opposite is not always true.
121 if (parent && parent->isOnNetwork()) {
122 m_fsType = NetworkFs;
123 }
124 }
125 ~KDirModelDirNode() override
126 {
127 qDeleteAll(m_childNodes);
128 }
129 QList<KDirModelNode *> m_childNodes; // owns the nodes
130
131 void setItem(const KFileItem &item) override
132 {
133 KDirModelNode::setItem(item);
134 if (item.isNull() || !item.url().isValid()) {
135 m_fsType = LocalFs;
136 } else {
137 m_fsType = FsTypeUnknown;
138 }
139 }
140
141 // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount.
142 int childCount() const
143 {
144 return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count();
145 }
146
147 void setChildCount(int count)
148 {
149 m_childCount = count;
150 }
151
152 bool isPopulated() const
153 {
154 return m_populated;
155 }
156
157 void setPopulated(bool populated)
158 {
159 m_populated = populated;
160 }
161
162 bool isOnNetwork() const
163 {
164 if (!item().isNull() && m_fsType == FsTypeUnknown) {
165 m_fsType = item().isSlow() ? NetworkFs : LocalFs;
166 }
167 return m_fsType == NetworkFs;
168 }
169
170 // For removing all child urls from the global hash.
171 QList<QUrl> collectAllChildUrls() const
172 {
173 QList<QUrl> urls;
174 urls.reserve(urls.size() + m_childNodes.size());
175 for (KDirModelNode *node : m_childNodes) {
176 const KFileItem &item = node->item();
177 urls.append(cleanupUrl(item.url()));
178 if (item.isDir()) {
179 urls += static_cast<KDirModelDirNode *>(node)->collectAllChildUrls();
180 }
181 }
182 return urls;
183 }
184
185private:
186 int m_childCount : 31;
187 bool m_populated : 1;
188 // Network file system? (nfs/smb/ssh)
189 mutable enum { FsTypeUnknown, LocalFs, NetworkFs } m_fsType : 3;
190};
191
192int KDirModelNode::rowNumber() const
193{
194 if (!m_parent) {
195 return 0;
196 }
197 return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode *>(this));
198}
199
200////
201
202class KDirModelPrivate
203{
204public:
205 explicit KDirModelPrivate(KDirModel *qq)
206 : q(qq)
207 , m_rootNode(new KDirModelDirNode(nullptr, KFileItem()))
208 {
209 }
210 ~KDirModelPrivate()
211 {
212 delete m_rootNode;
213 }
214
215 void _k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &);
216 void _k_slotCompleted(const QUrl &directoryUrl);
217 void _k_slotDeleteItems(const KFileItemList &);
218 void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &);
219 void _k_slotClear();
220 void _k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl);
221 void _k_slotJobUrlsChanged(const QStringList &urlList);
222
223 void clear()
224 {
225 delete m_rootNode;
226 m_rootNode = new KDirModelDirNode(nullptr, KFileItem());
227 m_showNodeForListedUrl = false;
228 m_rootNode->setItem(KFileItem(m_dirLister->url()));
229 }
230
231 // Emit expand for each parent and then return the
232 // last known parent if there is no node for this url
233 KDirModelNode *expandAllParentsUntil(const QUrl &url) const;
234
235 // Return the node for a given url, using the hash.
236 KDirModelNode *nodeForUrl(const QUrl &url) const;
237 KDirModelNode *nodeForIndex(const QModelIndex &index) const;
238 QModelIndex indexForNode(KDirModelNode *node, int rowNumber = -1 /*unknown*/) const;
239
240 static QUrl rootParentOf(const QUrl &url)
241 {
242 // <url> is what we listed, and which is visible at the root of the tree
243 // Here we want the (invisible) parent of that url
245 if (url.path() == QLatin1String("/")) {
246 parent.setPath(QString());
247 }
248 return parent;
249 }
250
251 bool isDir(KDirModelNode *node) const
252 {
253 return (node == m_rootNode) || node->item().isDir();
254 }
255
256 QUrl urlForNode(KDirModelNode *node) const
257 {
258 /**
259 * Queries and fragments are removed from the URL, so that the URL of
260 * child items really starts with the URL of the parent.
261 *
262 * For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100
263 * so we have to remove the query in both to be able to compare the URLs
264 */
265 QUrl url;
266 if (node == m_rootNode && !m_showNodeForListedUrl) {
267 url = m_dirLister->url();
268 } else {
269 url = node->item().url();
270 }
271 if (url.scheme().startsWith(QStringLiteral("ksvn")) || url.scheme().startsWith(QStringLiteral("svn"))) {
272 if (url.hasQuery() || url.hasFragment()) { // avoid detach if not necessary.
273 url.setQuery(QString());
274 url.setFragment(QString()); // kill ref (#171117)
275 }
276 }
277 return url;
278 }
279
280 void removeFromNodeHash(KDirModelNode *node, const QUrl &url);
281 void clearAllPreviews(KDirModelDirNode *node);
282#ifndef NDEBUG
283 void dump();
284#endif
285 Q_DISABLE_COPY(KDirModelPrivate)
286
287 KDirModel *const q;
288 KDirLister *m_dirLister = nullptr;
289 KDirModelDirNode *m_rootNode = nullptr;
290 KDirModel::DropsAllowed m_dropsAllowed = KDirModel::NoDrops;
291 bool m_jobTransfersVisible = false;
292 bool m_showNodeForListedUrl = false;
293 // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
294 // value = final url[s] being fetched
295 QMap<KDirModelNode *, QList<QUrl>> m_urlsBeingFetched;
296 QHash<QUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node
297 QStringList m_allCurrentDestUrls; // list of all dest urls that have jobs on them (e.g. copy, download)
298};
299
300KDirModelNode *KDirModelPrivate::nodeForUrl(const QUrl &_url) const // O(1), well, O(length of url as a string)
301{
302 QUrl url = cleanupUrl(_url);
303 if (url == urlForNode(m_rootNode)) {
304 return m_rootNode;
305 }
306 return m_nodeHash.value(url);
307}
308
309void KDirModelPrivate::removeFromNodeHash(KDirModelNode *node, const QUrl &url)
310{
311 if (node->item().isDir()) {
312 const QList<QUrl> urls = static_cast<KDirModelDirNode *>(node)->collectAllChildUrls();
313 for (const QUrl &u : urls) {
314 m_nodeHash.remove(u);
315 }
316 }
317 m_nodeHash.remove(cleanupUrl(url));
318}
319
320KDirModelNode *KDirModelPrivate::expandAllParentsUntil(const QUrl &_url) const // O(depth)
321{
322 QUrl url = cleanupUrl(_url);
323
324 // qDebug() << url;
325 QUrl nodeUrl = urlForNode(m_rootNode);
326 KDirModelDirNode *dirNode = m_rootNode;
327 if (m_showNodeForListedUrl && !m_rootNode->m_childNodes.isEmpty()) {
328 dirNode = static_cast<KDirModelDirNode *>(m_rootNode->m_childNodes.at(0)); // ### will be incorrect if we list drives on Windows
329 nodeUrl = dirNode->item().url();
330 qCDebug(category) << "listed URL is visible, adjusted starting point to" << nodeUrl;
331 }
332 if (url == nodeUrl) {
333 return dirNode;
334 }
335
336 // Protocol mismatch? Don't even start comparing paths then. #171721
337 if (url.scheme() != nodeUrl.scheme()) {
338 qCWarning(category) << "protocol mismatch:" << url.scheme() << "vs" << nodeUrl.scheme();
339 return nullptr;
340 }
341
342 const QString pathStr = url.path(); // no trailing slash
343
344 if (!pathStr.startsWith(nodeUrl.path())) {
345 qCDebug(category) << pathStr << "does not start with" << nodeUrl.path();
346 return nullptr;
347 }
348
349 for (;;) {
350 QString nodePath = nodeUrl.path();
351 if (!nodePath.endsWith(QLatin1Char('/'))) {
352 nodePath += QLatin1Char('/');
353 }
354 if (!pathStr.startsWith(nodePath)) {
355 qCWarning(category) << "The KIO worker for" << url.scheme() << "violates the hierarchy structure:"
356 << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path.";
357 return nullptr;
358 }
359
360 // E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b
361 const int nextSlash = pathStr.indexOf(QLatin1Char('/'), nodePath.length());
362 const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1
363 nodeUrl.setPath(newPath);
364 nodeUrl = nodeUrl.adjusted(QUrl::StripTrailingSlash); // #172508
365 KDirModelNode *node = nodeForUrl(nodeUrl);
366 if (!node) {
367 qCDebug(category) << nodeUrl << "not found, needs to be listed";
368 // return last parent found:
369 return dirNode;
370 }
371
372 Q_EMIT q->expand(indexForNode(node));
373
374 // qDebug() << " nodeUrl=" << nodeUrl;
375 if (nodeUrl == url) {
376 qCDebug(category) << "Found node" << node << "for" << url;
377 return node;
378 }
379 qCDebug(category) << "going into" << node->item().url();
380 Q_ASSERT(isDir(node));
381 dirNode = static_cast<KDirModelDirNode *>(node);
382 }
383 // NOTREACHED
384 // return 0;
385}
386
387#ifndef NDEBUG
388void KDirModelPrivate::dump()
389{
390 qCDebug(category) << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url();
392 while (it.hasNext()) {
393 it.next();
394 qCDebug(category) << it.key() << it.value();
395 }
396}
397#endif
398
399// node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
400QModelIndex KDirModelPrivate::indexForNode(KDirModelNode *node, int rowNumber) const
401{
402 if (node == m_rootNode) {
403 return QModelIndex();
404 }
405
406 Q_ASSERT(node->parent());
407 return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
408}
409
410// index -> node. O(1)
411KDirModelNode *KDirModelPrivate::nodeForIndex(const QModelIndex &index) const
412{
413 return index.isValid() ? static_cast<KDirModelNode *>(index.internalPointer()) : m_rootNode;
414}
415
416/*
417 * This model wraps the data held by KDirLister.
418 *
419 * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree.
420 * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root>
421 *
422 * Invalid parent index means root of the tree, m_rootNode
423 */
424
425static QString debugIndex(const QModelIndex &index)
426{
427 QString str;
428 if (!index.isValid()) {
429 str = QStringLiteral("[invalid index, i.e. root]");
430 } else {
431 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
432 str = QLatin1String("[index for ") + node->item().url().toString();
433 if (index.column() > 0) {
434 str += QLatin1String(", column ") + QString::number(index.column());
435 }
436 str += QLatin1Char(']');
437 }
438 return str;
439}
440
442 : QAbstractItemModel(parent)
443 , d(new KDirModelPrivate(this))
444{
445 setDirLister(new KDirLister(this));
446}
447
448KDirModel::~KDirModel() = default;
449
451{
452 if (d->m_dirLister) {
453 d->clear();
454 delete d->m_dirLister;
455 }
456 d->m_dirLister = dirLister;
457 d->m_dirLister->setParent(this);
458 connect(d->m_dirLister, &KCoreDirLister::itemsAdded, this, [this](const QUrl &dirUrl, const KFileItemList &items) {
459 d->_k_slotNewItems(dirUrl, items);
460 });
461 connect(d->m_dirLister, &KCoreDirLister::listingDirCompleted, this, [this](const QUrl &dirUrl) {
462 d->_k_slotCompleted(dirUrl);
463 });
464 connect(d->m_dirLister, &KCoreDirLister::itemsDeleted, this, [this](const KFileItemList &items) {
465 d->_k_slotDeleteItems(items);
466 });
467 connect(d->m_dirLister, &KCoreDirLister::refreshItems, this, [this](const QList<QPair<KFileItem, KFileItem>> &items) {
468 d->_k_slotRefreshItems(items);
469 });
470 connect(d->m_dirLister, qOverload<>(&KCoreDirLister::clear), this, [this]() {
471 d->_k_slotClear();
472 });
473 connect(d->m_dirLister, &KCoreDirLister::redirection, this, [this](const QUrl &oldUrl, const QUrl &newUrl) {
474 d->_k_slotRedirection(oldUrl, newUrl);
475 });
476}
477
478void KDirModel::openUrl(const QUrl &inputUrl, OpenUrlFlags flags)
479{
480 Q_ASSERT(d->m_dirLister);
481 const QUrl url = cleanupUrl(inputUrl);
482 if (flags & ShowRoot) {
483 d->_k_slotClear();
484 d->m_showNodeForListedUrl = true;
485 // Store the parent URL into the invisible root node
486 const QUrl parentUrl = d->rootParentOf(url);
487 d->m_rootNode->setItem(KFileItem(parentUrl));
488 // Stat the requested url, to create the visible node
490 connect(statJob, &KJob::result, this, [statJob, parentUrl, url, this]() {
491 if (!statJob->error()) {
492 const KIO::UDSEntry entry = statJob->statResult();
493 KFileItem visibleRootItem(entry, url);
494 visibleRootItem.setName(url.path() == QLatin1String("/") ? QStringLiteral("/") : url.fileName());
495 d->_k_slotNewItems(parentUrl, QList<KFileItem>{visibleRootItem});
496 Q_ASSERT(d->m_rootNode->m_childNodes.count() == 1);
497 expandToUrl(url);
498 } else {
499 qWarning() << statJob->errorString();
500 }
501 });
502 } else {
503 d->m_dirLister->openUrl(url, (flags & Reload) ? KDirLister::Reload : KDirLister::NoFlags);
504 }
505}
506
507Qt::DropActions KDirModel::supportedDropActions() const
508{
510}
511
513{
514 return d->m_dirLister;
515}
516
517void KDirModelPrivate::_k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &items)
518{
519 // qDebug() << "directoryUrl=" << directoryUrl;
520
521 KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth)
522 // If the directory containing the items wasn't found, then we have a big problem.
523 // Are you calling KDirLister::openUrl(url,Keep)? Please use expandToUrl() instead.
524 if (!result) {
525 qCWarning(category) << "Items emitted in directory" << directoryUrl << "but that directory isn't in KDirModel!"
526 << "Root directory:" << urlForNode(m_rootNode);
527 for (const KFileItem &item : items) {
528 qDebug() << "Item:" << item.url();
529 }
530#ifndef NDEBUG
531 dump();
532#endif
533 Q_ASSERT(result);
534 return;
535 }
536 Q_ASSERT(isDir(result));
537 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(result);
538
539 const QModelIndex index = indexForNode(dirNode); // O(n)
540 const int newItemsCount = items.count();
541 const int newRowCount = dirNode->m_childNodes.count() + newItemsCount;
542
543 qCDebug(category) << items.count() << "in" << directoryUrl << "index=" << debugIndex(index) << "newRowCount=" << newRowCount;
544
545 q->beginInsertRows(index, newRowCount - newItemsCount, newRowCount - 1); // parent, first, last
546
547 const QList<QUrl> urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
548 if (!urlsBeingFetched.isEmpty()) {
549 qCDebug(category) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched;
550 }
551
552 QList<QModelIndex> emitExpandFor;
553
554 dirNode->m_childNodes.reserve(newRowCount);
555 for (const auto &item : items) {
556 const bool isDir = item.isDir();
557 KDirModelNode *node = isDir ? new KDirModelDirNode(dirNode, item) : new KDirModelNode(dirNode, item);
558#ifndef NDEBUG
559 // Test code for possible duplication of items in the childnodes list,
560 // not sure if/how it ever happened.
561 // if (dirNode->m_childNodes.count() &&
562 // dirNode->m_childNodes.last()->item().name() == item.name()) {
563 // qCWarning(category) << "Already having" << item.name() << "in" << directoryUrl
564 // << "url=" << dirNode->m_childNodes.last()->item().url();
565 // abort();
566 //}
567#endif
568 dirNode->m_childNodes.append(node);
569 const QUrl url = item.url();
570 m_nodeHash.insert(cleanupUrl(url), node);
571
572 if (!urlsBeingFetched.isEmpty()) {
573 const QUrl &dirUrl = url;
574 for (const QUrl &urlFetched : std::as_const(urlsBeingFetched)) {
575 if (dirUrl.matches(urlFetched, QUrl::StripTrailingSlash) || dirUrl.isParentOf(urlFetched)) {
576 // qDebug() << "Listing found" << dirUrl.url() << "which is a parent of fetched url" << urlFetched;
577 const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count() - 1);
578 Q_ASSERT(parentIndex.isValid());
579 emitExpandFor.append(parentIndex);
580 if (isDir && dirUrl != urlFetched) {
581 q->fetchMore(parentIndex);
582 m_urlsBeingFetched[node].append(urlFetched);
583 }
584 }
585 }
586 }
587 }
588
589 q->endInsertRows();
590
591 // Emit expand signal after rowsInserted signal has been emitted,
592 // so that any proxy model will have updated its mapping already
593 for (const QModelIndex &idx : std::as_const(emitExpandFor)) {
594 Q_EMIT q->expand(idx);
595 }
596}
597
598void KDirModelPrivate::_k_slotCompleted(const QUrl &directoryUrl)
599{
600 KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth)
601 Q_ASSERT(isDir(result));
602 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(result);
603 m_urlsBeingFetched.remove(dirNode);
604}
605
606void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList &items)
607{
608 qCDebug(category) << items.count() << "items";
609
610 // I assume all items are from the same directory.
611 // From KDirLister's code, this should be the case, except maybe emitChanges?
612
613 // We need to find first item with existing node
614 // because the first deleted item could be a hidden file not belonging to any node.
615 auto findFirstNodeAndUrl = [this](const KFileItemList &items) -> QPair<KDirModelNode *, QUrl> {
616 for (const KFileItem &item : items) {
617 Q_ASSERT(!item.isNull());
618 const QUrl url = item.url();
619 KDirModelNode *node = nodeForUrl(url); // O(depth)
620 if (node) {
621 return {node, url};
622 } else {
623 qCWarning(category) << "No node found for item that was just removed:" << url;
624 }
625 }
626 return {nullptr, QUrl()};
627 };
628
629 auto [node, url] = findFirstNodeAndUrl(items);
630 if (!node) {
631 return;
632 }
633
634 KDirModelDirNode *dirNode = node->parent();
635 if (!dirNode) {
636 return;
637 }
638
639 QModelIndex parentIndex = indexForNode(dirNode); // O(n)
640
641 // Short path for deleting a single item
642 if (items.count() == 1) {
643 const int r = node->rowNumber();
644 q->beginRemoveRows(parentIndex, r, r);
645 removeFromNodeHash(node, url);
646 delete dirNode->m_childNodes.takeAt(r);
647 q->endRemoveRows();
648 return;
649 }
650
651 // We need to make lists of consecutive row numbers, for the beginRemoveRows call.
652 // Let's use a bit array where each bit represents a given child node.
653 const int childCount = dirNode->m_childNodes.count();
654 QBitArray rowNumbers(childCount, false);
655 for (const KFileItem &item : items) {
656 url = item.url();
657 node = nodeForUrl(url);
658 if (!node) {
659 qCWarning(category) << "No node found for item that was just removed:" << url;
660 continue;
661 }
662 if (!node->parent()) {
663 // The root node has been deleted, but it was not first in the list 'items'.
664 // see https://bugs.kde.org/show_bug.cgi?id=196695
665 return;
666 }
667 rowNumbers.setBit(node->rowNumber(), 1); // O(n)
668 removeFromNodeHash(node, url);
669 }
670
671 int start = -1;
672 int end = -1;
673 bool lastVal = false;
674 // Start from the end, otherwise all the row numbers are offset while we go
675 for (int i = childCount - 1; i >= 0; --i) {
676 const bool val = rowNumbers.testBit(i);
677 if (!lastVal && val) {
678 end = i;
679 // qDebug() << "end=" << end;
680 }
681 if ((lastVal && !val) || (i == 0 && val)) {
682 start = val ? i : i + 1;
683 // qDebug() << "beginRemoveRows" << start << end;
684 q->beginRemoveRows(parentIndex, start, end);
685 for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
686 // qDebug() << "Removing from m_childNodes at" << r;
687 delete dirNode->m_childNodes.takeAt(r);
688 }
689 q->endRemoveRows();
690 }
691 lastVal = val;
692 }
693}
694
695void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items)
696{
697 QModelIndex topLeft;
698 QModelIndex bottomRight;
699
700 // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
701 // Solution 2: more fine-grained, actually figure out the beginning and end rows.
702 for (const auto &[oldItem, newItem] : items) {
703 Q_ASSERT(!oldItem.isNull());
704 Q_ASSERT(!newItem.isNull());
705 const QUrl oldUrl = oldItem.url();
706 const QUrl newUrl = newItem.url();
707 KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
708 // qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node;
709 if (!node) { // not found [can happen when renaming a dir, redirection was emitted already]
710 continue;
711 }
712 if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
713 bool hasNewNode = false;
714 // A file became directory (well, it was overwritten)
715 if (oldItem.isDir() != newItem.isDir()) {
716 // qDebug() << "DIR/FILE STATUS CHANGE";
717 const int r = node->rowNumber();
718 removeFromNodeHash(node, oldUrl);
719 KDirModelDirNode *dirNode = node->parent();
720 delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node"
721 node = newItem.isDir() ? new KDirModelDirNode(dirNode, newItem) : new KDirModelNode(dirNode, newItem);
722 dirNode->m_childNodes.insert(r, node); // same position!
723 hasNewNode = true;
724 } else {
725 node->setItem(newItem);
726 }
727
728 if (oldUrl != newUrl || hasNewNode) {
729 // What if a renamed dir had children? -> kdirlister takes care of emitting for each item
730 // qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
731 m_nodeHash.remove(cleanupUrl(oldUrl));
732 m_nodeHash.insert(cleanupUrl(newUrl), node);
733 }
734 // MIME type changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
735 if (oldItem.determineMimeType().name() != newItem.determineMimeType().name()) {
736 node->setPreview(QIcon());
737 }
738
739 const QModelIndex index = indexForNode(node);
740 if (!topLeft.isValid() || index.row() < topLeft.row()) {
741 topLeft = index;
742 }
743 if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
744 bottomRight = index;
745 }
746 }
747 }
748 // qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
749 bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1);
750 Q_EMIT q->dataChanged(topLeft, bottomRight);
751}
752
753// Called when a KIO worker redirects (e.g. smb:/Workgroup -> smb://workgroup)
754// and when renaming a directory.
755void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl)
756{
757 KDirModelNode *node = nodeForUrl(oldUrl);
758 if (!node) {
759 return;
760 }
761 m_nodeHash.remove(cleanupUrl(oldUrl));
762 m_nodeHash.insert(cleanupUrl(newUrl), node);
763
764 // Ensure the node's URL is updated. In case of a listjob redirection
765 // we won't get a refreshItem, and in case of renaming a directory
766 // we'll get it too late (so the hash won't find the old url anymore).
767 KFileItem item = node->item();
768 if (!item.isNull()) { // null if root item, #180156
769 item.setUrl(newUrl);
770 node->setItem(item);
771 }
772
773 // The items inside the renamed directory have been handled before,
774 // KDirLister took care of emitting refreshItem for each of them.
775}
776
777void KDirModelPrivate::_k_slotClear()
778{
779 const int numRows = m_rootNode->m_childNodes.count();
780 if (numRows > 0) {
781 q->beginRemoveRows(QModelIndex(), 0, numRows - 1);
782 }
783 m_nodeHash.clear();
784 clear();
785 if (numRows > 0) {
786 q->endRemoveRows();
787 }
788}
789
790void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList)
791{
792 QStringList dirtyUrls;
793
794 std::set_symmetric_difference(urlList.begin(),
795 urlList.end(),
796 m_allCurrentDestUrls.constBegin(),
797 m_allCurrentDestUrls.constEnd(),
798 std::back_inserter(dirtyUrls));
799
800 m_allCurrentDestUrls = urlList;
801
802 for (const QString &dirtyUrl : std::as_const(dirtyUrls)) {
803 if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) {
804 const QModelIndex idx = indexForNode(node);
805 Q_EMIT q->dataChanged(idx, idx, {KDirModel::HasJobRole});
806 }
807 }
808}
809
810void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode)
811{
812 const int numRows = dirNode->m_childNodes.count();
813 if (numRows > 0) {
814 KDirModelNode *lastNode = nullptr;
815 for (KDirModelNode *node : std::as_const(dirNode->m_childNodes)) {
816 node->setPreview(QIcon());
817 // node->setPreview(QIcon::fromTheme(node->item().iconName()));
818 if (isDir(node)) {
819 // recurse into child dirs
820 clearAllPreviews(static_cast<KDirModelDirNode *>(node));
821 }
822 lastNode = node;
823 }
824 Q_EMIT q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1)
825 indexForNode(lastNode, numRows - 1)); // O(1)
826 }
827}
828
830{
831 d->clearAllPreviews(d->m_rootNode);
832}
833
835{
836 // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator.
837 // When the MIME type is determined, clear the old "preview" (could be
838 // MIME type dependent like when cutting files, #164185)
839 KDirModelNode *node = d->nodeForIndex(index);
840 if (node) {
841 node->setPreview(QIcon());
842 }
843
844 qCDebug(category) << "dataChanged(" << debugIndex(index) << ")";
846}
847
849{
850 return ColumnCount;
851}
852
853QVariant KDirModel::data(const QModelIndex &index, int role) const
854{
855 if (index.isValid()) {
856 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
857 const KFileItem &item(node->item());
858 switch (role) {
859 case Qt::DisplayRole:
860 switch (index.column()) {
861 case Name:
862 return item.text();
863 case Size:
864 return KIO::convertSize(item.size()); // size formatted as QString
865 case ModifiedTime: {
866 QDateTime dt = item.time(KFileItem::ModificationTime);
868 }
869 case Permissions:
870 return item.permissionsString();
871 case Owner:
872 return item.user();
873 case Group:
874 return item.group();
875 case Type:
876 return item.mimeComment();
877 }
878 break;
879 case Qt::EditRole:
880 switch (index.column()) {
881 case Name:
882 return item.text();
883 }
884 break;
886 if (index.column() == Name) {
887 if (!node->preview().isNull()) {
888 // qDebug() << item->url() << " preview found";
889 return node->preview();
890 }
891 Q_ASSERT(!item.isNull());
892 // qDebug() << item->url() << " overlays=" << item->overlays();
893 static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown"));
894
895 const QString iconName(item.iconName());
896 QIcon icon;
897
898 if (QDir::isAbsolutePath(iconName)) {
899 icon = QIcon(iconName);
900 }
901 if (icon.isNull()
902 || (!(iconName.endsWith(QLatin1String(".svg")) || iconName.endsWith(QLatin1String(".svgz"))) && icon.availableSizes().isEmpty())) {
903 icon = QIcon::fromTheme(iconName, fallbackIcon);
904 }
905
906 const auto parentNode = node->parent();
907 if (parentNode->isOnNetwork()) {
908 return icon;
909 } else {
910 return KIconUtils::addOverlays(icon, item.overlays());
911 }
912 }
913 break;
915 if (index.column() == Size) {
916 // use a right alignment for L2R and R2L languages
918 return int(alignment);
919 }
920 break;
921 case Qt::ToolTipRole:
922 return item.text();
923 case FileItemRole:
924 return QVariant::fromValue(item);
925 case ChildCountRole:
926 if (!item.isDir()) {
927 return ChildCountUnknown;
928 } else {
929 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(node);
930 int count = dirNode->childCount();
931 if (count == ChildCountUnknown && !dirNode->isOnNetwork() && item.isReadable()) {
932 const QString path = item.localPath();
933 if (!path.isEmpty()) {
934// slow
935// QDir dir(path);
936// count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
937#ifdef Q_OS_WIN
938 QString s = path + QLatin1String("\\*.*");
939 s.replace(QLatin1Char('/'), QLatin1Char('\\'));
940 count = 0;
941 WIN32_FIND_DATA findData;
942 HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData);
943 if (hFile != INVALID_HANDLE_VALUE) {
944 do {
945 if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0')
946 && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) {
947 ++count;
948 }
949 } while (FindNextFile(hFile, &findData) != 0);
950 FindClose(hFile);
951 }
952#else
953 DIR *dir = QT_OPENDIR(QFile::encodeName(path).constData());
954 if (dir) {
955 count = 0;
956 QT_DIRENT *dirEntry = nullptr;
957 while ((dirEntry = QT_READDIR(dir))) {
958 if (dirEntry->d_name[0] == '.') {
959 if (dirEntry->d_name[1] == '\0') { // skip "."
960 continue;
961 }
962 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".."
963 continue;
964 }
965 }
966 ++count;
967 }
968 QT_CLOSEDIR(dir);
969 }
970#endif
971 // qDebug() << "child count for " << path << ":" << count;
972 dirNode->setChildCount(count);
973 }
974 }
975 return count;
976 }
977 case HasJobRole:
978 if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) {
979 KDirModelNode *node = d->nodeForIndex(index);
980 const QString url = node->item().url().toString();
981 // return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint.
982 return QVariant(d->m_allCurrentDestUrls.contains(url));
983 }
984 }
985 }
986 return QVariant();
987}
988
989void KDirModel::sort(int column, Qt::SortOrder order)
990{
991 // Not implemented - we should probably use QSortFilterProxyModel instead.
992 QAbstractItemModel::sort(column, order);
993}
994
995bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role)
996{
997 switch (role) {
998 case Qt::EditRole:
999 if (index.column() == Name && value.typeId() == QMetaType::QString) {
1000 Q_ASSERT(index.isValid());
1001 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
1002 const KFileItem &item = node->item();
1003 const QString newName = value.toString();
1004 if (newName.isEmpty() || newName == item.text() || (newName == QLatin1Char('.')) || (newName == QLatin1String(".."))) {
1005 return true;
1006 }
1007 QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename);
1008 newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
1009 KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
1011 // undo handling
1012 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList<QUrl>() << item.url(), newUrl, job);
1013 return true;
1014 }
1015 break;
1016 case Qt::DecorationRole:
1017 if (index.column() == Name) {
1018 Q_ASSERT(index.isValid());
1019 // Set new pixmap - e.g. preview
1020 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
1021 // qDebug() << "setting icon for " << node->item()->url();
1022 Q_ASSERT(node);
1023 if (value.typeId() == QMetaType::QIcon) {
1024 const QIcon icon(qvariant_cast<QIcon>(value));
1025 node->setPreview(icon);
1026 } else if (value.typeId() == QMetaType::QPixmap) {
1027 node->setPreview(qvariant_cast<QPixmap>(value));
1028 }
1030 return true;
1031 }
1032 break;
1033 default:
1034 break;
1035 }
1036 return false;
1037}
1038
1039int KDirModel::rowCount(const QModelIndex &parent) const
1040{
1041 if (parent.column() > 0) { // for QAbstractItemModelTester
1042 return 0;
1043 }
1044 KDirModelNode *node = d->nodeForIndex(parent);
1045 if (!node || !d->isDir(node)) { // #176555
1046 return 0;
1047 }
1048
1049 KDirModelDirNode *parentNode = static_cast<KDirModelDirNode *>(node);
1050 Q_ASSERT(parentNode);
1051 const int count = parentNode->m_childNodes.count();
1052#if 0
1053 QStringList filenames;
1054 for (int i = 0; i < count; ++i) {
1055 filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
1056 }
1057 qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
1058#endif
1059 return count;
1060}
1061
1063{
1064 if (!index.isValid()) {
1065 return QModelIndex();
1066 }
1067 KDirModelNode *childNode = static_cast<KDirModelNode *>(index.internalPointer());
1068 Q_ASSERT(childNode);
1069 KDirModelNode *parentNode = childNode->parent();
1070 Q_ASSERT(parentNode);
1071 return d->indexForNode(parentNode); // O(n)
1072}
1073
1074// Reimplemented to avoid the default implementation which calls parent
1075// (O(n) for finding the parent's row number for nothing). This implementation is O(1).
1076QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const
1077{
1078 if (!index.isValid()) {
1079 return QModelIndex();
1080 }
1081 KDirModelNode *oldChildNode = static_cast<KDirModelNode *>(index.internalPointer());
1082 Q_ASSERT(oldChildNode);
1083 KDirModelNode *parentNode = oldChildNode->parent();
1084 Q_ASSERT(parentNode);
1085 Q_ASSERT(d->isDir(parentNode));
1086 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
1087 if (childNode) {
1088 return createIndex(row, column, childNode);
1089 }
1090 return QModelIndex();
1091}
1092
1093void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex)
1094{
1095 Q_EMIT needSequenceIcon(index, sequenceIndex);
1096}
1097
1099{
1100 if (d->m_jobTransfersVisible == show) {
1101 return;
1102 }
1103
1104 d->m_jobTransfersVisible = show;
1105 if (show) {
1106 connect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, [this](const QStringList &urlList) {
1107 d->_k_slotJobUrlsChanged(urlList);
1108 });
1109
1110 JobUrlCache::instance().requestJobUrlsChanged();
1111 } else {
1112 disconnect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, nullptr);
1113 }
1114}
1115
1117{
1118 return d->m_jobTransfersVisible;
1119}
1120
1122{
1123 if (urls.isEmpty()) {
1124 return urls;
1125 }
1126
1127 QList<QUrl> ret(urls);
1128 std::sort(ret.begin(), ret.end());
1129
1130 QUrl url;
1131
1132 auto filterFunc = [&url](const QUrl &u) {
1133 if (url == u || url.isParentOf(u)) {
1134 return true;
1135 } else {
1136 url = u;
1137 return false;
1138 }
1139 };
1140
1141 auto beginIt = ret.begin();
1142 url = *beginIt;
1143 ++beginIt;
1144 auto it = std::remove_if(beginIt, ret.end(), filterFunc);
1145 ret.erase(it, ret.end());
1146
1147 return ret;
1148}
1149
1154
1155QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const
1156{
1157 QList<QUrl> urls;
1158 QList<QUrl> mostLocalUrls;
1159 urls.reserve(indexes.size());
1160 mostLocalUrls.reserve(indexes.size());
1161 bool canUseMostLocalUrls = true;
1162 for (const QModelIndex &index : indexes) {
1163 const KFileItem &item = d->nodeForIndex(index)->item();
1164 urls.append(item.url());
1165 const auto [url, isLocal] = item.isMostLocalUrl();
1166 mostLocalUrls.append(url);
1167 if (!isLocal) {
1168 canUseMostLocalUrls = false;
1169 }
1170 }
1171 QMimeData *data = new QMimeData();
1172 const bool different = canUseMostLocalUrls && (mostLocalUrls != urls);
1173 urls = simplifiedUrlList(urls);
1174 if (different) {
1175 mostLocalUrls = simplifiedUrlList(mostLocalUrls);
1176 KUrlMimeData::setUrls(urls, mostLocalUrls, data);
1177 } else {
1178 data->setUrls(urls);
1179 }
1180
1181 return data;
1182}
1183
1184// Public API; not much point in calling it internally
1186{
1187 if (!index.isValid()) {
1188 if (d->m_showNodeForListedUrl) {
1189 return {};
1190 }
1191 return d->m_dirLister->rootItem();
1192 } else {
1193 return static_cast<KDirModelNode *>(index.internalPointer())->item();
1194 }
1195}
1196
1198{
1199 return indexForUrl(item.url()); // O(n)
1200}
1201
1202// url -> index. O(n)
1204{
1205 KDirModelNode *node = d->nodeForUrl(url); // O(depth)
1206 if (!node) {
1207 // qDebug() << url << "not found";
1208 return QModelIndex();
1209 }
1210 return d->indexForNode(node); // O(n)
1211}
1212
1213QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const
1214{
1215 KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1)
1216 Q_ASSERT(parentNode);
1217 if (d->isDir(parentNode)) {
1218 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
1219 if (childNode) {
1220 return createIndex(row, column, childNode);
1221 }
1222 }
1223 return QModelIndex();
1224}
1225
1226QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const
1227{
1228 if (orientation == Qt::Horizontal) {
1229 switch (role) {
1230 case Qt::DisplayRole:
1231 switch (section) {
1232 case Name:
1233 return i18nc("@title:column", "Name");
1234 case Size:
1235 return i18nc("@title:column", "Size");
1236 case ModifiedTime:
1237 return i18nc("@title:column", "Date");
1238 case Permissions:
1239 return i18nc("@title:column", "Permissions");
1240 case Owner:
1241 return i18nc("@title:column", "Owner");
1242 case Group:
1243 return i18nc("@title:column", "Group");
1244 case Type:
1245 return i18nc("@title:column", "Type");
1246 }
1247 }
1248 }
1249 return QVariant();
1250}
1251
1252bool KDirModel::hasChildren(const QModelIndex &parent) const
1253{
1254 if (!parent.isValid()) {
1255 return true;
1256 }
1257
1258 const KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer());
1259 const KFileItem &parentItem = parentNode->item();
1260 Q_ASSERT(!parentItem.isNull());
1261 if (!parentItem.isDir()) {
1262 return false;
1263 }
1264 if (static_cast<const KDirModelDirNode *>(parentNode)->isPopulated()) {
1265 return !static_cast<const KDirModelDirNode *>(parentNode)->m_childNodes.isEmpty();
1266 }
1267 if (parentItem.isLocalFile() && !static_cast<const KDirModelDirNode *>(parentNode)->isOnNetwork()) {
1269
1270 if (d->m_dirLister->dirOnlyMode()) {
1271 filters |= QDir::NoSymLinks;
1272 } else {
1273 filters |= QDir::Files | QDir::System;
1274 }
1275
1276 if (d->m_dirLister->showHiddenFiles()) {
1277 filters |= QDir::Hidden;
1278 }
1279
1280 QDirIterator it(parentItem.localPath(), filters, QDirIterator::Subdirectories);
1281 return it.hasNext();
1282 }
1283 // Remote and not listed yet, we can't know; let the user click on it so we'll find out
1284 return true;
1285}
1286
1288{
1289 Qt::ItemFlags f;
1290 if (index.isValid()) {
1291 f |= Qt::ItemIsEnabled;
1292 if (index.column() == Name) {
1294 }
1295 }
1296
1297 // Allow dropping onto this item?
1298 if (d->m_dropsAllowed != NoDrops) {
1299 if (!index.isValid()) {
1300 if (d->m_dropsAllowed & DropOnDirectory) {
1302 }
1303 } else {
1305 if (item.isNull()) {
1306 qCWarning(category) << "Invalid item returned for index";
1307 } else if (item.isDir()) {
1308 if (d->m_dropsAllowed & DropOnDirectory) {
1310 }
1311 } else { // regular file item
1312 if (d->m_dropsAllowed & DropOnAnyFile) {
1314 } else if (d->m_dropsAllowed & DropOnLocalExecutable) {
1315 if (!item.localPath().isEmpty()) {
1316 // Desktop file?
1317 if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) {
1319 }
1320 // Executable, shell script ... ?
1321 else if (QFileInfo(item.localPath()).isExecutable()) {
1323 }
1324 }
1325 }
1326 }
1327 }
1328 }
1329
1330 return f;
1331}
1332
1333bool KDirModel::canFetchMore(const QModelIndex &parent) const
1334{
1335 if (!parent.isValid()) {
1336 return false;
1337 }
1338
1339 // We now have a bool KDirModelNode::m_populated,
1340 // to avoid calling fetchMore more than once on empty dirs.
1341 // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
1342 // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
1343
1344 KDirModelNode *node = static_cast<KDirModelNode *>(parent.internalPointer());
1345 const KFileItem &item = node->item();
1346 return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated() && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
1347}
1348
1350{
1351 if (!parent.isValid()) {
1352 return;
1353 }
1354
1355 KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer());
1356
1357 KFileItem parentItem = parentNode->item();
1358 Q_ASSERT(!parentItem.isNull());
1359 if (!parentItem.isDir()) {
1360 return;
1361 }
1362 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(parentNode);
1363 if (dirNode->isPopulated()) {
1364 return;
1365 }
1366 dirNode->setPopulated(true);
1367
1368 const QUrl parentUrl = parentItem.url();
1369 d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
1370}
1371
1372bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
1373{
1374 // Not sure we want to implement any drop handling at this level,
1375 // but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
1376 Q_UNUSED(data);
1377 Q_UNUSED(action);
1378 Q_UNUSED(row);
1379 Q_UNUSED(column);
1380 Q_UNUSED(parent);
1381 return false;
1382}
1383
1385{
1386 d->m_dropsAllowed = dropsAllowed;
1387}
1388
1390{
1391 // emit expand for each parent and return last parent
1392 KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth)
1393
1394 if (!result) { // doesn't seem related to our base url?
1395 qCDebug(category) << url << "does not seem related to our base URL, aborting";
1396 return;
1397 }
1398 if (!result->item().isNull() && result->item().url() == url) {
1399 // We have it already, nothing to do
1400 qCDebug(category) << "we have it already:" << url;
1401 return;
1402 }
1403
1404 d->m_urlsBeingFetched[result].append(url);
1405
1406 if (result == d->m_rootNode) {
1407 qCDebug(category) << "Remembering to emit expand after listing the root url";
1408 // the root is fetched by default, so it must be currently being fetched
1409 return;
1410 }
1411
1412 qCDebug(category) << "Remembering to emit expand after listing" << result->item().url();
1413
1414 // start a new fetch to look for the next level down the URL
1415 const QModelIndex parentIndex = d->indexForNode(result); // O(n)
1416 Q_ASSERT(parentIndex.isValid());
1417 fetchMore(parentIndex);
1418}
1419
1420bool KDirModel::insertRows(int, int, const QModelIndex &)
1421{
1422 return false;
1423}
1424
1425bool KDirModel::insertColumns(int, int, const QModelIndex &)
1426{
1427 return false;
1428}
1429
1430bool KDirModel::removeRows(int, int, const QModelIndex &)
1431{
1432 return false;
1433}
1434
1435bool KDirModel::removeColumns(int, int, const QModelIndex &)
1436{
1437 return false;
1438}
1439
1441{
1442 auto super = QAbstractItemModel::roleNames();
1443
1444 super[AdditionalRoles::FileItemRole] = "fileItem";
1445 super[AdditionalRoles::ChildCountRole] = "childCount";
1446 super[AdditionalRoles::HasJobRole] = "hasJob";
1447
1448 return super;
1449}
1450
1451#include "moc_kdirmodel.cpp"
void listingDirCompleted(const QUrl &dirUrl)
Tell the view that the listing of the directory dirUrl is finished.
void clear()
Signals to the view to remove all items (when e.g. going from dirA to dirB).
QUrl url() const
Returns the top level URL that is listed by this KCoreDirLister.
void refreshItems(const QList< QPair< KFileItem, KFileItem > > &items)
Signal an item to refresh (its MIME-type/icon/name has changed).
void redirection(const QUrl &oldUrl, const QUrl &newUrl)
Signals a redirection.
void itemsDeleted(const KFileItemList &items)
Signal that items have been deleted.
@ Reload
Indicates whether to use the cache or to reread the directory from the disk.
@ Keep
Previous directories aren't forgotten.
@ NoFlags
No additional flags specified.
void itemsAdded(const QUrl &directoryUrl, const KFileItemList &items)
Signal that new items were found during directory listing.
Subclass of KCoreDirLister which uses QWidgets to show error messages and to associate jobs with wind...
Definition kdirlister.h:25
A model for a KIO-based directory tree.
Definition kdirmodel.h:42
QHash< int, QByteArray > roleNames() const override
Reimplemented from QAbstractItemModel.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Reimplemented from QAbstractItemModel.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Reimplemented from QAbstractItemModel. Returns ColumnCount.
Q_INVOKABLE void itemChanged(const QModelIndex &index)
Notify the model that the item at this index has changed.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Reimplemented from QAbstractItemModel.
@ DropOnDirectory
allow drops on any directory
Definition kdirmodel.h:168
@ DropOnAnyFile
allow drops on any file
Definition kdirmodel.h:169
@ DropOnLocalExecutable
allow drops on local executables, shell scripts and desktop files. Can be used with DropOnDirectory.
Definition kdirmodel.h:170
QModelIndex sibling(int row, int column, const QModelIndex &index) const override
Reimplemented from QAbstractItemModel.
Q_INVOKABLE QModelIndex indexForItem(const KFileItem &) const
Return the index for a given kfileitem.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Reimplemented from QAbstractItemModel. O(1)
Q_INVOKABLE void clearAllPreviews()
Forget all previews (optimization for turning previews off).
Q_INVOKABLE void openUrl(const QUrl &url, OpenUrlFlags flags=NoFlags)
Display the contents of url in the model.
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
Reimplemented from QAbstractItemModel. Not implemented yet.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
Reimplemented from QAbstractItemModel. Returns the column titles.
@ FileItemRole
returns the KFileItem for a given index. roleName is "fileItem".
Definition kdirmodel.h:158
@ ChildCountRole
returns the number of items in a directory, or ChildCountUnknown. roleName is "childCount".
Definition kdirmodel.h:159
@ HasJobRole
returns whether or not there is a job on an item (file/directory). roleName is "hasJob".
Definition kdirmodel.h:160
void fetchMore(const QModelIndex &parent) override
Reimplemented from QAbstractItemModel. Lists the subdirectory.
void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override
Reimplemented from QAbstractItemModel. Not implemented.
QMimeData * mimeData(const QModelIndexList &indexes) const override
Reimplemented from QAbstractItemModel.
void needSequenceIcon(const QModelIndex &index, int sequenceIndex)
Emitted when another icon sequence index is requested.
KDirLister * dirLister() const
Return the directory lister used by this model.
void setJobTransfersVisible(bool show)
Enable/Disable the displaying of an animated overlay that is shown for any destination urls (in the v...
bool canFetchMore(const QModelIndex &parent) const override
Reimplemented from QAbstractItemModel. Returns true for empty directories.
KDirModel(QObject *parent=nullptr)
bool jobTransfersVisible() const
Returns whether or not displaying job transfers has been enabled.
void setDirLister(KDirLister *dirLister)
Set the directory lister to use by this model, instead of the default KDirLister created internally.
Q_INVOKABLE void setDropsAllowed(DropsAllowed dropsAllowed)
Set whether dropping onto items should be allowed, and for which kind of item Drops are disabled by d...
@ ShowRoot
Display a root node for the URL being opened.
Definition kdirmodel.h:63
@ Reload
Indicates whether to use the cache or to reread the directory from the disk.
Definition kdirmodel.h:59
KFileItem itemForIndex(const QModelIndex &index) const
Return the fileitem for a given index.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Reimplemented from QAbstractItemModel.
Q_INVOKABLE void expandToUrl(const QUrl &url)
Lists subdirectories using fetchMore() as needed until the given url exists in the model.
QStringList mimeTypes() const override
Reimplemented from QAbstractItemModel.
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
Reimplemented from QAbstractItemModel. Returns true for directories.
static QList< QUrl > simplifiedUrlList(const QList< QUrl > &urls)
Remove urls from the list if an ancestor is present on the list.
Q_INVOKABLE QModelIndex indexForUrl(const QUrl &url) const
Return the index for a given url.
void requestSequenceIcon(const QModelIndex &index, int sequenceIndex)
This emits the needSequenceIcon signal, requesting another sequence icon.
Qt::ItemFlags flags(const QModelIndex &index) const override
Reimplemented from QAbstractItemModel.
List of KFileItems, which adds a few helper methods to QList<KFileItem>.
Definition kfileitem.h:630
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
void setUrl(const QUrl &url)
Sets the item's URL.
KIO::filesize_t size() const
Returns the size of the file, if known.
Q_INVOKABLE QDateTime time(KFileItem::FileTimes which) const
Requests the modification, access or creation time, depending on which.
MostLocalUrlResult isMostLocalUrl() const
Returns a MostLocalUrlResult, with the local Url for this item if possible (otherwise an empty Url),...
bool isNull() const
Return true if default-constructed.
QString permissionsString() const
Returns the access permissions for the file as a string.
void setName(const QString &name)
Sets the item's name (i.e. the filename).
static FileUndoManager * self()
void recordJob(CommandType op, const QList< QUrl > &src, const QUrl &dst, KIO::Job *job)
Record this job while it's happening and add a command for it so that the user can undo it.
The base class for all jobs.
QString errorString() const override
Converts an error code and a non-i18n error message into an error message in the current language.
Definition job_error.cpp:26
A KIO job that retrieves information about a file or directory.
const UDSEntry & statResult() const
Result of the stat operation.
Definition statjob.cpp:80
Universal Directory Service.
void setAutoErrorHandlingEnabled(bool enable)
int error() const
void result(KJob *job)
KJobUiDelegate * uiDelegate() const
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
KIOCORE_EXPORT SimpleJob * rename(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Rename a file or directory.
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition statjob.cpp:203
KIOCORE_EXPORT QString convertSize(KIO::filesize_t size)
Converts size from bytes to the string representation.
Definition global.cpp:43
@ DefaultFlags
Show the progress info GUI, no Resume and no Overwrite.
Definition job_base.h:246
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
KIOCORE_EXPORT QString encodeFileName(const QString &str)
Encodes (from the text displayed to the real filename) This translates '/' into a "unicode fraction s...
Definition global.cpp:111
QIcon addOverlays(const QIcon &icon, const QHash< Qt::Corner, QIcon > &overlays)
const QList< QKeySequence > & end()
KCOREADDONS_EXPORT void setUrls(const QList< QUrl > &urls, const QList< QUrl > &mostLocalUrls, QMimeData *mimeData)
KCOREADDONS_EXPORT QStringList mimeDataTypes()
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
QModelIndex createIndex(int row, int column, const void *ptr) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QHash< int, QByteArray > roleNames() const const
virtual void sort(int column, Qt::SortOrder order)
QString cleanPath(const QString &path)
bool isAbsolutePath(const QString &path)
bool hasNext() const const
QByteArray encodeName(const QString &fileName)
bool isExecutable() const const
void clear()
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
void addPixmap(const QPixmap &pixmap, Mode mode, State state)
QList< QSize > availableSizes(Mode mode, State state) const const
QIcon fromTheme(const QString &name)
bool isNull() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
qsizetype indexOf(const AT &value, qsizetype from) const const
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
T takeAt(qsizetype i)
QString toString(QDate date, FormatType format) const const
size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
bool inherits(const QString &mimeTypeName) const const
int column() const const
void * internalPointer() const const
bool isValid() const const
int row() const const
QModelIndex sibling(int row, int column) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
const ushort * utf16() const const
typedef Alignment
typedef DropActions
DisplayRole
typedef ItemFlags
Orientation
SortOrder
StripTrailingSlash
QUrl adjusted(FormattingOptions options) const const
QString fileName(ComponentFormattingOptions options) const const
bool hasFragment() const const
bool hasQuery() const const
bool isLocalFile() const const
bool isParentOf(const QUrl &childUrl) const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QString scheme() const const
void setFragment(const QString &fragment, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
QString toString(FormattingOptions options) const const
QString url(FormattingOptions options) const const
QVariant fromValue(T &&value)
QString toString() const const
int typeId() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:11:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.