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 const KFileItem item = items.first();
613 Q_ASSERT(!item.isNull());
614 QUrl url = item.url();
615 KDirModelNode *node = nodeForUrl(url); // O(depth)
616 if (!node) {
617 qCWarning(category) << "No node found for item that was just removed:" << url;
618 return;
619 }
620
621 KDirModelDirNode *dirNode = node->parent();
622 if (!dirNode) {
623 return;
624 }
625
626 QModelIndex parentIndex = indexForNode(dirNode); // O(n)
627
628 // Short path for deleting a single item
629 if (items.count() == 1) {
630 const int r = node->rowNumber();
631 q->beginRemoveRows(parentIndex, r, r);
632 removeFromNodeHash(node, url);
633 delete dirNode->m_childNodes.takeAt(r);
634 q->endRemoveRows();
635 return;
636 }
637
638 // We need to make lists of consecutive row numbers, for the beginRemoveRows call.
639 // Let's use a bit array where each bit represents a given child node.
640 const int childCount = dirNode->m_childNodes.count();
641 QBitArray rowNumbers(childCount, false);
642 for (const KFileItem &item : items) {
643 if (!node) { // don't lookup the first item twice
644 url = item.url();
645 node = nodeForUrl(url);
646 if (!node) {
647 qCWarning(category) << "No node found for item that was just removed:" << url;
648 continue;
649 }
650 if (!node->parent()) {
651 // The root node has been deleted, but it was not first in the list 'items'.
652 // see https://bugs.kde.org/show_bug.cgi?id=196695
653 return;
654 }
655 }
656 rowNumbers.setBit(node->rowNumber(), 1); // O(n)
657 removeFromNodeHash(node, url);
658 node = nullptr;
659 }
660
661 int start = -1;
662 int end = -1;
663 bool lastVal = false;
664 // Start from the end, otherwise all the row numbers are offset while we go
665 for (int i = childCount - 1; i >= 0; --i) {
666 const bool val = rowNumbers.testBit(i);
667 if (!lastVal && val) {
668 end = i;
669 // qDebug() << "end=" << end;
670 }
671 if ((lastVal && !val) || (i == 0 && val)) {
672 start = val ? i : i + 1;
673 // qDebug() << "beginRemoveRows" << start << end;
674 q->beginRemoveRows(parentIndex, start, end);
675 for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;)
676 // qDebug() << "Removing from m_childNodes at" << r;
677 delete dirNode->m_childNodes.takeAt(r);
678 }
679 q->endRemoveRows();
680 }
681 lastVal = val;
682 }
683}
684
685void KDirModelPrivate::_k_slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items)
686{
687 QModelIndex topLeft;
688 QModelIndex bottomRight;
689
690 // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows
691 // Solution 2: more fine-grained, actually figure out the beginning and end rows.
692 for (const auto &[oldItem, newItem] : items) {
693 Q_ASSERT(!oldItem.isNull());
694 Q_ASSERT(!newItem.isNull());
695 const QUrl oldUrl = oldItem.url();
696 const QUrl newUrl = newItem.url();
697 KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once
698 // qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node;
699 if (!node) { // not found [can happen when renaming a dir, redirection was emitted already]
700 continue;
701 }
702 if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead.
703 bool hasNewNode = false;
704 // A file became directory (well, it was overwritten)
705 if (oldItem.isDir() != newItem.isDir()) {
706 // qDebug() << "DIR/FILE STATUS CHANGE";
707 const int r = node->rowNumber();
708 removeFromNodeHash(node, oldUrl);
709 KDirModelDirNode *dirNode = node->parent();
710 delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node"
711 node = newItem.isDir() ? new KDirModelDirNode(dirNode, newItem) : new KDirModelNode(dirNode, newItem);
712 dirNode->m_childNodes.insert(r, node); // same position!
713 hasNewNode = true;
714 } else {
715 node->setItem(newItem);
716 }
717
718 if (oldUrl != newUrl || hasNewNode) {
719 // What if a renamed dir had children? -> kdirlister takes care of emitting for each item
720 // qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash";
721 m_nodeHash.remove(cleanupUrl(oldUrl));
722 m_nodeHash.insert(cleanupUrl(newUrl), node);
723 }
724 // MIME type changed -> forget cached icon (e.g. from "cut", #164185 comment #13)
725 if (oldItem.determineMimeType().name() != newItem.determineMimeType().name()) {
726 node->setPreview(QIcon());
727 }
728
729 const QModelIndex index = indexForNode(node);
730 if (!topLeft.isValid() || index.row() < topLeft.row()) {
731 topLeft = index;
732 }
733 if (!bottomRight.isValid() || index.row() > bottomRight.row()) {
734 bottomRight = index;
735 }
736 }
737 }
738 // qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight);
739 bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1);
740 Q_EMIT q->dataChanged(topLeft, bottomRight);
741}
742
743// Called when a KIO worker redirects (e.g. smb:/Workgroup -> smb://workgroup)
744// and when renaming a directory.
745void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl)
746{
747 KDirModelNode *node = nodeForUrl(oldUrl);
748 if (!node) {
749 return;
750 }
751 m_nodeHash.remove(cleanupUrl(oldUrl));
752 m_nodeHash.insert(cleanupUrl(newUrl), node);
753
754 // Ensure the node's URL is updated. In case of a listjob redirection
755 // we won't get a refreshItem, and in case of renaming a directory
756 // we'll get it too late (so the hash won't find the old url anymore).
757 KFileItem item = node->item();
758 if (!item.isNull()) { // null if root item, #180156
759 item.setUrl(newUrl);
760 node->setItem(item);
761 }
762
763 // The items inside the renamed directory have been handled before,
764 // KDirLister took care of emitting refreshItem for each of them.
765}
766
767void KDirModelPrivate::_k_slotClear()
768{
769 const int numRows = m_rootNode->m_childNodes.count();
770 if (numRows > 0) {
771 q->beginRemoveRows(QModelIndex(), 0, numRows - 1);
772 }
773 m_nodeHash.clear();
774 clear();
775 if (numRows > 0) {
776 q->endRemoveRows();
777 }
778}
779
780void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList)
781{
782 QStringList dirtyUrls;
783
784 std::set_symmetric_difference(urlList.begin(),
785 urlList.end(),
786 m_allCurrentDestUrls.constBegin(),
787 m_allCurrentDestUrls.constEnd(),
788 std::back_inserter(dirtyUrls));
789
790 m_allCurrentDestUrls = urlList;
791
792 for (const QString &dirtyUrl : std::as_const(dirtyUrls)) {
793 if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) {
794 const QModelIndex idx = indexForNode(node);
795 Q_EMIT q->dataChanged(idx, idx, {KDirModel::HasJobRole});
796 }
797 }
798}
799
800void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode)
801{
802 const int numRows = dirNode->m_childNodes.count();
803 if (numRows > 0) {
804 KDirModelNode *lastNode = nullptr;
805 for (KDirModelNode *node : std::as_const(dirNode->m_childNodes)) {
806 node->setPreview(QIcon());
807 // node->setPreview(QIcon::fromTheme(node->item().iconName()));
808 if (isDir(node)) {
809 // recurse into child dirs
810 clearAllPreviews(static_cast<KDirModelDirNode *>(node));
811 }
812 lastNode = node;
813 }
814 Q_EMIT q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1)
815 indexForNode(lastNode, numRows - 1)); // O(1)
816 }
817}
818
820{
821 d->clearAllPreviews(d->m_rootNode);
822}
823
825{
826 // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator.
827 // When the MIME type is determined, clear the old "preview" (could be
828 // MIME type dependent like when cutting files, #164185)
829 KDirModelNode *node = d->nodeForIndex(index);
830 if (node) {
831 node->setPreview(QIcon());
832 }
833
834 qCDebug(category) << "dataChanged(" << debugIndex(index) << ")";
836}
837
839{
840 return ColumnCount;
841}
842
843QVariant KDirModel::data(const QModelIndex &index, int role) const
844{
845 if (index.isValid()) {
846 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
847 const KFileItem &item(node->item());
848 switch (role) {
849 case Qt::DisplayRole:
850 switch (index.column()) {
851 case Name:
852 return item.text();
853 case Size:
854 return KIO::convertSize(item.size()); // size formatted as QString
855 case ModifiedTime: {
856 QDateTime dt = item.time(KFileItem::ModificationTime);
858 }
859 case Permissions:
860 return item.permissionsString();
861 case Owner:
862 return item.user();
863 case Group:
864 return item.group();
865 case Type:
866 return item.mimeComment();
867 }
868 break;
869 case Qt::EditRole:
870 switch (index.column()) {
871 case Name:
872 return item.text();
873 }
874 break;
876 if (index.column() == Name) {
877 if (!node->preview().isNull()) {
878 // qDebug() << item->url() << " preview found";
879 return node->preview();
880 }
881 Q_ASSERT(!item.isNull());
882 // qDebug() << item->url() << " overlays=" << item->overlays();
883 static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown"));
884
885 const QString iconName(item.iconName());
886 QIcon icon;
887
888 if (QDir::isAbsolutePath(iconName)) {
889 icon = QIcon(iconName);
890 }
891 if (icon.isNull()
892 || (!(iconName.endsWith(QLatin1String(".svg")) || iconName.endsWith(QLatin1String(".svgz"))) && icon.availableSizes().isEmpty())) {
893 icon = QIcon::fromTheme(iconName, fallbackIcon);
894 }
895
896 const auto parentNode = node->parent();
897 if (parentNode->isOnNetwork()) {
898 return icon;
899 } else {
900 return KIconUtils::addOverlays(icon, item.overlays());
901 }
902 }
903 break;
905 if (index.column() == Size) {
906 // use a right alignment for L2R and R2L languages
908 return int(alignment);
909 }
910 break;
911 case Qt::ToolTipRole:
912 return item.text();
913 case FileItemRole:
914 return QVariant::fromValue(item);
915 case ChildCountRole:
916 if (!item.isDir()) {
917 return ChildCountUnknown;
918 } else {
919 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(node);
920 int count = dirNode->childCount();
921 if (count == ChildCountUnknown && !dirNode->isOnNetwork() && item.isReadable()) {
922 const QString path = item.localPath();
923 if (!path.isEmpty()) {
924// slow
925// QDir dir(path);
926// count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count();
927#ifdef Q_OS_WIN
928 QString s = path + QLatin1String("\\*.*");
929 s.replace(QLatin1Char('/'), QLatin1Char('\\'));
930 count = 0;
931 WIN32_FIND_DATA findData;
932 HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData);
933 if (hFile != INVALID_HANDLE_VALUE) {
934 do {
935 if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0')
936 && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) {
937 ++count;
938 }
939 } while (FindNextFile(hFile, &findData) != 0);
940 FindClose(hFile);
941 }
942#else
943 DIR *dir = QT_OPENDIR(QFile::encodeName(path).constData());
944 if (dir) {
945 count = 0;
946 QT_DIRENT *dirEntry = nullptr;
947 while ((dirEntry = QT_READDIR(dir))) {
948 if (dirEntry->d_name[0] == '.') {
949 if (dirEntry->d_name[1] == '\0') { // skip "."
950 continue;
951 }
952 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".."
953 continue;
954 }
955 }
956 ++count;
957 }
958 QT_CLOSEDIR(dir);
959 }
960#endif
961 // qDebug() << "child count for " << path << ":" << count;
962 dirNode->setChildCount(count);
963 }
964 }
965 return count;
966 }
967 case HasJobRole:
968 if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) {
969 KDirModelNode *node = d->nodeForIndex(index);
970 const QString url = node->item().url().toString();
971 // return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint.
972 return QVariant(d->m_allCurrentDestUrls.contains(url));
973 }
974 }
975 }
976 return QVariant();
977}
978
979void KDirModel::sort(int column, Qt::SortOrder order)
980{
981 // Not implemented - we should probably use QSortFilterProxyModel instead.
982 QAbstractItemModel::sort(column, order);
983}
984
985bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role)
986{
987 switch (role) {
988 case Qt::EditRole:
989 if (index.column() == Name && value.typeId() == QMetaType::QString) {
990 Q_ASSERT(index.isValid());
991 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
992 const KFileItem &item = node->item();
993 const QString newName = value.toString();
994 if (newName.isEmpty() || newName == item.text() || (newName == QLatin1Char('.')) || (newName == QLatin1String(".."))) {
995 return true;
996 }
997 QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename);
998 newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
999 KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags);
1001 // undo handling
1002 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList<QUrl>() << item.url(), newUrl, job);
1003 return true;
1004 }
1005 break;
1006 case Qt::DecorationRole:
1007 if (index.column() == Name) {
1008 Q_ASSERT(index.isValid());
1009 // Set new pixmap - e.g. preview
1010 KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
1011 // qDebug() << "setting icon for " << node->item()->url();
1012 Q_ASSERT(node);
1013 if (value.typeId() == QMetaType::QIcon) {
1014 const QIcon icon(qvariant_cast<QIcon>(value));
1015 node->setPreview(icon);
1016 } else if (value.typeId() == QMetaType::QPixmap) {
1017 node->setPreview(qvariant_cast<QPixmap>(value));
1018 }
1020 return true;
1021 }
1022 break;
1023 default:
1024 break;
1025 }
1026 return false;
1027}
1028
1029int KDirModel::rowCount(const QModelIndex &parent) const
1030{
1031 if (parent.column() > 0) { // for QAbstractItemModelTester
1032 return 0;
1033 }
1034 KDirModelNode *node = d->nodeForIndex(parent);
1035 if (!node || !d->isDir(node)) { // #176555
1036 return 0;
1037 }
1038
1039 KDirModelDirNode *parentNode = static_cast<KDirModelDirNode *>(node);
1040 Q_ASSERT(parentNode);
1041 const int count = parentNode->m_childNodes.count();
1042#if 0
1043 QStringList filenames;
1044 for (int i = 0; i < count; ++i) {
1045 filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName();
1046 }
1047 qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames;
1048#endif
1049 return count;
1050}
1051
1053{
1054 if (!index.isValid()) {
1055 return QModelIndex();
1056 }
1057 KDirModelNode *childNode = static_cast<KDirModelNode *>(index.internalPointer());
1058 Q_ASSERT(childNode);
1059 KDirModelNode *parentNode = childNode->parent();
1060 Q_ASSERT(parentNode);
1061 return d->indexForNode(parentNode); // O(n)
1062}
1063
1064// Reimplemented to avoid the default implementation which calls parent
1065// (O(n) for finding the parent's row number for nothing). This implementation is O(1).
1066QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const
1067{
1068 if (!index.isValid()) {
1069 return QModelIndex();
1070 }
1071 KDirModelNode *oldChildNode = static_cast<KDirModelNode *>(index.internalPointer());
1072 Q_ASSERT(oldChildNode);
1073 KDirModelNode *parentNode = oldChildNode->parent();
1074 Q_ASSERT(parentNode);
1075 Q_ASSERT(d->isDir(parentNode));
1076 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
1077 if (childNode) {
1078 return createIndex(row, column, childNode);
1079 }
1080 return QModelIndex();
1081}
1082
1083void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex)
1084{
1085 Q_EMIT needSequenceIcon(index, sequenceIndex);
1086}
1087
1089{
1090 if (d->m_jobTransfersVisible == show) {
1091 return;
1092 }
1093
1094 d->m_jobTransfersVisible = show;
1095 if (show) {
1096 connect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, [this](const QStringList &urlList) {
1097 d->_k_slotJobUrlsChanged(urlList);
1098 });
1099
1100 JobUrlCache::instance().requestJobUrlsChanged();
1101 } else {
1102 disconnect(&JobUrlCache::instance(), &JobUrlCache::jobUrlsChanged, this, nullptr);
1103 }
1104}
1105
1107{
1108 return d->m_jobTransfersVisible;
1109}
1110
1112{
1113 if (urls.isEmpty()) {
1114 return urls;
1115 }
1116
1117 QList<QUrl> ret(urls);
1118 std::sort(ret.begin(), ret.end());
1119
1120 QUrl url;
1121
1122 auto filterFunc = [&url](const QUrl &u) {
1123 if (url == u || url.isParentOf(u)) {
1124 return true;
1125 } else {
1126 url = u;
1127 return false;
1128 }
1129 };
1130
1131 auto beginIt = ret.begin();
1132 url = *beginIt;
1133 ++beginIt;
1134 auto it = std::remove_if(beginIt, ret.end(), filterFunc);
1135 ret.erase(it, ret.end());
1136
1137 return ret;
1138}
1139
1144
1145QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const
1146{
1147 QList<QUrl> urls;
1148 QList<QUrl> mostLocalUrls;
1149 urls.reserve(indexes.size());
1150 mostLocalUrls.reserve(indexes.size());
1151 bool canUseMostLocalUrls = true;
1152 for (const QModelIndex &index : indexes) {
1153 const KFileItem &item = d->nodeForIndex(index)->item();
1154 urls.append(item.url());
1155 const auto [url, isLocal] = item.isMostLocalUrl();
1156 mostLocalUrls.append(url);
1157 if (!isLocal) {
1158 canUseMostLocalUrls = false;
1159 }
1160 }
1161 QMimeData *data = new QMimeData();
1162 const bool different = canUseMostLocalUrls && (mostLocalUrls != urls);
1163 urls = simplifiedUrlList(urls);
1164 if (different) {
1165 mostLocalUrls = simplifiedUrlList(mostLocalUrls);
1166 KUrlMimeData::setUrls(urls, mostLocalUrls, data);
1167 } else {
1168 data->setUrls(urls);
1169 }
1170
1171 return data;
1172}
1173
1174// Public API; not much point in calling it internally
1176{
1177 if (!index.isValid()) {
1178 if (d->m_showNodeForListedUrl) {
1179 return {};
1180 }
1181 return d->m_dirLister->rootItem();
1182 } else {
1183 return static_cast<KDirModelNode *>(index.internalPointer())->item();
1184 }
1185}
1186
1188{
1189 return indexForUrl(item.url()); // O(n)
1190}
1191
1192// url -> index. O(n)
1194{
1195 KDirModelNode *node = d->nodeForUrl(url); // O(depth)
1196 if (!node) {
1197 // qDebug() << url << "not found";
1198 return QModelIndex();
1199 }
1200 return d->indexForNode(node); // O(n)
1201}
1202
1203QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const
1204{
1205 KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1)
1206 Q_ASSERT(parentNode);
1207 if (d->isDir(parentNode)) {
1208 KDirModelNode *childNode = static_cast<KDirModelDirNode *>(parentNode)->m_childNodes.value(row); // O(1)
1209 if (childNode) {
1210 return createIndex(row, column, childNode);
1211 }
1212 }
1213 return QModelIndex();
1214}
1215
1216QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const
1217{
1218 if (orientation == Qt::Horizontal) {
1219 switch (role) {
1220 case Qt::DisplayRole:
1221 switch (section) {
1222 case Name:
1223 return i18nc("@title:column", "Name");
1224 case Size:
1225 return i18nc("@title:column", "Size");
1226 case ModifiedTime:
1227 return i18nc("@title:column", "Date");
1228 case Permissions:
1229 return i18nc("@title:column", "Permissions");
1230 case Owner:
1231 return i18nc("@title:column", "Owner");
1232 case Group:
1233 return i18nc("@title:column", "Group");
1234 case Type:
1235 return i18nc("@title:column", "Type");
1236 }
1237 }
1238 }
1239 return QVariant();
1240}
1241
1242bool KDirModel::hasChildren(const QModelIndex &parent) const
1243{
1244 if (!parent.isValid()) {
1245 return true;
1246 }
1247
1248 const KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer());
1249 const KFileItem &parentItem = parentNode->item();
1250 Q_ASSERT(!parentItem.isNull());
1251 if (!parentItem.isDir()) {
1252 return false;
1253 }
1254 if (static_cast<const KDirModelDirNode *>(parentNode)->isPopulated()) {
1255 return !static_cast<const KDirModelDirNode *>(parentNode)->m_childNodes.isEmpty();
1256 }
1257 if (parentItem.isLocalFile() && !static_cast<const KDirModelDirNode *>(parentNode)->isOnNetwork()) {
1259
1260 if (d->m_dirLister->dirOnlyMode()) {
1261 filters |= QDir::NoSymLinks;
1262 } else {
1263 filters |= QDir::Files | QDir::System;
1264 }
1265
1266 if (d->m_dirLister->showHiddenFiles()) {
1267 filters |= QDir::Hidden;
1268 }
1269
1270 QDirIterator it(parentItem.localPath(), filters, QDirIterator::Subdirectories);
1271 return it.hasNext();
1272 }
1273 // Remote and not listed yet, we can't know; let the user click on it so we'll find out
1274 return true;
1275}
1276
1278{
1279 Qt::ItemFlags f;
1280 if (index.isValid()) {
1281 f |= Qt::ItemIsEnabled;
1282 if (index.column() == Name) {
1284 }
1285 }
1286
1287 // Allow dropping onto this item?
1288 if (d->m_dropsAllowed != NoDrops) {
1289 if (!index.isValid()) {
1290 if (d->m_dropsAllowed & DropOnDirectory) {
1292 }
1293 } else {
1295 if (item.isNull()) {
1296 qCWarning(category) << "Invalid item returned for index";
1297 } else if (item.isDir()) {
1298 if (d->m_dropsAllowed & DropOnDirectory) {
1300 }
1301 } else { // regular file item
1302 if (d->m_dropsAllowed & DropOnAnyFile) {
1304 } else if (d->m_dropsAllowed & DropOnLocalExecutable) {
1305 if (!item.localPath().isEmpty()) {
1306 // Desktop file?
1307 if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) {
1309 }
1310 // Executable, shell script ... ?
1311 else if (QFileInfo(item.localPath()).isExecutable()) {
1313 }
1314 }
1315 }
1316 }
1317 }
1318 }
1319
1320 return f;
1321}
1322
1323bool KDirModel::canFetchMore(const QModelIndex &parent) const
1324{
1325 if (!parent.isValid()) {
1326 return false;
1327 }
1328
1329 // We now have a bool KDirModelNode::m_populated,
1330 // to avoid calling fetchMore more than once on empty dirs.
1331 // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview?
1332 // Maybe we can ask KDirLister "have you listed <url> already"? (to discuss with M. Brade)
1333
1334 KDirModelNode *node = static_cast<KDirModelNode *>(parent.internalPointer());
1335 const KFileItem &item = node->item();
1336 return item.isDir() && !static_cast<KDirModelDirNode *>(node)->isPopulated() && static_cast<KDirModelDirNode *>(node)->m_childNodes.isEmpty();
1337}
1338
1340{
1341 if (!parent.isValid()) {
1342 return;
1343 }
1344
1345 KDirModelNode *parentNode = static_cast<KDirModelNode *>(parent.internalPointer());
1346
1347 KFileItem parentItem = parentNode->item();
1348 Q_ASSERT(!parentItem.isNull());
1349 if (!parentItem.isDir()) {
1350 return;
1351 }
1352 KDirModelDirNode *dirNode = static_cast<KDirModelDirNode *>(parentNode);
1353 if (dirNode->isPopulated()) {
1354 return;
1355 }
1356 dirNode->setPopulated(true);
1357
1358 const QUrl parentUrl = parentItem.url();
1359 d->m_dirLister->openUrl(parentUrl, KDirLister::Keep);
1360}
1361
1362bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
1363{
1364 // Not sure we want to implement any drop handling at this level,
1365 // but for sure the default QAbstractItemModel implementation makes no sense for a dir model.
1366 Q_UNUSED(data);
1367 Q_UNUSED(action);
1368 Q_UNUSED(row);
1369 Q_UNUSED(column);
1370 Q_UNUSED(parent);
1371 return false;
1372}
1373
1375{
1376 d->m_dropsAllowed = dropsAllowed;
1377}
1378
1380{
1381 // emit expand for each parent and return last parent
1382 KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth)
1383
1384 if (!result) { // doesn't seem related to our base url?
1385 qCDebug(category) << url << "does not seem related to our base URL, aborting";
1386 return;
1387 }
1388 if (!result->item().isNull() && result->item().url() == url) {
1389 // We have it already, nothing to do
1390 qCDebug(category) << "we have it already:" << url;
1391 return;
1392 }
1393
1394 d->m_urlsBeingFetched[result].append(url);
1395
1396 if (result == d->m_rootNode) {
1397 qCDebug(category) << "Remembering to emit expand after listing the root url";
1398 // the root is fetched by default, so it must be currently being fetched
1399 return;
1400 }
1401
1402 qCDebug(category) << "Remembering to emit expand after listing" << result->item().url();
1403
1404 // start a new fetch to look for the next level down the URL
1405 const QModelIndex parentIndex = d->indexForNode(result); // O(n)
1406 Q_ASSERT(parentIndex.isValid());
1407 fetchMore(parentIndex);
1408}
1409
1410bool KDirModel::insertRows(int, int, const QModelIndex &)
1411{
1412 return false;
1413}
1414
1415bool KDirModel::insertColumns(int, int, const QModelIndex &)
1416{
1417 return false;
1418}
1419
1420bool KDirModel::removeRows(int, int, const QModelIndex &)
1421{
1422 return false;
1423}
1424
1425bool KDirModel::removeColumns(int, int, const QModelIndex &)
1426{
1427 return false;
1428}
1429
1431{
1432 auto super = QAbstractItemModel::roleNames();
1433
1434 super[AdditionalRoles::FileItemRole] = "fileItem";
1435 super[AdditionalRoles::ChildCountRole] = "childCount";
1436 super[AdditionalRoles::HasJobRole] = "hasJob";
1437
1438 return super;
1439}
1440
1441#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
void expand(const QModelIndex &index)
Emitted for each subdirectory that is a parent of a url passed to expandToUrl This allows to asynchro...
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
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.
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.
Definition job_base.h:45
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.
Definition statjob.h:26
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:113
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)
typedef Filters
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)
T & first()
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)
T value(qsizetype i) const const
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
bool hasFragment() const const
bool hasQuery() const const
bool isLocalFile() const const
bool isParentOf(const QUrl &childUrl) const const
bool isValid() const const
bool matches(const QUrl &url, FormattingOptions options) 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 Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.