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 {
190 FsTypeUnknown,
191 LocalFs,
192 NetworkFs
193 } m_fsType : 3;
194};
195
196int KDirModelNode::rowNumber() const
197{
198 if (!m_parent) {
199 return 0;
200 }
201 return m_parent->m_childNodes.indexOf(const_cast<KDirModelNode *>(this));
202}
203
204////
205
206class KDirModelPrivate
207{
208public:
209 explicit KDirModelPrivate(KDirModel *qq)
210 : q(qq)
211 , m_rootNode(new KDirModelDirNode(nullptr, KFileItem()))
212 {
213 }
214 ~KDirModelPrivate()
215 {
216 delete m_rootNode;
217 }
218
219 void _k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &);
220 void _k_slotCompleted(const QUrl &directoryUrl);
221 void _k_slotDeleteItems(const KFileItemList &);
222 void _k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &);
223 void _k_slotClear();
224 void _k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl);
225 void _k_slotJobUrlsChanged(const QStringList &urlList);
226
227 void clear()
228 {
229 delete m_rootNode;
230 m_rootNode = new KDirModelDirNode(nullptr, KFileItem());
231 m_showNodeForListedUrl = false;
232 m_rootNode->setItem(KFileItem(m_dirLister->url()));
233 }
234
235 // Emit expand for each parent and then return the
236 // last known parent if there is no node for this url
237 KDirModelNode *expandAllParentsUntil(const QUrl &url) const;
238
239 // Return the node for a given url, using the hash.
240 KDirModelNode *nodeForUrl(const QUrl &url) const;
241 KDirModelNode *nodeForIndex(const QModelIndex &index) const;
242 QModelIndex indexForNode(KDirModelNode *node, int rowNumber = -1 /*unknown*/) const;
243
244 static QUrl rootParentOf(const QUrl &url)
245 {
246 // <url> is what we listed, and which is visible at the root of the tree
247 // Here we want the (invisible) parent of that url
249 if (url.path() == QLatin1String("/")) {
250 parent.setPath(QString());
251 }
252 return parent;
253 }
254
255 bool isDir(KDirModelNode *node) const
256 {
257 return (node == m_rootNode) || node->item().isDir();
258 }
259
260 QUrl urlForNode(KDirModelNode *node) const
261 {
262 /**
263 * Queries and fragments are removed from the URL, so that the URL of
264 * child items really starts with the URL of the parent.
265 *
266 * For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100
267 * so we have to remove the query in both to be able to compare the URLs
268 */
269 QUrl url;
270 if (node == m_rootNode && !m_showNodeForListedUrl) {
271 url = m_dirLister->url();
272 } else {
273 url = node->item().url();
274 }
275 if (url.scheme().startsWith(QStringLiteral("ksvn")) || url.scheme().startsWith(QStringLiteral("svn"))) {
276 if (url.hasQuery() || url.hasFragment()) { // avoid detach if not necessary.
277 url.setQuery(QString());
278 url.setFragment(QString()); // kill ref (#171117)
279 }
280 }
281 return url;
282 }
283
284 void removeFromNodeHash(KDirModelNode *node, const QUrl &url);
285 void clearAllPreviews(KDirModelDirNode *node);
286#ifndef NDEBUG
287 void dump();
288#endif
289 Q_DISABLE_COPY(KDirModelPrivate)
290
291 KDirModel *const q;
292 KDirLister *m_dirLister = nullptr;
293 KDirModelDirNode *m_rootNode = nullptr;
294 KDirModel::DropsAllowed m_dropsAllowed = KDirModel::NoDrops;
295 bool m_jobTransfersVisible = false;
296 bool m_showNodeForListedUrl = false;
297 // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient),
298 // value = final url[s] being fetched
299 QMap<KDirModelNode *, QList<QUrl>> m_urlsBeingFetched;
300 QHash<QUrl, KDirModelNode *> m_nodeHash; // global node hash: url -> node
301 QStringList m_allCurrentDestUrls; // list of all dest urls that have jobs on them (e.g. copy, download)
302};
303
304KDirModelNode *KDirModelPrivate::nodeForUrl(const QUrl &_url) const // O(1), well, O(length of url as a string)
305{
306 QUrl url = cleanupUrl(_url);
307 if (url == urlForNode(m_rootNode)) {
308 return m_rootNode;
309 }
310 return m_nodeHash.value(url);
311}
312
313void KDirModelPrivate::removeFromNodeHash(KDirModelNode *node, const QUrl &url)
314{
315 if (node->item().isDir()) {
316 const QList<QUrl> urls = static_cast<KDirModelDirNode *>(node)->collectAllChildUrls();
317 for (const QUrl &u : urls) {
318 m_nodeHash.remove(u);
319 }
320 }
321 m_nodeHash.remove(cleanupUrl(url));
322}
323
324KDirModelNode *KDirModelPrivate::expandAllParentsUntil(const QUrl &_url) const // O(depth)
325{
326 QUrl url = cleanupUrl(_url);
327
328 // qDebug() << url;
329 QUrl nodeUrl = urlForNode(m_rootNode);
330 KDirModelDirNode *dirNode = m_rootNode;
331 if (m_showNodeForListedUrl && !m_rootNode->m_childNodes.isEmpty()) {
332 dirNode = static_cast<KDirModelDirNode *>(m_rootNode->m_childNodes.at(0)); // ### will be incorrect if we list drives on Windows
333 nodeUrl = dirNode->item().url();
334 qCDebug(category) << "listed URL is visible, adjusted starting point to" << nodeUrl;
335 }
336 if (url == nodeUrl) {
337 return dirNode;
338 }
339
340 // Protocol mismatch? Don't even start comparing paths then. #171721
341 if (url.scheme() != nodeUrl.scheme()) {
342 qCWarning(category) << "protocol mismatch:" << url.scheme() << "vs" << nodeUrl.scheme();
343 return nullptr;
344 }
345
346 const QString pathStr = url.path(); // no trailing slash
347
348 if (!pathStr.startsWith(nodeUrl.path())) {
349 qCDebug(category) << pathStr << "does not start with" << nodeUrl.path();
350 return nullptr;
351 }
352
353 for (;;) {
354 QString nodePath = nodeUrl.path();
355 if (!nodePath.endsWith(QLatin1Char('/'))) {
356 nodePath += QLatin1Char('/');
357 }
358 if (!pathStr.startsWith(nodePath)) {
359 qCWarning(category) << "The KIO worker for" << url.scheme() << "violates the hierarchy structure:"
360 << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path.";
361 return nullptr;
362 }
363
364 // E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b
365 const int nextSlash = pathStr.indexOf(QLatin1Char('/'), nodePath.length());
366 const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1
367 nodeUrl.setPath(newPath);
368 nodeUrl = nodeUrl.adjusted(QUrl::StripTrailingSlash); // #172508
369 KDirModelNode *node = nodeForUrl(nodeUrl);
370 if (!node) {
371 qCDebug(category) << nodeUrl << "not found, needs to be listed";
372 // return last parent found:
373 return dirNode;
374 }
375
376 Q_EMIT q->expand(indexForNode(node));
377
378 // qDebug() << " nodeUrl=" << nodeUrl;
379 if (nodeUrl == url) {
380 qCDebug(category) << "Found node" << node << "for" << url;
381 return node;
382 }
383 qCDebug(category) << "going into" << node->item().url();
384 Q_ASSERT(isDir(node));
385 dirNode = static_cast<KDirModelDirNode *>(node);
386 }
387 // NOTREACHED
388 // return 0;
389}
390
391#ifndef NDEBUG
392void KDirModelPrivate::dump()
393{
394 qCDebug(category) << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url();
396 while (it.hasNext()) {
397 it.next();
398 qCDebug(category) << it.key() << it.value();
399 }
400}
401#endif
402
403// node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n).
404QModelIndex KDirModelPrivate::indexForNode(KDirModelNode *node, int rowNumber) const
405{
406 if (node == m_rootNode) {
407 return QModelIndex();
408 }
409
410 Q_ASSERT(node->parent());
411 return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node);
412}
413
414// index -> node. O(1)
415KDirModelNode *KDirModelPrivate::nodeForIndex(const QModelIndex &index) const
416{
417 return index.isValid() ? static_cast<KDirModelNode *>(index.internalPointer()) : m_rootNode;
418}
419
420/*
421 * This model wraps the data held by KDirLister.
422 *
423 * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree.
424 * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer=<KDirModelNode for the 3rd child of the root>
425 *
426 * Invalid parent index means root of the tree, m_rootNode
427 */
428
429static QString debugIndex(const QModelIndex &index)
430{
431 QString str;
432 if (!index.isValid()) {
433 str = QStringLiteral("[invalid index, i.e. root]");
434 } else {
435 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
436 str = QLatin1String("[index for ") + node->item().url().toString();
437 if (index.column() > 0) {
438 str += QLatin1String(", column ") + QString::number(index.column());
439 }
440 str += QLatin1Char(']');
441 }
442 return str;
443}
444
446 : QAbstractItemModel(parent)
447 , d(new KDirModelPrivate(this))
448{
449 setDirLister(new KDirLister(this));
450}
451
452KDirModel::~KDirModel() = default;
453
455{
456 if (d->m_dirLister) {
457 d->clear();
458 delete d->m_dirLister;
459 }
460 d->m_dirLister = dirLister;
461 d->m_dirLister->setParent(this);
462 connect(d->m_dirLister, &KCoreDirLister::itemsAdded, this, [this](const QUrl &dirUrl, const KFileItemList &items) {
463 d->_k_slotNewItems(dirUrl, items);
464 });
465 connect(d->m_dirLister, &KCoreDirLister::listingDirCompleted, this, [this](const QUrl &dirUrl) {
466 d->_k_slotCompleted(dirUrl);
467 });
468 connect(d->m_dirLister, &KCoreDirLister::itemsDeleted, this, [this](const KFileItemList &items) {
469 d->_k_slotDeleteItems(items);
470 });
471 connect(d->m_dirLister, &KCoreDirLister::refreshItems, this, [this](const QList<QPair<KFileItem, KFileItem>> &items) {
472 d->_k_slotRefreshItems(items);
473 });
474 connect(d->m_dirLister, qOverload<>(&KCoreDirLister::clear), this, [this]() {
475 d->_k_slotClear();
476 });
477 connect(d->m_dirLister, &KCoreDirLister::redirection, this, [this](const QUrl &oldUrl, const QUrl &newUrl) {
478 d->_k_slotRedirection(oldUrl, newUrl);
479 });
480}
481
482void KDirModel::openUrl(const QUrl &inputUrl, OpenUrlFlags flags)
483{
484 Q_ASSERT(d->m_dirLister);
485 const QUrl url = cleanupUrl(inputUrl);
486 if (flags & ShowRoot) {
487 d->_k_slotClear();
488 d->m_showNodeForListedUrl = true;
489 // Store the parent URL into the invisible root node
490 const QUrl parentUrl = d->rootParentOf(url);
491 d->m_rootNode->setItem(KFileItem(parentUrl));
492 // Stat the requested url, to create the visible node
494 connect(statJob, &KJob::result, this, [statJob, parentUrl, url, this]() {
495 if (!statJob->error()) {
496 const KIO::UDSEntry entry = statJob->statResult();
497 KFileItem visibleRootItem(entry, url);
498 visibleRootItem.setName(url.path() == QLatin1String("/") ? QStringLiteral("/") : url.fileName());
499 d->_k_slotNewItems(parentUrl, QList<KFileItem>{visibleRootItem});
500 Q_ASSERT(d->m_rootNode->m_childNodes.count() == 1);
501 expandToUrl(url);
502 } else {
503 qWarning() << statJob->errorString();
504 }
505 });
506 } else {
507 d->m_dirLister->openUrl(url, (flags & Reload) ? KDirLister::Reload : KDirLister::NoFlags);
508 }
509}
510
511Qt::DropActions KDirModel::supportedDropActions() const
512{
514}
515
517{
518 return d->m_dirLister;
519}
520
521void KDirModelPrivate::_k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &items)
522{
523 // qDebug() << "directoryUrl=" << directoryUrl;
524
525 KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth)
526 // If the directory containing the items wasn't found, then we have a big problem.
527 // Are you calling KDirLister::openUrl(url,Keep)? Please use expandToUrl() instead.
528 if (!result) {
529 qCWarning(category) << "Items emitted in directory" << directoryUrl << "but that directory isn't in KDirModel!"
530 << "Root directory:" << urlForNode(m_rootNode);
531 for (const KFileItem &item : items) {
532 qDebug() << "Item:" << item.url();
533 }
534#ifndef NDEBUG
535 dump();
536#endif
537 Q_ASSERT(result);
538 return;
539 }
540 Q_ASSERT(isDir(result));
541 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(result);
542
543 const QModelIndex index = indexForNode(dirNode); // O(n)
544 const int newItemsCount = items.count();
545 const int newRowCount = dirNode->m_childNodes.count() + newItemsCount;
546
547 qCDebug(category) << items.count() << "in" << directoryUrl << "index=" << debugIndex(index) << "newRowCount=" << newRowCount;
548
549 q->beginInsertRows(index, newRowCount - newItemsCount, newRowCount - 1); // parent, first, last
550
551 const QList<QUrl> urlsBeingFetched = m_urlsBeingFetched.value(dirNode);
552 if (!urlsBeingFetched.isEmpty()) {
553 qCDebug(category) << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched;
554 }
555
556 QList<QModelIndex> emitExpandFor;
557
558 dirNode->m_childNodes.reserve(newRowCount);
559 for (const auto &item : items) {
560 const bool isDir = item.isDir();
561 KDirModelNode *node = isDir ? new KDirModelDirNode(dirNode, item) : new KDirModelNode(dirNode, item);
562#ifndef NDEBUG
563 // Test code for possible duplication of items in the childnodes list,
564 // not sure if/how it ever happened.
565 // if (dirNode->m_childNodes.count() &&
566 // dirNode->m_childNodes.last()->item().name() == item.name()) {
567 // qCWarning(category) << "Already having" << item.name() << "in" << directoryUrl
568 // << "url=" << dirNode->m_childNodes.last()->item().url();
569 // abort();
570 //}
571#endif
572 dirNode->m_childNodes.append(node);
573 const QUrl url = item.url();
574 m_nodeHash.insert(cleanupUrl(url), node);
575
576 if (!urlsBeingFetched.isEmpty()) {
577 const QUrl &dirUrl = url;
578 for (const QUrl &urlFetched : std::as_const(urlsBeingFetched)) {
579 if (dirUrl.matches(urlFetched, QUrl::StripTrailingSlash) || dirUrl.isParentOf(urlFetched)) {
580 // qDebug() << "Listing found" << dirUrl.url() << "which is a parent of fetched url" << urlFetched;
581 const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count() - 1);
582 Q_ASSERT(parentIndex.isValid());
583 emitExpandFor.append(parentIndex);
584 if (isDir && dirUrl != urlFetched) {
585 q->fetchMore(parentIndex);
586 m_urlsBeingFetched[node].append(urlFetched);
587 }
588 }
589 }
590 }
591 }
592
593 q->endInsertRows();
594
595 // Emit expand signal after rowsInserted signal has been emitted,
596 // so that any proxy model will have updated its mapping already
597 for (const QModelIndex &idx : std::as_const(emitExpandFor)) {
598 Q_EMIT q->expand(idx);
599 }
600}
601
602void KDirModelPrivate::_k_slotCompleted(const QUrl &directoryUrl)
603{
604 KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth)
605 Q_ASSERT(isDir(result));
606 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(result);
607 m_urlsBeingFetched.remove(dirNode);
608}
609
610void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList &items)
611{
612 qCDebug(category) << items.count() << "items";
613
614 // I assume all items are from the same directory.
615 // From KDirLister's code, this should be the case, except maybe emitChanges?
616
617 // We need to find first item with existing node
618 // because the first deleted item could be a hidden file not belonging to any node.
619 auto findFirstNodeAndUrl = [this](const KFileItemList &items) -> QPair<KDirModelNode *, QUrl> {
620 for (const KFileItem &item : items) {
621 Q_ASSERT(!item.isNull());
622 const QUrl url = item.url();
623 KDirModelNode *node = nodeForUrl(url); // O(depth)
624 if (node) {
625 return {node, url};
626 } else {
627 qCWarning(category) << "No node found for item that was just removed:" << url;
628 }
629 }
630 return {nullptr, QUrl()};
631 };
632
633 auto [node, url] = findFirstNodeAndUrl(items);
634 if (!node) {
635 return;
636 }
637
638 KDirModelDirNode *dirNode = node->parent();
639 if (!dirNode) {
640 return;
641 }
642
643 QModelIndex parentIndex = indexForNode(dirNode); // O(n)
644
645 // Short path for deleting a single item
646 if (items.count() == 1) {
647 const int r = node->rowNumber();
648 q->beginRemoveRows(parentIndex, r, r);
649 removeFromNodeHash(node, url);
650 delete dirNode->m_childNodes.takeAt(r);
651 q->endRemoveRows();
652 return;
653 }
654
655 // We need to make lists of consecutive row numbers, for the beginRemoveRows call.
656 // Let's use a bit array where each bit represents a given child node.
657 const int childCount = dirNode->m_childNodes.count();
658 QBitArray rowNumbers(childCount, false);
659 for (const KFileItem &item : items) {
660 url = item.url();
661 node = nodeForUrl(url);
662 if (!node) {
663 qCWarning(category) << "No node found for item that was just removed:" << url;
664 continue;
665 }
666 if (!node->parent()) {
667 // The root node has been deleted, but it was not first in the list 'items'.
668 // see https://bugs.kde.org/show_bug.cgi?id=196695
669 return;
670 }
671 rowNumbers.setBit(node->rowNumber(), 1); // O(n)
672 removeFromNodeHash(node, url);
673 }
674
675 int start = -1;
676 int end = -1;
677 bool lastVal = false;
678 // Start from the end, otherwise all the row numbers are offset while we go
679 for (int i = childCount - 1; i >= 0; --i) {
680 const bool val = rowNumbers.testBit(i);
681 if (!lastVal && val) {
682 end = i;
683 // qDebug() << "end=" << end;
684 }
685 if ((lastVal && !val) || (i == 0 && val)) {
686 start = val ? i : i + 1;
687 // qDebug() << "beginRemoveRows" << start << end;
688 q->beginRemoveRows(parentIndex, start, end);
689 for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
690 // qDebug() << "Removing from m_childNodes at" << r;
691 delete dirNode->m_childNodes.takeAt(r);
692 }
693 q->endRemoveRows();
694 }
695 lastVal = val;
696 }
697}
698
699void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items)
700{
701 QModelIndex topLeft;
702 QModelIndex bottomRight;
703
704 // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
705 // Solution 2: more fine-grained, actually figure out the beginning and end rows.
706 for (const auto &[oldItem, newItem] : items) {
707 Q_ASSERT(!oldItem.isNull());
708 Q_ASSERT(!newItem.isNull());
709 const QUrl oldUrl = oldItem.url();
710 const QUrl newUrl = newItem.url();
711 KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
712 // qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node;
713 if (!node) { // not found [can happen when renaming a dir, redirection was emitted already]
714 continue;
715 }
716 if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
717 bool hasNewNode = false;
718 // A file became directory (well, it was overwritten)
719 if (oldItem.isDir() != newItem.isDir()) {
720 // qDebug() << "DIR/FILE STATUS CHANGE";
721 const int r = node->rowNumber();
722 removeFromNodeHash(node, oldUrl);
723 KDirModelDirNode *dirNode = node->parent();
724 delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node"
725 node = newItem.isDir() ? new KDirModelDirNode(dirNode, newItem) : new KDirModelNode(dirNode, newItem);
726 dirNode->m_childNodes.insert(r, node); // same position!
727 hasNewNode = true;
728 } else {
729 node->setItem(newItem);
730 }
731
732 if (oldUrl != newUrl || hasNewNode) {
733 // What if a renamed dir had children? -> kdirlister takes care of emitting for each item
734 // qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
735 m_nodeHash.remove(cleanupUrl(oldUrl));
736 m_nodeHash.insert(cleanupUrl(newUrl), node);
737 }
738 // MIME type changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
739 if (oldItem.determineMimeType().name() != newItem.determineMimeType().name()) {
740 node->setPreview(QIcon());
741 }
742
743 const QModelIndex index = indexForNode(node);
744 if (!topLeft.isValid() || index.row() < topLeft.row()) {
745 topLeft = index;
746 }
747 if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
748 bottomRight = index;
749 }
750 }
751 }
752 // qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
753 bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1);
754 Q_EMIT q->dataChanged(topLeft, bottomRight);
755}
756
757// Called when a KIO worker redirects (e.g. smb:/Workgroup -> smb://workgroup)
758// and when renaming a directory.
759void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl)
760{
761 KDirModelNode *node = nodeForUrl(oldUrl);
762 if (!node) {
763 return;
764 }
765 m_nodeHash.remove(cleanupUrl(oldUrl));
766 m_nodeHash.insert(cleanupUrl(newUrl), node);
767
768 // Ensure the node's URL is updated. In case of a listjob redirection
769 // we won't get a refreshItem, and in case of renaming a directory
770 // we'll get it too late (so the hash won't find the old url anymore).
771 KFileItem item = node->item();
772 if (!item.isNull()) { // null if root item, #180156
773 item.setUrl(newUrl);
774 node->setItem(item);
775 }
776
777 // The items inside the renamed directory have been handled before,
778 // KDirLister took care of emitting refreshItem for each of them.
779}
780
781void KDirModelPrivate::_k_slotClear()
782{
783 const int numRows = m_rootNode->m_childNodes.count();
784 if (numRows > 0) {
785 q->beginRemoveRows(QModelIndex(), 0, numRows - 1);
786 }
787 m_nodeHash.clear();
788 clear();
789 if (numRows > 0) {
790 q->endRemoveRows();
791 }
792}
793
794void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList)
795{
796 QStringList dirtyUrls;
797
798 std::set_symmetric_difference(urlList.begin(),
799 urlList.end(),
800 m_allCurrentDestUrls.constBegin(),
801 m_allCurrentDestUrls.constEnd(),
802 std::back_inserter(dirtyUrls));
803
804 m_allCurrentDestUrls = urlList;
805
806 for (const QString &dirtyUrl : std::as_const(dirtyUrls)) {
807 if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) {
808 const QModelIndex idx = indexForNode(node);
809 Q_EMIT q->dataChanged(idx, idx, {KDirModel::HasJobRole});
810 }
811 }
812}
813
814void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode)
815{
816 const int numRows = dirNode->m_childNodes.count();
817 if (numRows > 0) {
818 KDirModelNode *lastNode = nullptr;
819 for (KDirModelNode *node : std::as_const(dirNode->m_childNodes)) {
820 node->setPreview(QIcon());
821 // node->setPreview(QIcon::fromTheme(node->item().iconName()));
822 if (isDir(node)) {
823 // recurse into child dirs
824 clearAllPreviews(static_cast<KDirModelDirNode *>(node));
825 }
826 lastNode = node;
827 }
828 Q_EMIT q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1)
829 indexForNode(lastNode, numRows - 1)); // O(1)
830 }
831}
832
834{
835 d->clearAllPreviews(d->m_rootNode);
836}
837
839{
840 // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator.
841 // When the MIME type is determined, clear the old "preview" (could be
842 // MIME type dependent like when cutting files, #164185)
843 KDirModelNode *node = d->nodeForIndex(index);
844 if (node) {
845 node->setPreview(QIcon());
846 }
847
848 qCDebug(category) << "dataChanged(" << debugIndex(index) << ")";
850}
851
853{
854 return ColumnCount;
855}
856
857QVariant KDirModel::data(const QModelIndex &index, int role) const
858{
859 if (index.isValid()) {
860 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
861 const KFileItem &item(node->item());
862 switch (role) {
863 case Qt::DisplayRole:
864 switch (index.column()) {
865 case Name:
866 return item.text();
867 case Size:
868 return KIO::convertSize(item.size()); // size formatted as QString
869 case ModifiedTime: {
870 QDateTime dt = item.time(KFileItem::ModificationTime);
872 }
873 case Permissions:
874 return item.permissionsString();
875 case Owner:
876 return item.user();
877 case Group:
878 return item.group();
879 case Type:
880 return item.mimeComment();
881 }
882 break;
883 case Qt::EditRole:
884 switch (index.column()) {
885 case Name:
886 return item.text();
887 }
888 break;
890 if (index.column() == Name) {
891 if (!node->preview().isNull()) {
892 // qDebug() << item->url() << " preview found";
893 return node->preview();
894 }
895 Q_ASSERT(!item.isNull());
896 // qDebug() << item->url() << " overlays=" << item->overlays();
897 static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown"));
898
899 const QString iconName(item.iconName());
900 QIcon icon;
901
902 if (QDir::isAbsolutePath(iconName)) {
903 icon = QIcon(iconName);
904 }
905 if (icon.isNull()
906 || (!(iconName.endsWith(QLatin1String(".svg")) || iconName.endsWith(QLatin1String(".svgz"))) && icon.availableSizes().isEmpty())) {
907 icon = QIcon::fromTheme(iconName, fallbackIcon);
908 }
909
910 const auto parentNode = node->parent();
911 if (parentNode->isOnNetwork()) {
912 return icon;
913 } else {
914 return KIconUtils::addOverlays(icon, item.overlays());
915 }
916 }
917 break;
919 if (index.column() == Size) {
920 // use a right alignment for L2R and R2L languages
922 return int(alignment);
923 }
924 break;
925 case Qt::ToolTipRole:
926 return item.text();
927 case FileItemRole:
928 return QVariant::fromValue(item);
929 case ChildCountRole:
930 if (!item.isDir()) {
931 return ChildCountUnknown;
932 } else {
933 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(node);
934 int count = dirNode->childCount();
935 if (count == ChildCountUnknown && !dirNode->isOnNetwork() && item.isReadable()) {
936 const QString path = item.localPath();
937 if (!path.isEmpty()) {
938// slow
939// QDir dir(path);
940// count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
941#ifdef Q_OS_WIN
942 QString s = path + QLatin1String("\\*.*");
943 s.replace(QLatin1Char('/'), QLatin1Char('\\'));
944 count = 0;
945 WIN32_FIND_DATA findData;
946 HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData);
947 if (hFile != INVALID_HANDLE_VALUE) {
948 do {
949 if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0')
950 && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) {
951 ++count;
952 }
953 } while (FindNextFile(hFile, &findData) != 0);
954 FindClose(hFile);
955 }
956#else
957 DIR *dir = QT_OPENDIR(QFile::encodeName(path).constData());
958 if (dir) {
959 count = 0;
960 QT_DIRENT *dirEntry = nullptr;
961 while ((dirEntry = QT_READDIR(dir))) {
962 if (dirEntry->d_name[0] == '.') {
963 if (dirEntry->d_name[1] == '\0') { // skip "."
964 continue;
965 }
966 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".."
967 continue;
968 }
969 }
970 ++count;
971 }
972 QT_CLOSEDIR(dir);
973 }
974#endif
975 // qDebug() << "child count for " << path << ":" << count;
976 dirNode->setChildCount(count);
977 }
978 }
979 return count;
980 }
981 case HasJobRole:
982 if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) {
983 KDirModelNode *node = d->nodeForIndex(index);
984 const QString url = node->item().url().toString();
985 // return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint.
986 return QVariant(d->m_allCurrentDestUrls.contains(url));
987 }
988 }
989 }
990 return QVariant();
991}
992
993void KDirModel::sort(int column, Qt::SortOrder order)
994{
995 // Not implemented - we should probably use QSortFilterProxyModel instead.
996 QAbstractItemModel::sort(column, order);
997}
998
999bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role)
1000{
1001 switch (role) {
1002 case Qt::EditRole:
1003 if (index.column() == Name && value.typeId() == QMetaType::QString) {
1004 Q_ASSERT(index.isValid());
1005 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
1006 const KFileItem &item = node->item();
1007 const QString newName = value.toString();
1008 if (newName.isEmpty() || newName == item.text() || (newName == QLatin1Char('.')) || (newName == QLatin1String(".."))) {
1009 return true;
1010 }
1011 QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename);
1012 newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
1013 KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
1015 // undo handling
1016 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList<QUrl>() << item.url(), newUrl, job);
1017 return true;
1018 }
1019 break;
1020 case Qt::DecorationRole:
1021 if (index.column() == Name) {
1022 Q_ASSERT(index.isValid());
1023 // Set new pixmap - e.g. preview
1024 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
1025 // qDebug() << "setting icon for " << node->item()->url();
1026 Q_ASSERT(node);
1027 if (value.typeId() == QMetaType::QIcon) {
1028 const QIcon icon(qvariant_cast<QIcon>(value));
1029 node->setPreview(icon);
1030 } else if (value.typeId() == QMetaType::QPixmap) {
1031 node->setPreview(qvariant_cast<QPixmap>(value));
1032 }
1034 return true;
1035 }
1036 break;
1037 default:
1038 break;
1039 }
1040 return false;
1041}
1042
1043int KDirModel::rowCount(const QModelIndex &parent) const
1044{
1045 if (parent.column() > 0) { // for QAbstractItemModelTester
1046 return 0;
1047 }
1048 KDirModelNode *node = d->nodeForIndex(parent);
1049 if (!node || !d->isDir(node)) { // #176555
1050 return 0;
1051 }
1052
1053 KDirModelDirNode *parentNode = static_cast<KDirModelDirNode *>(node);
1054 Q_ASSERT(parentNode);
1055 const int count = parentNode->m_childNodes.count();
1056#if 0
1057 QStringList filenames;
1058 for (int i = 0; i < count; ++i) {
1059 filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
1060 }
1061 qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
1062#endif
1063 return count;
1064}
1065
1067{
1068 if (!index.isValid()) {
1069 return QModelIndex();
1070 }
1071 KDirModelNode *childNode = static_cast<KDirModelNode *>(index.internalPointer());
1072 Q_ASSERT(childNode);
1073 KDirModelNode *parentNode = childNode->parent();
1074 Q_ASSERT(parentNode);
1075 return d->indexForNode(parentNode); // O(n)
1076}
1077
1078// Reimplemented to avoid the default implementation which calls parent
1079// (O(n) for finding the parent's row number for nothing). This implementation is O(1).
1080QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const
1081{
1082 if (!index.isValid()) {
1083 return QModelIndex();
1084 }
1085 KDirModelNode *oldChildNode = static_cast<KDirModelNode *>(index.internalPointer());
1086 Q_ASSERT(oldChildNode);
1087 KDirModelNode *parentNode = oldChildNode->parent();
1088 Q_ASSERT(parentNode);
1089 Q_ASSERT(d->isDir(parentNode));
1090 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
1091 if (childNode) {
1092 return createIndex(row, column, childNode);
1093 }
1094 return QModelIndex();
1095}
1096
1097void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex)
1098{
1099 Q_EMIT needSequenceIcon(index, sequenceIndex);
1100}
1101
1103{
1104 if (d->m_jobTransfersVisible == show) {
1105 return;
1106 }
1107
1108 d->m_jobTransfersVisible = show;
1109 if (show) {
1110 connect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, [this](const QStringList &urlList) {
1111 d->_k_slotJobUrlsChanged(urlList);
1112 });
1113
1114 JobUrlCache::instance().requestJobUrlsChanged();
1115 } else {
1116 disconnect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, nullptr);
1117 }
1118}
1119
1121{
1122 return d->m_jobTransfersVisible;
1123}
1124
1126{
1127 if (urls.isEmpty()) {
1128 return urls;
1129 }
1130
1131 QList<QUrl> ret(urls);
1132 std::sort(ret.begin(), ret.end());
1133
1134 QUrl url;
1135
1136 auto filterFunc = [&url](const QUrl &u) {
1137 if (url == u || url.isParentOf(u)) {
1138 return true;
1139 } else {
1140 url = u;
1141 return false;
1142 }
1143 };
1144
1145 auto beginIt = ret.begin();
1146 url = *beginIt;
1147 ++beginIt;
1148 auto it = std::remove_if(beginIt, ret.end(), filterFunc);
1149 ret.erase(it, ret.end());
1150
1151 return ret;
1152}
1153
1158
1159QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const
1160{
1161 QList<QUrl> urls;
1162 QList<QUrl> mostLocalUrls;
1163 urls.reserve(indexes.size());
1164 mostLocalUrls.reserve(indexes.size());
1165 bool canUseMostLocalUrls = true;
1166 for (const QModelIndex &index : indexes) {
1167 const KFileItem &item = d->nodeForIndex(index)->item();
1168 urls.append(item.url());
1169 const auto [url, isLocal] = item.isMostLocalUrl();
1170 mostLocalUrls.append(url);
1171 if (!isLocal) {
1172 canUseMostLocalUrls = false;
1173 }
1174 }
1175 QMimeData *data = new QMimeData();
1176 const bool different = canUseMostLocalUrls && (mostLocalUrls != urls);
1177 urls = simplifiedUrlList(urls);
1178 if (different) {
1179 mostLocalUrls = simplifiedUrlList(mostLocalUrls);
1180 KUrlMimeData::setUrls(urls, mostLocalUrls, data);
1181 } else {
1182 data->setUrls(urls);
1183 }
1184
1185 return data;
1186}
1187
1188// Public API; not much point in calling it internally
1190{
1191 if (!index.isValid()) {
1192 if (d->m_showNodeForListedUrl) {
1193 return {};
1194 }
1195 return d->m_dirLister->rootItem();
1196 } else {
1197 return static_cast<KDirModelNode *>(index.internalPointer())->item();
1198 }
1199}
1200
1202{
1203 return indexForUrl(item.url()); // O(n)
1204}
1205
1206// url -> index. O(n)
1208{
1209 KDirModelNode *node = d->nodeForUrl(url); // O(depth)
1210 if (!node) {
1211 // qDebug() << url << "not found";
1212 return QModelIndex();
1213 }
1214 return d->indexForNode(node); // O(n)
1215}
1216
1217QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const
1218{
1219 KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1)
1220 Q_ASSERT(parentNode);
1221 if (d->isDir(parentNode)) {
1222 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
1223 if (childNode) {
1224 return createIndex(row, column, childNode);
1225 }
1226 }
1227 return QModelIndex();
1228}
1229
1230QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const
1231{
1232 if (orientation == Qt::Horizontal) {
1233 switch (role) {
1234 case Qt::DisplayRole:
1235 switch (section) {
1236 case Name:
1237 return i18nc("@title:column", "Name");
1238 case Size:
1239 return i18nc("@title:column", "Size");
1240 case ModifiedTime:
1241 return i18nc("@title:column", "Date");
1242 case Permissions:
1243 return i18nc("@title:column", "Permissions");
1244 case Owner:
1245 return i18nc("@title:column", "Owner");
1246 case Group:
1247 return i18nc("@title:column", "Group");
1248 case Type:
1249 return i18nc("@title:column", "Type");
1250 }
1251 }
1252 }
1253 return QVariant();
1254}
1255
1256bool KDirModel::hasChildren(const QModelIndex &parent) const
1257{
1258 if (!parent.isValid()) {
1259 return true;
1260 }
1261
1262 const KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer());
1263 const KFileItem &parentItem = parentNode->item();
1264 Q_ASSERT(!parentItem.isNull());
1265 if (!parentItem.isDir()) {
1266 return false;
1267 }
1268 if (static_cast<const KDirModelDirNode *>(parentNode)->isPopulated()) {
1269 return !static_cast<const KDirModelDirNode *>(parentNode)->m_childNodes.isEmpty();
1270 }
1271 if (parentItem.isLocalFile() && !static_cast<const KDirModelDirNode *>(parentNode)->isOnNetwork()) {
1273
1274 if (d->m_dirLister->dirOnlyMode()) {
1275 filters |= QDir::NoSymLinks;
1276 } else {
1277 filters |= QDir::Files | QDir::System;
1278 }
1279
1280 if (d->m_dirLister->showHiddenFiles()) {
1281 filters |= QDir::Hidden;
1282 }
1283
1284 QDirIterator it(parentItem.localPath(), filters, QDirIterator::Subdirectories);
1285 return it.hasNext();
1286 }
1287 // Remote and not listed yet, we can't know; let the user click on it so we'll find out
1288 return true;
1289}
1290
1292{
1293 Qt::ItemFlags f;
1294 if (index.isValid()) {
1295 f |= Qt::ItemIsEnabled;
1296 if (index.column() == Name) {
1298 }
1299 }
1300
1301 // Allow dropping onto this item?
1302 if (d->m_dropsAllowed != NoDrops) {
1303 if (!index.isValid()) {
1304 if (d->m_dropsAllowed & DropOnDirectory) {
1306 }
1307 } else {
1309 if (item.isNull()) {
1310 qCWarning(category) << "Invalid item returned for index";
1311 } else if (item.isDir()) {
1312 if (d->m_dropsAllowed & DropOnDirectory) {
1314 }
1315 } else { // regular file item
1316 if (d->m_dropsAllowed & DropOnAnyFile) {
1318 } else if (d->m_dropsAllowed & DropOnLocalExecutable) {
1319 if (!item.localPath().isEmpty()) {
1320 // Desktop file?
1321 if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) {
1323 }
1324 // Executable, shell script ... ?
1325 else if (QFileInfo(item.localPath()).isExecutable()) {
1327 }
1328 }
1329 }
1330 }
1331 }
1332 }
1333
1334 return f;
1335}
1336
1337bool KDirModel::canFetchMore(const QModelIndex &parent) const
1338{
1339 if (!parent.isValid()) {
1340 return false;
1341 }
1342
1343 // We now have a bool KDirModelNode::m_populated,
1344 // to avoid calling fetchMore more than once on empty dirs.
1345 // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
1346 // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
1347
1348 KDirModelNode *node = static_cast<KDirModelNode *>(parent.internalPointer());
1349 const KFileItem &item = node->item();
1350 return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated() && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
1351}
1352
1354{
1355 if (!parent.isValid()) {
1356 return;
1357 }
1358
1359 KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer());
1360
1361 KFileItem parentItem = parentNode->item();
1362 Q_ASSERT(!parentItem.isNull());
1363 if (!parentItem.isDir()) {
1364 return;
1365 }
1366 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(parentNode);
1367 if (dirNode->isPopulated()) {
1368 return;
1369 }
1370 dirNode->setPopulated(true);
1371
1372 const QUrl parentUrl = parentItem.url();
1373 d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
1374}
1375
1376bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
1377{
1378 // Not sure we want to implement any drop handling at this level,
1379 // but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
1380 Q_UNUSED(data);
1381 Q_UNUSED(action);
1382 Q_UNUSED(row);
1383 Q_UNUSED(column);
1384 Q_UNUSED(parent);
1385 return false;
1386}
1387
1389{
1390 d->m_dropsAllowed = dropsAllowed;
1391}
1392
1394{
1395 // emit expand for each parent and return last parent
1396 KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth)
1397
1398 if (!result) { // doesn't seem related to our base url?
1399 qCDebug(category) << url << "does not seem related to our base URL, aborting";
1400 return;
1401 }
1402 if (!result->item().isNull() && result->item().url() == url) {
1403 // We have it already, nothing to do
1404 qCDebug(category) << "we have it already:" << url;
1405 return;
1406 }
1407
1408 d->m_urlsBeingFetched[result].append(url);
1409
1410 if (result == d->m_rootNode) {
1411 qCDebug(category) << "Remembering to emit expand after listing the root url";
1412 // the root is fetched by default, so it must be currently being fetched
1413 return;
1414 }
1415
1416 qCDebug(category) << "Remembering to emit expand after listing" << result->item().url();
1417
1418 // start a new fetch to look for the next level down the URL
1419 const QModelIndex parentIndex = d->indexForNode(result); // O(n)
1420 Q_ASSERT(parentIndex.isValid());
1421 fetchMore(parentIndex);
1422}
1423
1424bool KDirModel::insertRows(int, int, const QModelIndex &)
1425{
1426 return false;
1427}
1428
1429bool KDirModel::insertColumns(int, int, const QModelIndex &)
1430{
1431 return false;
1432}
1433
1434bool KDirModel::removeRows(int, int, const QModelIndex &)
1435{
1436 return false;
1437}
1438
1439bool KDirModel::removeColumns(int, int, const QModelIndex &)
1440{
1441 return false;
1442}
1443
1445{
1446 auto super = QAbstractItemModel::roleNames();
1447
1448 super[AdditionalRoles::FileItemRole] = "fileItem";
1449 super[AdditionalRoles::ChildCountRole] = "childCount";
1450 super[AdditionalRoles::HasJobRole] = "hasJob";
1451
1452 return super;
1453}
1454
1455#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:170
@ DropOnAnyFile
allow drops on any file
Definition kdirmodel.h:171
@ DropOnLocalExecutable
allow drops on local executables, shell scripts and desktop files. Can be used with DropOnDirectory.
Definition kdirmodel.h:172
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:160
@ ChildCountRole
returns the number of items in a directory, or ChildCountUnknown. roleName is "childCount".
Definition kdirmodel.h:161
@ HasJobRole
returns whether or not there is a job on an item (file/directory). roleName is "hasJob".
Definition kdirmodel.h:162
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:632
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 Mon Nov 18 2024 12:16:28 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.