1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qfilesystemmodel_p.h"
5#include "qfilesystemmodel.h"
6#include <qabstractfileiconprovider.h>
7#include <qlocale.h>
8#include <qmimedata.h>
9#include <qurl.h>
10#include <qdebug.h>
11#include <QtCore/qcollator.h>
12#if QT_CONFIG(regularexpression)
13# include <QtCore/qregularexpression.h>
14#endif
15
16#include <algorithm>
17
18#ifdef Q_OS_WIN
19# include <QtCore/QVarLengthArray>
20# include <qt_windows.h>
21# include <shlobj.h>
22#endif
23
24QT_BEGIN_NAMESPACE
25
26using namespace Qt::StringLiterals;
27
28/*!
29 \enum QFileSystemModel::Roles
30 \value FileIconRole
31 \value FilePathRole
32 \value FileNameRole
33 \value FilePermissions
34 \value FileInfoRole The QFileInfo object for the index
35*/
36
37/*!
38 \class QFileSystemModel
39 \since 4.4
40
41 \brief The QFileSystemModel class provides a data model for the local filesystem.
42
43 \ingroup model-view
44 \inmodule QtGui
45
46 This class provides access to the local filesystem, providing functions
47 for renaming and removing files and directories, and for creating new
48 directories. In the simplest case, it can be used with a suitable display
49 widget as part of a browser or filter.
50
51 QFileSystemModel can be accessed using the standard interface provided by
52 QAbstractItemModel, but it also provides some convenience functions that are
53 specific to a directory model.
54 The fileInfo(), isDir(), fileName() and filePath() functions provide information
55 about the underlying files and directories related to items in the model.
56 Directories can be created and removed using mkdir(), rmdir().
57
58 \note QFileSystemModel requires an instance of \l QApplication.
59
60 \section1 Example Usage
61
62 A directory model that displays the contents of a default directory
63 is usually constructed with a parent object:
64
65 \snippet shareddirmodel/main.cpp 2
66
67 A tree view can be used to display the contents of the model
68
69 \snippet shareddirmodel/main.cpp 4
70
71 and the contents of a particular directory can be displayed by
72 setting the tree view's root index:
73
74 \snippet shareddirmodel/main.cpp 7
75
76 The view's root index can be used to control how much of a
77 hierarchical model is displayed. QFileSystemModel provides a convenience
78 function that returns a suitable model index for a path to a
79 directory within the model.
80
81 \section1 Caching and Performance
82
83 QFileSystemModel will not fetch any files or directories until setRootPath()
84 is called. This will prevent any unnecessary querying on the file system
85 until that point such as listing the drives on Windows.
86
87 QFileSystemModel uses a separate thread to populate itself so it will not
88 cause the main thread to hang as the file system is being queried.
89 Calls to rowCount() will return 0 until the model populates a directory.
90
91 QFileSystemModel keeps a cache with file information. The cache is
92 automatically kept up to date using the QFileSystemWatcher.
93
94 \sa {Model Classes}
95*/
96
97/*!
98 \fn bool QFileSystemModel::rmdir(const QModelIndex &index)
99
100 Removes the directory corresponding to the model item \a index in the
101 file system model and \b{deletes the corresponding directory from the
102 file system}, returning true if successful. If the directory cannot be
103 removed, false is returned.
104
105 \warning This function deletes directories from the file system; it does
106 \b{not} move them to a location where they can be recovered.
107
108 \sa remove()
109*/
110
111/*!
112 \fn QString QFileSystemModel::fileName(const QModelIndex &index) const
113
114 Returns the file name for the item stored in the model under the given
115 \a index.
116*/
117
118/*!
119 \fn QIcon QFileSystemModel::fileIcon(const QModelIndex &index) const
120
121 Returns the icon for the item stored in the model under the given
122 \a index.
123*/
124
125/*!
126 \fn QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const
127
128 Returns the QFileInfo for the item stored in the model under the given
129 \a index.
130*/
131QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const
132{
133 Q_D(const QFileSystemModel);
134 return d->node(index)->fileInfo();
135}
136
137/*!
138 \fn void QFileSystemModel::rootPathChanged(const QString &newPath);
139
140 This signal is emitted whenever the root path has been changed to a \a newPath.
141*/
142
143/*!
144 \fn void QFileSystemModel::fileRenamed(const QString &path, const QString &oldName, const QString &newName)
145
146 This signal is emitted whenever a file with the \a oldName is successfully
147 renamed to \a newName. The file is located in the directory \a path.
148*/
149
150/*!
151 \since 4.7
152 \fn void QFileSystemModel::directoryLoaded(const QString &path)
153
154 This signal is emitted when the gatherer thread has finished to load the \a path.
155
156*/
157
158/*!
159 \fn bool QFileSystemModel::remove(const QModelIndex &index)
160
161 Removes the model item \a index from the file system model and \b{deletes the
162 corresponding file from the file system}, returning true if successful. If the
163 item cannot be removed, false is returned.
164
165 \warning This function deletes files from the file system; it does \b{not}
166 move them to a location where they can be recovered.
167
168 \sa rmdir()
169*/
170
171bool QFileSystemModel::remove(const QModelIndex &aindex)
172{
173 Q_D(QFileSystemModel);
174
175 const QString path = d->filePath(index: aindex);
176 const QFileInfo fileInfo(path);
177#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
178 // QTBUG-65683: Remove file system watchers prior to deletion to prevent
179 // failure due to locked files on Windows.
180 const QStringList watchedPaths = d->unwatchPathsAt(aindex);
181#endif // filesystemwatcher && Q_OS_WIN
182 const bool success = (fileInfo.isFile() || fileInfo.isSymLink())
183 ? QFile::remove(fileName: path) : QDir(path).removeRecursively();
184#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
185 if (!success)
186 d->watchPaths(watchedPaths);
187#endif // filesystemwatcher && Q_OS_WIN
188 return success;
189}
190
191/*!
192 Constructs a file system model with the given \a parent.
193*/
194QFileSystemModel::QFileSystemModel(QObject *parent) :
195 QFileSystemModel(*new QFileSystemModelPrivate, parent)
196{
197}
198
199/*!
200 \internal
201*/
202QFileSystemModel::QFileSystemModel(QFileSystemModelPrivate &dd, QObject *parent)
203 : QAbstractItemModel(dd, parent)
204{
205 Q_D(QFileSystemModel);
206 d->init();
207}
208
209/*!
210 Destroys this file system model.
211*/
212QFileSystemModel::~QFileSystemModel() = default;
213
214/*!
215 \reimp
216*/
217QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent) const
218{
219 Q_D(const QFileSystemModel);
220 if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
221 return QModelIndex();
222
223 // get the parent node
224 QFileSystemModelPrivate::QFileSystemNode *parentNode = (d->indexValid(index: parent) ? d->node(index: parent) :
225 const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&d->root));
226 Q_ASSERT(parentNode);
227
228 // now get the internal pointer for the index
229 const int i = d->translateVisibleLocation(parent: parentNode, row);
230 if (i >= parentNode->visibleChildren.size())
231 return QModelIndex();
232 const QString &childName = parentNode->visibleChildren.at(i);
233 const QFileSystemModelPrivate::QFileSystemNode *indexNode = parentNode->children.value(key: childName);
234 Q_ASSERT(indexNode);
235
236 return createIndex(arow: row, acolumn: column, adata: const_cast<QFileSystemModelPrivate::QFileSystemNode*>(indexNode));
237}
238
239/*!
240 \reimp
241*/
242QModelIndex QFileSystemModel::sibling(int row, int column, const QModelIndex &idx) const
243{
244 if (row == idx.row() && column < columnCount(parent: idx.parent())) {
245 // cheap sibling operation: just adjust the column:
246 return createIndex(arow: row, acolumn: column, adata: idx.internalPointer());
247 } else {
248 // for anything else: call the default implementation
249 // (this could probably be optimized, too):
250 return QAbstractItemModel::sibling(row, column, idx);
251 }
252}
253
254/*!
255 \overload
256
257 Returns the model item index for the given \a path and \a column.
258*/
259QModelIndex QFileSystemModel::index(const QString &path, int column) const
260{
261 Q_D(const QFileSystemModel);
262 QFileSystemModelPrivate::QFileSystemNode *node = d->node(path, fetch: false);
263 return d->index(node, column);
264}
265
266/*!
267 \internal
268
269 Return the QFileSystemNode that goes to index.
270 */
271QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QModelIndex &index) const
272{
273 if (!index.isValid())
274 return const_cast<QFileSystemNode*>(&root);
275 QFileSystemModelPrivate::QFileSystemNode *indexNode = static_cast<QFileSystemModelPrivate::QFileSystemNode*>(index.internalPointer());
276 Q_ASSERT(indexNode);
277 return indexNode;
278}
279
280#ifdef Q_OS_WIN32
281static QString qt_GetLongPathName(const QString &strShortPath)
282{
283 if (strShortPath.isEmpty()
284 || strShortPath == "."_L1 || strShortPath == ".."_L1)
285 return strShortPath;
286 if (strShortPath.length() == 2 && strShortPath.endsWith(u':'))
287 return strShortPath.toUpper();
288 const QString absPath = QDir(strShortPath).absolutePath();
289 if (absPath.startsWith("//"_L1)
290 || absPath.startsWith("\\\\"_L1)) // unc
291 return QDir::fromNativeSeparators(absPath);
292 if (absPath.startsWith(u'/'))
293 return QString();
294 const QString inputString = "\\\\?\\"_L1 + QDir::toNativeSeparators(absPath);
295 QVarLengthArray<TCHAR, MAX_PATH> buffer(MAX_PATH);
296 DWORD result = ::GetLongPathName((wchar_t*)inputString.utf16(),
297 buffer.data(),
298 buffer.size());
299 if (result > DWORD(buffer.size())) {
300 buffer.resize(result);
301 result = ::GetLongPathName((wchar_t*)inputString.utf16(),
302 buffer.data(),
303 buffer.size());
304 }
305 if (result > 4) {
306 QString longPath = QString::fromWCharArray(buffer.data() + 4); // ignoring prefix
307 longPath[0] = longPath.at(0).toUpper(); // capital drive letters
308 return QDir::fromNativeSeparators(longPath);
309 } else {
310 return QDir::fromNativeSeparators(strShortPath);
311 }
312}
313
314static inline void chopSpaceAndDot(QString &element)
315{
316 if (element == "."_L1 || element == ".."_L1)
317 return;
318 // On Windows, "filename " and "filename" are equivalent and
319 // "filename . " and "filename" are equivalent
320 // "filename......." and "filename" are equivalent Task #133928
321 // whereas "filename .txt" is still "filename .txt"
322 while (element.endsWith(u'.') || element.endsWith(u' '))
323 element.chop(1);
324
325 // If a file is saved as ' Foo.txt', where the leading character(s)
326 // is an ASCII Space (0x20), it will be saved to the file system as 'Foo.txt'.
327 while (element.startsWith(u' '))
328 element.remove(0, 1);
329}
330
331#endif
332
333/*!
334 \internal
335
336 Given a path return the matching QFileSystemNode or &root if invalid
337*/
338QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const
339{
340 Q_Q(const QFileSystemModel);
341 Q_UNUSED(q);
342 if (path.isEmpty() || path == myComputer() || path.startsWith(c: u':'))
343 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
344
345 // Construct the nodes up to the new root path if they need to be built
346 QString absolutePath;
347#ifdef Q_OS_WIN32
348 QString longPath = qt_GetLongPathName(path);
349#else
350 QString longPath = path;
351#endif
352 if (longPath == rootDir.path())
353 absolutePath = rootDir.absolutePath();
354 else
355 absolutePath = QDir(longPath).absolutePath();
356
357 // ### TODO can we use bool QAbstractFileEngine::caseSensitive() const?
358 QStringList pathElements = absolutePath.split(sep: u'/', behavior: Qt::SkipEmptyParts);
359 if ((pathElements.isEmpty())
360#if !defined(Q_OS_WIN)
361 && QDir::fromNativeSeparators(pathName: longPath) != "/"_L1
362#endif
363 )
364 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
365 QModelIndex index = QModelIndex(); // start with "My Computer"
366 QString elementPath;
367 QChar separator = u'/';
368 QString trailingSeparator;
369#if defined(Q_OS_WIN)
370 if (absolutePath.startsWith("//"_L1)) { // UNC path
371 QString host = "\\\\"_L1 + pathElements.constFirst();
372 if (absolutePath == QDir::fromNativeSeparators(host))
373 absolutePath.append(u'/');
374 if (longPath.endsWith(u'/') && !absolutePath.endsWith(u'/'))
375 absolutePath.append(u'/');
376 if (absolutePath.endsWith(u'/'))
377 trailingSeparator = "\\"_L1;
378 int r = 0;
379 auto rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
380 auto it = root.children.constFind(host);
381 if (it != root.children.cend()) {
382 host = it.key(); // Normalize case for lookup in visibleLocation()
383 } else {
384 if (pathElements.count() == 1 && !absolutePath.endsWith(u'/'))
385 return rootNode;
386 QFileInfo info(host);
387 if (!info.exists())
388 return rootNode;
389 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
390 p->addNode(rootNode, host,info);
391 p->addVisibleFiles(rootNode, QStringList(host));
392 }
393 r = rootNode->visibleLocation(host);
394 r = translateVisibleLocation(rootNode, r);
395 index = q->index(r, 0, QModelIndex());
396 pathElements.pop_front();
397 separator = u'\\';
398 elementPath = host;
399 elementPath.append(separator);
400 } else {
401 if (!pathElements.at(0).contains(u':')) {
402 QString rootPath = QDir(longPath).rootPath();
403 pathElements.prepend(rootPath);
404 }
405 if (pathElements.at(0).endsWith(u'/'))
406 pathElements[0].chop(1);
407 }
408#else
409 // add the "/" item, since it is a valid path element on Unix
410 if (absolutePath[0] == u'/')
411 pathElements.prepend(t: "/"_L1);
412#endif
413
414 QFileSystemModelPrivate::QFileSystemNode *parent = node(index);
415
416 for (int i = 0; i < pathElements.size(); ++i) {
417 QString element = pathElements.at(i);
418 if (i != 0)
419 elementPath.append(c: separator);
420 elementPath.append(s: element);
421 if (i == pathElements.size() - 1)
422 elementPath.append(s: trailingSeparator);
423#ifdef Q_OS_WIN
424 // If after stripping the characters there is nothing left then we
425 // just return the parent directory as it is assumed that the path
426 // is referring to the parent.
427 chopSpaceAndDot(element);
428 // Only filenames that can't possibly exist will be end up being empty
429 if (element.isEmpty())
430 return parent;
431#endif
432 bool alreadyExisted = parent->children.contains(key: element);
433
434 // we couldn't find the path element, we create a new node since we
435 // _know_ that the path is valid
436 if (alreadyExisted) {
437 if ((parent->children.size() == 0)
438 || (parent->caseSensitive()
439 && parent->children.value(key: element)->fileName != element)
440 || (!parent->caseSensitive()
441 && parent->children.value(key: element)->fileName.toLower() != element.toLower()))
442 alreadyExisted = false;
443 }
444
445 QFileSystemModelPrivate::QFileSystemNode *node;
446 if (!alreadyExisted) {
447 // Someone might call ::index("file://cookie/monster/doesn't/like/veggies"),
448 // a path that doesn't exists, I.E. don't blindly create directories.
449 QFileInfo info(elementPath);
450 if (!info.exists())
451 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
452 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
453 node = p->addNode(parentNode: parent, fileName: element,info);
454#if QT_CONFIG(filesystemwatcher)
455 node->populate(fileInfo: fileInfoGatherer->getInfo(info));
456#endif
457 } else {
458 node = parent->children.value(key: element);
459 }
460
461 Q_ASSERT(node);
462 if (!node->isVisible) {
463 // It has been filtered out
464 if (alreadyExisted && node->hasInformation() && !fetch)
465 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
466
467 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
468 p->addVisibleFiles(parentNode: parent, newFiles: QStringList(element));
469 if (!p->bypassFilters.contains(key: node))
470 p->bypassFilters[node] = 1;
471 QString dir = q->filePath(index: this->index(node: parent));
472 if (!node->hasInformation() && fetch) {
473 Fetching f = { .dir: std::move(dir), .file: std::move(element), .node: node };
474 p->toFetch.append(t: std::move(f));
475 p->fetchingTimer.start(msec: 0, obj: const_cast<QFileSystemModel*>(q));
476 }
477 }
478 parent = node;
479 }
480
481 return parent;
482}
483
484/*!
485 \reimp
486*/
487void QFileSystemModel::timerEvent(QTimerEvent *event)
488{
489 Q_D(QFileSystemModel);
490 if (event->timerId() == d->fetchingTimer.timerId()) {
491 d->fetchingTimer.stop();
492#if QT_CONFIG(filesystemwatcher)
493 for (int i = 0; i < d->toFetch.size(); ++i) {
494 const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node;
495 if (!node->hasInformation()) {
496 d->fileInfoGatherer->fetchExtendedInformation(path: d->toFetch.at(i).dir,
497 files: QStringList(d->toFetch.at(i).file));
498 } else {
499 // qDebug("yah!, you saved a little gerbil soul");
500 }
501 }
502#endif
503 d->toFetch.clear();
504 }
505}
506
507/*!
508 Returns \c true if the model item \a index represents a directory;
509 otherwise returns \c false.
510*/
511bool QFileSystemModel::isDir(const QModelIndex &index) const
512{
513 // This function is for public usage only because it could create a file info
514 Q_D(const QFileSystemModel);
515 if (!index.isValid())
516 return true;
517 QFileSystemModelPrivate::QFileSystemNode *n = d->node(index);
518 if (n->hasInformation())
519 return n->isDir();
520 return fileInfo(index).isDir();
521}
522
523/*!
524 Returns the size in bytes of \a index. If the file does not exist, 0 is returned.
525 */
526qint64 QFileSystemModel::size(const QModelIndex &index) const
527{
528 Q_D(const QFileSystemModel);
529 if (!index.isValid())
530 return 0;
531 return d->node(index)->size();
532}
533
534/*!
535 Returns the type of file \a index such as "Directory" or "JPEG file".
536 */
537QString QFileSystemModel::type(const QModelIndex &index) const
538{
539 Q_D(const QFileSystemModel);
540 if (!index.isValid())
541 return QString();
542 return d->node(index)->type();
543}
544
545/*!
546 Returns the date and time (in local time) when \a index was last modified.
547
548 This is an overloaded function, equivalent to calling:
549 \code
550 lastModified(index, QTimeZone::LocalTime);
551 \endcode
552
553 If \a index is invalid, a default constructed QDateTime is returned.
554 */
555QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const
556{
557 return lastModified(index, tz: QTimeZone::LocalTime);
558}
559
560/*!
561 \since 6.6
562 Returns the date and time, in the time zone \a tz, when
563 \a index was last modified.
564
565 Typical arguments for \a tz are \c QTimeZone::UTC or \c QTimeZone::LocalTime.
566 UTC does not require any conversion from the time returned by the native file
567 system API, therefore getting the time in UTC is potentially faster. LocalTime
568 is typically chosen if the time is shown to the user.
569
570 If \a index is invalid, a default constructed QDateTime is returned.
571 */
572QDateTime QFileSystemModel::lastModified(const QModelIndex &index, const QTimeZone &tz) const
573{
574 Q_D(const QFileSystemModel);
575 if (!index.isValid())
576 return QDateTime();
577 return d->node(index)->lastModified(tz);
578}
579
580/*!
581 \reimp
582*/
583QModelIndex QFileSystemModel::parent(const QModelIndex &index) const
584{
585 Q_D(const QFileSystemModel);
586 if (!d->indexValid(index))
587 return QModelIndex();
588
589 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
590 Q_ASSERT(indexNode != nullptr);
591 QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent;
592 if (parentNode == nullptr || parentNode == &d->root)
593 return QModelIndex();
594
595 // get the parent's row
596 QFileSystemModelPrivate::QFileSystemNode *grandParentNode = parentNode->parent;
597 Q_ASSERT(grandParentNode->children.contains(parentNode->fileName));
598 int visualRow = d->translateVisibleLocation(parent: grandParentNode, row: grandParentNode->visibleLocation(childName: grandParentNode->children.value(key: parentNode->fileName)->fileName));
599 if (visualRow == -1)
600 return QModelIndex();
601 return createIndex(arow: visualRow, acolumn: 0, adata: parentNode);
602}
603
604/*
605 \internal
606
607 return the index for node
608*/
609QModelIndex QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode *node, int column) const
610{
611 Q_Q(const QFileSystemModel);
612 QFileSystemModelPrivate::QFileSystemNode *parentNode = (node ? node->parent : nullptr);
613 if (node == &root || !parentNode)
614 return QModelIndex();
615
616 // get the parent's row
617 Q_ASSERT(node);
618 if (!node->isVisible)
619 return QModelIndex();
620
621 int visualRow = translateVisibleLocation(parent: parentNode, row: parentNode->visibleLocation(childName: node->fileName));
622 return q->createIndex(arow: visualRow, acolumn: column, adata: const_cast<QFileSystemNode*>(node));
623}
624
625/*!
626 \reimp
627*/
628bool QFileSystemModel::hasChildren(const QModelIndex &parent) const
629{
630 Q_D(const QFileSystemModel);
631 if (parent.column() > 0)
632 return false;
633
634 if (!parent.isValid()) // drives
635 return true;
636
637 const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index: parent);
638 Q_ASSERT(indexNode);
639 return (indexNode->isDir());
640}
641
642/*!
643 \reimp
644 */
645bool QFileSystemModel::canFetchMore(const QModelIndex &parent) const
646{
647 Q_D(const QFileSystemModel);
648 if (!d->setRootPath)
649 return false;
650 const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index: parent);
651 return (!indexNode->populatedChildren);
652}
653
654/*!
655 \reimp
656 */
657void QFileSystemModel::fetchMore(const QModelIndex &parent)
658{
659 Q_D(QFileSystemModel);
660 if (!d->setRootPath)
661 return;
662 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index: parent);
663 if (indexNode->populatedChildren)
664 return;
665 indexNode->populatedChildren = true;
666#if QT_CONFIG(filesystemwatcher)
667 d->fileInfoGatherer->list(directoryPath: filePath(index: parent));
668#endif
669}
670
671/*!
672 \reimp
673*/
674int QFileSystemModel::rowCount(const QModelIndex &parent) const
675{
676 Q_D(const QFileSystemModel);
677 if (parent.column() > 0)
678 return 0;
679
680 if (!parent.isValid())
681 return d->root.visibleChildren.size();
682
683 const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(index: parent);
684 return parentNode->visibleChildren.size();
685}
686
687/*!
688 \reimp
689*/
690int QFileSystemModel::columnCount(const QModelIndex &parent) const
691{
692 return (parent.column() > 0) ? 0 : QFileSystemModelPrivate::NumColumns;
693}
694
695/*!
696 Returns the data stored under the given \a role for the item "My Computer".
697
698 \sa Qt::ItemDataRole
699 */
700QVariant QFileSystemModel::myComputer(int role) const
701{
702#if QT_CONFIG(filesystemwatcher)
703 Q_D(const QFileSystemModel);
704#endif
705 switch (role) {
706 case Qt::DisplayRole:
707 return QFileSystemModelPrivate::myComputer();
708#if QT_CONFIG(filesystemwatcher)
709 case Qt::DecorationRole:
710 if (auto *provider = d->fileInfoGatherer->iconProvider())
711 return provider->icon(QAbstractFileIconProvider::Computer);
712 break;
713#endif
714 }
715 return QVariant();
716}
717
718/*!
719 \reimp
720*/
721QVariant QFileSystemModel::data(const QModelIndex &index, int role) const
722{
723 Q_D(const QFileSystemModel);
724 if (!index.isValid() || index.model() != this)
725 return QVariant();
726
727 switch (role) {
728 case Qt::EditRole:
729 if (index.column() == QFileSystemModelPrivate::NameColumn)
730 return d->name(index);
731 Q_FALLTHROUGH();
732 case Qt::DisplayRole:
733 switch (index.column()) {
734 case QFileSystemModelPrivate::NameColumn: return d->displayName(index);
735 case QFileSystemModelPrivate::SizeColumn: return d->size(index);
736 case QFileSystemModelPrivate::TypeColumn: return d->type(index);
737 case QFileSystemModelPrivate::TimeColumn: return d->time(index);
738 default:
739 qWarning(msg: "data: invalid display value column %d", index.column());
740 break;
741 }
742 break;
743 case FilePathRole:
744 return filePath(index);
745 case FileNameRole:
746 return d->name(index);
747 case FileInfoRole:
748 return QVariant::fromValue(value: fileInfo(index));
749 case Qt::DecorationRole:
750 if (index.column() == QFileSystemModelPrivate::NameColumn) {
751 QIcon icon = d->icon(index);
752#if QT_CONFIG(filesystemwatcher)
753 if (icon.isNull()) {
754 using P = QAbstractFileIconProvider;
755 if (auto *provider = d->fileInfoGatherer->iconProvider())
756 icon = provider->icon(d->node(index)->isDir() ? P::Folder: P::File);
757 }
758#endif // filesystemwatcher
759 return icon;
760 }
761 break;
762 case Qt::TextAlignmentRole:
763 if (index.column() == QFileSystemModelPrivate::SizeColumn)
764 return QVariant(Qt::AlignTrailing | Qt::AlignVCenter);
765 break;
766 case FilePermissions:
767 int p = permissions(index);
768 return p;
769 }
770
771 return QVariant();
772}
773
774/*!
775 \internal
776*/
777QString QFileSystemModelPrivate::size(const QModelIndex &index) const
778{
779 if (!index.isValid())
780 return QString();
781 const QFileSystemNode *n = node(index);
782 if (n->isDir()) {
783#ifdef Q_OS_MAC
784 return "--"_L1;
785#else
786 return ""_L1;
787#endif
788 // Windows - ""
789 // OS X - "--"
790 // Konqueror - "4 KB"
791 // Nautilus - "9 items" (the number of children)
792 }
793 return size(bytes: n->size());
794}
795
796QString QFileSystemModelPrivate::size(qint64 bytes)
797{
798 return QLocale::system().formattedDataSize(bytes);
799}
800
801/*!
802 \internal
803*/
804QString QFileSystemModelPrivate::time(const QModelIndex &index) const
805{
806 if (!index.isValid())
807 return QString();
808#if QT_CONFIG(datestring)
809 return QLocale::system().toString(dateTime: node(index)->lastModified(tz: QTimeZone::LocalTime), format: QLocale::ShortFormat);
810#else
811 Q_UNUSED(index);
812 return QString();
813#endif
814}
815
816/*
817 \internal
818*/
819QString QFileSystemModelPrivate::type(const QModelIndex &index) const
820{
821 if (!index.isValid())
822 return QString();
823 return node(index)->type();
824}
825
826/*!
827 \internal
828*/
829QString QFileSystemModelPrivate::name(const QModelIndex &index) const
830{
831 if (!index.isValid())
832 return QString();
833 QFileSystemNode *dirNode = node(index);
834 if (
835#if QT_CONFIG(filesystemwatcher)
836 fileInfoGatherer->resolveSymlinks() &&
837#endif
838 !resolvedSymLinks.isEmpty() && dirNode->isSymLink(/* ignoreNtfsSymLinks = */ true)) {
839 QString fullPath = QDir::fromNativeSeparators(pathName: filePath(index));
840 return resolvedSymLinks.value(key: fullPath, defaultValue: dirNode->fileName);
841 }
842 return dirNode->fileName;
843}
844
845/*!
846 \internal
847*/
848QString QFileSystemModelPrivate::displayName(const QModelIndex &index) const
849{
850#if defined(Q_OS_WIN)
851 QFileSystemNode *dirNode = node(index);
852 if (!dirNode->volumeName.isEmpty())
853 return dirNode->volumeName;
854#endif
855 return name(index);
856}
857
858/*!
859 \internal
860*/
861QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const
862{
863 if (!index.isValid())
864 return QIcon();
865 return node(index)->icon();
866}
867
868/*!
869 \reimp
870*/
871bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, int role)
872{
873 Q_D(QFileSystemModel);
874 if (!idx.isValid()
875 || idx.column() != 0
876 || role != Qt::EditRole
877 || (flags(index: idx) & Qt::ItemIsEditable) == 0) {
878 return false;
879 }
880
881 QString newName = value.toString();
882#ifdef Q_OS_WIN
883 chopSpaceAndDot(newName);
884 if (newName.isEmpty())
885 return false;
886#endif
887
888 QString oldName = idx.data().toString();
889 if (newName == oldName)
890 return true;
891
892 const QString parentPath = filePath(index: parent(index: idx));
893
894 if (newName.isEmpty() || QDir::toNativeSeparators(pathName: newName).contains(c: QDir::separator()))
895 return false;
896
897#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
898 // QTBUG-65683: Remove file system watchers prior to renaming to prevent
899 // failure due to locked files on Windows.
900 const QStringList watchedPaths = d->unwatchPathsAt(idx);
901#endif // filesystemwatcher && Q_OS_WIN
902 if (!QDir(parentPath).rename(oldName, newName)) {
903#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
904 d->watchPaths(watchedPaths);
905#endif
906 return false;
907 } else {
908 /*
909 *After re-naming something we don't want the selection to change*
910 - can't remove rows and later insert
911 - can't quickly remove and insert
912 - index pointer can't change because treeview doesn't use persistent index's
913
914 - if this get any more complicated think of changing it to just
915 use layoutChanged
916 */
917
918 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index: idx);
919 QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent;
920 int visibleLocation = parentNode->visibleLocation(childName: parentNode->children.value(key: indexNode->fileName)->fileName);
921
922 parentNode->visibleChildren.removeAt(i: visibleLocation);
923 std::unique_ptr<QFileSystemModelPrivate::QFileSystemNode> nodeToRename(parentNode->children.take(key: oldName));
924 nodeToRename->fileName = newName;
925 nodeToRename->parent = parentNode;
926#if QT_CONFIG(filesystemwatcher)
927 nodeToRename->populate(fileInfo: d->fileInfoGatherer->getInfo(info: QFileInfo(parentPath, newName)));
928#endif
929 nodeToRename->isVisible = true;
930 parentNode->children[newName] = nodeToRename.release();
931 parentNode->visibleChildren.insert(i: visibleLocation, t: newName);
932
933 d->delayedSort();
934 emit fileRenamed(path: parentPath, oldName, newName);
935 }
936 return true;
937}
938
939/*!
940 \reimp
941*/
942QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
943{
944 switch (role) {
945 case Qt::DecorationRole:
946 if (section == 0) {
947 // ### TODO oh man this is ugly and doesn't even work all the way!
948 // it is still 2 pixels off
949 QImage pixmap(16, 1, QImage::Format_ARGB32_Premultiplied);
950 pixmap.fill(color: Qt::transparent);
951 return pixmap;
952 }
953 break;
954 case Qt::TextAlignmentRole:
955 return Qt::AlignLeft;
956 }
957
958 if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
959 return QAbstractItemModel::headerData(section, orientation, role);
960
961 QString returnValue;
962 switch (section) {
963 case QFileSystemModelPrivate::NameColumn:
964 returnValue = tr(s: "Name");
965 break;
966 case QFileSystemModelPrivate::SizeColumn:
967 returnValue = tr(s: "Size");
968 break;
969 case QFileSystemModelPrivate::TypeColumn:
970 returnValue =
971#ifdef Q_OS_MAC
972 tr("Kind", "Match OS X Finder");
973#else
974 tr(s: "Type", c: "All other platforms");
975#endif
976 break;
977 // Windows - Type
978 // OS X - Kind
979 // Konqueror - File Type
980 // Nautilus - Type
981 case QFileSystemModelPrivate::TimeColumn:
982 returnValue = tr(s: "Date Modified");
983 break;
984 default: return QVariant();
985 }
986 return returnValue;
987}
988
989/*!
990 \reimp
991*/
992Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const
993{
994 Q_D(const QFileSystemModel);
995 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
996 if (!index.isValid())
997 return flags;
998
999 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
1000 if (d->nameFilterDisables && !d->passNameFilters(node: indexNode)) {
1001 flags &= ~Qt::ItemIsEnabled;
1002 // ### TODO you shouldn't be able to set this as the current item, task 119433
1003 return flags;
1004 }
1005
1006 flags |= Qt::ItemIsDragEnabled;
1007
1008 if (!indexNode->isDir())
1009 flags |= Qt::ItemNeverHasChildren;
1010 if (d->readOnly)
1011 return flags;
1012 if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) {
1013 flags |= Qt::ItemIsEditable;
1014 if (indexNode->isDir())
1015 flags |= Qt::ItemIsDropEnabled;
1016 }
1017 return flags;
1018}
1019
1020/*!
1021 \internal
1022*/
1023void QFileSystemModelPrivate::performDelayedSort()
1024{
1025 Q_Q(QFileSystemModel);
1026 q->sort(column: sortColumn, order: sortOrder);
1027}
1028
1029
1030/*
1031 \internal
1032 Helper functor used by sort()
1033*/
1034class QFileSystemModelSorter
1035{
1036public:
1037 inline QFileSystemModelSorter(int column) : sortColumn(column)
1038 {
1039 naturalCompare.setNumericMode(true);
1040 naturalCompare.setCaseSensitivity(Qt::CaseInsensitive);
1041 }
1042
1043 bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l,
1044 const QFileSystemModelPrivate::QFileSystemNode *r) const
1045 {
1046 switch (sortColumn) {
1047 case QFileSystemModelPrivate::NameColumn: {
1048#ifndef Q_OS_MAC
1049 // place directories before files
1050 bool left = l->isDir();
1051 bool right = r->isDir();
1052 if (left ^ right)
1053 return left;
1054#endif
1055 return naturalCompare.compare(s1: l->fileName, s2: r->fileName) < 0;
1056 }
1057 case QFileSystemModelPrivate::SizeColumn:
1058 {
1059 // Directories go first
1060 bool left = l->isDir();
1061 bool right = r->isDir();
1062 if (left ^ right)
1063 return left;
1064
1065 qint64 sizeDifference = l->size() - r->size();
1066 if (sizeDifference == 0)
1067 return naturalCompare.compare(s1: l->fileName, s2: r->fileName) < 0;
1068
1069 return sizeDifference < 0;
1070 }
1071 case QFileSystemModelPrivate::TypeColumn:
1072 {
1073 int compare = naturalCompare.compare(s1: l->type(), s2: r->type());
1074 if (compare == 0)
1075 return naturalCompare.compare(s1: l->fileName, s2: r->fileName) < 0;
1076
1077 return compare < 0;
1078 }
1079 case QFileSystemModelPrivate::TimeColumn:
1080 {
1081 const QDateTime left = l->lastModified(tz: QTimeZone::UTC);
1082 const QDateTime right = r->lastModified(tz: QTimeZone::UTC);
1083 if (left == right)
1084 return naturalCompare.compare(s1: l->fileName, s2: r->fileName) < 0;
1085
1086 return left < right;
1087 }
1088 }
1089 Q_ASSERT(false);
1090 return false;
1091 }
1092
1093 bool operator()(const QFileSystemModelPrivate::QFileSystemNode *l,
1094 const QFileSystemModelPrivate::QFileSystemNode *r) const
1095 {
1096 return compareNodes(l, r);
1097 }
1098
1099
1100private:
1101 QCollator naturalCompare;
1102 int sortColumn;
1103};
1104
1105/*
1106 \internal
1107
1108 Sort all of the children of parent
1109*/
1110void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent)
1111{
1112 Q_Q(QFileSystemModel);
1113 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(index: parent);
1114 if (indexNode->children.size() == 0)
1115 return;
1116
1117 QList<QFileSystemModelPrivate::QFileSystemNode *> values;
1118
1119 for (auto iterator = indexNode->children.constBegin(), cend = indexNode->children.constEnd(); iterator != cend; ++iterator) {
1120 if (filtersAcceptsNode(node: iterator.value())) {
1121 values.append(t: iterator.value());
1122 } else {
1123 iterator.value()->isVisible = false;
1124 }
1125 }
1126 QFileSystemModelSorter ms(column);
1127 std::sort(first: values.begin(), last: values.end(), comp: ms);
1128 // First update the new visible list
1129 indexNode->visibleChildren.clear();
1130 //No more dirty item we reset our internal dirty index
1131 indexNode->dirtyChildrenIndex = -1;
1132 indexNode->visibleChildren.reserve(asize: values.size());
1133 for (QFileSystemNode *node : std::as_const(t&: values)) {
1134 indexNode->visibleChildren.append(t: node->fileName);
1135 node->isVisible = true;
1136 }
1137
1138 if (!disableRecursiveSort) {
1139 for (int i = 0; i < q->rowCount(parent); ++i) {
1140 const QModelIndex childIndex = q->index(row: i, column: 0, parent);
1141 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(index: childIndex);
1142 //Only do a recursive sort on visible nodes
1143 if (indexNode->isVisible)
1144 sortChildren(column, parent: childIndex);
1145 }
1146 }
1147}
1148
1149/*!
1150 \reimp
1151*/
1152void QFileSystemModel::sort(int column, Qt::SortOrder order)
1153{
1154 Q_D(QFileSystemModel);
1155 if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
1156 return;
1157
1158 emit layoutAboutToBeChanged();
1159 QModelIndexList oldList = persistentIndexList();
1160 QList<QPair<QFileSystemModelPrivate::QFileSystemNode *, int>> oldNodes;
1161 oldNodes.reserve(asize: oldList.size());
1162 for (const QModelIndex &oldNode : oldList) {
1163 QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(index: oldNode), oldNode.column());
1164 oldNodes.append(t: pair);
1165 }
1166
1167 if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) {
1168 //we sort only from where we are, don't need to sort all the model
1169 d->sortChildren(column, parent: index(path: rootPath()));
1170 d->sortColumn = column;
1171 d->forceSort = false;
1172 }
1173 d->sortOrder = order;
1174
1175 QModelIndexList newList;
1176 newList.reserve(asize: oldNodes.size());
1177 for (const auto &[node, col]: std::as_const(t&: oldNodes))
1178 newList.append(t: d->index(node, column: col));
1179
1180 changePersistentIndexList(from: oldList, to: newList);
1181 emit layoutChanged();
1182}
1183
1184/*!
1185 Returns a list of MIME types that can be used to describe a list of items
1186 in the model.
1187*/
1188QStringList QFileSystemModel::mimeTypes() const
1189{
1190 return QStringList("text/uri-list"_L1);
1191}
1192
1193/*!
1194 Returns an object that contains a serialized description of the specified
1195 \a indexes. The format used to describe the items corresponding to the
1196 indexes is obtained from the mimeTypes() function.
1197
1198 If the list of indexes is empty, \nullptr is returned rather than a
1199 serialized empty list.
1200*/
1201QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const
1202{
1203 QList<QUrl> urls;
1204 QList<QModelIndex>::const_iterator it = indexes.begin();
1205 for (; it != indexes.end(); ++it)
1206 if ((*it).column() == QFileSystemModelPrivate::NameColumn)
1207 urls << QUrl::fromLocalFile(localfile: filePath(index: *it));
1208 QMimeData *data = new QMimeData();
1209 data->setUrls(urls);
1210 return data;
1211}
1212
1213/*!
1214 Handles the \a data supplied by a drag and drop operation that ended with
1215 the given \a action over the row in the model specified by the \a row and
1216 \a column and by the \a parent index. Returns true if the operation was
1217 successful.
1218
1219 \sa supportedDropActions()
1220*/
1221bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
1222 int row, int column, const QModelIndex &parent)
1223{
1224 Q_UNUSED(row);
1225 Q_UNUSED(column);
1226 if (!parent.isValid() || isReadOnly())
1227 return false;
1228
1229 bool success = true;
1230 QString to = filePath(index: parent) + QDir::separator();
1231
1232 QList<QUrl> urls = data->urls();
1233 QList<QUrl>::const_iterator it = urls.constBegin();
1234
1235 switch (action) {
1236 case Qt::CopyAction:
1237 for (; it != urls.constEnd(); ++it) {
1238 QString path = (*it).toLocalFile();
1239 success = QFile::copy(fileName: path, newName: to + QFileInfo(path).fileName()) && success;
1240 }
1241 break;
1242 case Qt::LinkAction:
1243 for (; it != urls.constEnd(); ++it) {
1244 QString path = (*it).toLocalFile();
1245 success = QFile::link(fileName: path, newName: to + QFileInfo(path).fileName()) && success;
1246 }
1247 break;
1248 case Qt::MoveAction:
1249 for (; it != urls.constEnd(); ++it) {
1250 QString path = (*it).toLocalFile();
1251 success = QFile::rename(oldName: path, newName: to + QFileInfo(path).fileName()) && success;
1252 }
1253 break;
1254 default:
1255 return false;
1256 }
1257
1258 return success;
1259}
1260
1261/*!
1262 \reimp
1263*/
1264Qt::DropActions QFileSystemModel::supportedDropActions() const
1265{
1266 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
1267}
1268
1269/*!
1270 \reimp
1271*/
1272QHash<int, QByteArray> QFileSystemModel::roleNames() const
1273{
1274 auto ret = QAbstractItemModel::roleNames();
1275 ret.insert(key: QFileSystemModel::FileIconRole,
1276 QByteArrayLiteral("fileIcon")); // == Qt::decoration
1277 ret.insert(key: QFileSystemModel::FilePathRole, QByteArrayLiteral("filePath"));
1278 ret.insert(key: QFileSystemModel::FileNameRole, QByteArrayLiteral("fileName"));
1279 ret.insert(key: QFileSystemModel::FilePermissions, QByteArrayLiteral("filePermissions"));
1280 ret.insert(key: QFileSystemModel::FileInfoRole, QByteArrayLiteral("fileInfo"));
1281 return ret;
1282}
1283
1284/*!
1285 \enum QFileSystemModel::Option
1286 \since 5.14
1287
1288 \value DontWatchForChanges Do not add file watchers to the paths.
1289 This reduces overhead when using the model for simple tasks
1290 like line edit completion.
1291
1292 \value DontResolveSymlinks Don't resolve symlinks in the file
1293 system model. By default, symlinks are resolved.
1294
1295 \value DontUseCustomDirectoryIcons Always use the default directory icon.
1296 Some platforms allow the user to set a different icon. Custom icon lookup
1297 causes a big performance impact over network or removable drives.
1298 This sets the QFileIconProvider::DontUseCustomDirectoryIcons
1299 option in the icon provider accordingly.
1300
1301 \sa resolveSymlinks
1302*/
1303
1304/*!
1305 \since 5.14
1306 Sets the given \a option to be enabled if \a on is true; otherwise,
1307 clears the given \a option.
1308
1309 Options should be set before changing properties.
1310
1311 \sa options, testOption()
1312*/
1313void QFileSystemModel::setOption(Option option, bool on)
1314{
1315 QFileSystemModel::Options previousOptions = options();
1316 setOptions(previousOptions.setFlag(flag: option, on));
1317}
1318
1319/*!
1320 \since 5.14
1321
1322 Returns \c true if the given \a option is enabled; otherwise, returns
1323 false.
1324
1325 \sa options, setOption()
1326*/
1327bool QFileSystemModel::testOption(Option option) const
1328{
1329 return options().testFlag(flag: option);
1330}
1331
1332/*!
1333 \property QFileSystemModel::options
1334 \brief the various options that affect the model
1335 \since 5.14
1336
1337 By default, all options are disabled.
1338
1339 Options should be set before changing properties.
1340
1341 \sa setOption(), testOption()
1342*/
1343void QFileSystemModel::setOptions(Options options)
1344{
1345 const Options changed = (options ^ QFileSystemModel::options());
1346
1347 if (changed.testFlag(flag: DontResolveSymlinks))
1348 setResolveSymlinks(!options.testFlag(flag: DontResolveSymlinks));
1349
1350#if QT_CONFIG(filesystemwatcher)
1351 Q_D(QFileSystemModel);
1352 if (changed.testFlag(flag: DontWatchForChanges))
1353 d->fileInfoGatherer->setWatching(!options.testFlag(flag: DontWatchForChanges));
1354#endif
1355
1356 if (changed.testFlag(flag: DontUseCustomDirectoryIcons)) {
1357 if (auto provider = iconProvider()) {
1358 QAbstractFileIconProvider::Options providerOptions = provider->options();
1359 providerOptions.setFlag(flag: QAbstractFileIconProvider::DontUseCustomDirectoryIcons,
1360 on: options.testFlag(flag: QFileSystemModel::DontUseCustomDirectoryIcons));
1361 provider->setOptions(providerOptions);
1362 } else {
1363 qWarning(msg: "Setting QFileSystemModel::DontUseCustomDirectoryIcons has no effect when no provider is used");
1364 }
1365 }
1366}
1367
1368QFileSystemModel::Options QFileSystemModel::options() const
1369{
1370 QFileSystemModel::Options result;
1371 result.setFlag(flag: DontResolveSymlinks, on: !resolveSymlinks());
1372#if QT_CONFIG(filesystemwatcher)
1373 Q_D(const QFileSystemModel);
1374 result.setFlag(flag: DontWatchForChanges, on: !d->fileInfoGatherer->isWatching());
1375#else
1376 result.setFlag(DontWatchForChanges);
1377#endif
1378 if (auto provider = iconProvider()) {
1379 result.setFlag(flag: DontUseCustomDirectoryIcons,
1380 on: provider->options().testFlag(flag: QAbstractFileIconProvider::DontUseCustomDirectoryIcons));
1381 }
1382 return result;
1383}
1384
1385/*!
1386 Returns the path of the item stored in the model under the
1387 \a index given.
1388*/
1389QString QFileSystemModel::filePath(const QModelIndex &index) const
1390{
1391 Q_D(const QFileSystemModel);
1392 QString fullPath = d->filePath(index);
1393 QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
1394 if (dirNode->isSymLink()
1395#if QT_CONFIG(filesystemwatcher)
1396 && d->fileInfoGatherer->resolveSymlinks()
1397#endif
1398 && d->resolvedSymLinks.contains(key: fullPath)
1399 && dirNode->isDir()) {
1400 QFileInfo fullPathInfo(dirNode->fileInfo());
1401 if (!dirNode->hasInformation())
1402 fullPathInfo = QFileInfo(fullPath);
1403 QString canonicalPath = fullPathInfo.canonicalFilePath();
1404 auto *canonicalNode = d->node(path: fullPathInfo.canonicalFilePath(), fetch: false);
1405 QFileInfo resolvedInfo = canonicalNode->fileInfo();
1406 if (!canonicalNode->hasInformation())
1407 resolvedInfo = QFileInfo(canonicalPath);
1408 if (resolvedInfo.exists())
1409 return resolvedInfo.filePath();
1410 }
1411 return fullPath;
1412}
1413
1414QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const
1415{
1416 Q_Q(const QFileSystemModel);
1417 Q_UNUSED(q);
1418 if (!index.isValid())
1419 return QString();
1420 Q_ASSERT(index.model() == q);
1421
1422 QStringList path;
1423 QModelIndex idx = index;
1424 while (idx.isValid()) {
1425 QFileSystemModelPrivate::QFileSystemNode *dirNode = node(index: idx);
1426 if (dirNode)
1427 path.prepend(t: dirNode->fileName);
1428 idx = idx.parent();
1429 }
1430 QString fullPath = QDir::fromNativeSeparators(pathName: path.join(sep: QDir::separator()));
1431#if !defined(Q_OS_WIN)
1432 if ((fullPath.size() > 2) && fullPath[0] == u'/' && fullPath[1] == u'/')
1433 fullPath = fullPath.mid(position: 1);
1434#else
1435 if (fullPath.length() == 2 && fullPath.endsWith(u':'))
1436 fullPath.append(u'/');
1437#endif
1438 return fullPath;
1439}
1440
1441/*!
1442 Create a directory with the \a name in the \a parent model index.
1443*/
1444QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name)
1445{
1446 Q_D(QFileSystemModel);
1447 if (!parent.isValid())
1448 return parent;
1449
1450 QString fileName = name;
1451#ifdef Q_OS_WIN
1452 chopSpaceAndDot(fileName);
1453 if (fileName.isEmpty())
1454 return QModelIndex();
1455#endif
1456
1457 QDir dir(filePath(index: parent));
1458 if (!dir.mkdir(dirName: fileName))
1459 return QModelIndex();
1460 QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(index: parent);
1461 d->addNode(parentNode, fileName, info: QFileInfo());
1462 Q_ASSERT(parentNode->children.contains(fileName));
1463 QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[fileName];
1464#if QT_CONFIG(filesystemwatcher)
1465 node->populate(fileInfo: d->fileInfoGatherer->getInfo(info: QFileInfo(dir.absolutePath() + QDir::separator() + fileName)));
1466#endif
1467 d->addVisibleFiles(parentNode, newFiles: QStringList(fileName));
1468 return d->index(node);
1469}
1470
1471/*!
1472 Returns the complete OR-ed together combination of QFile::Permission for the \a index.
1473 */
1474QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const
1475{
1476 Q_D(const QFileSystemModel);
1477 return d->node(index)->permissions();
1478}
1479
1480/*!
1481 Sets the directory that is being watched by the model to \a newPath by
1482 installing a \l{QFileSystemWatcher}{file system watcher} on it. Any
1483 changes to files and directories within this directory will be
1484 reflected in the model.
1485
1486 If the path is changed, the rootPathChanged() signal will be emitted.
1487
1488 \note This function does not change the structure of the model or
1489 modify the data available to views. In other words, the "root" of
1490 the model is \e not changed to include only files and directories
1491 within the directory specified by \a newPath in the file system.
1492 */
1493QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
1494{
1495 Q_D(QFileSystemModel);
1496#ifdef Q_OS_WIN
1497#ifdef Q_OS_WIN32
1498 QString longNewPath = qt_GetLongPathName(newPath);
1499#else
1500 QString longNewPath = QDir::fromNativeSeparators(newPath);
1501#endif
1502#else
1503 QString longNewPath = newPath;
1504#endif
1505 //we remove .. and . from the given path if exist
1506 if (!newPath.isEmpty())
1507 longNewPath = QDir::cleanPath(path: longNewPath);
1508
1509 d->setRootPath = true;
1510
1511 //user don't ask for the root path ("") but the conversion failed
1512 if (!newPath.isEmpty() && longNewPath.isEmpty())
1513 return d->index(path: rootPath());
1514
1515 if (d->rootDir.path() == longNewPath)
1516 return d->index(path: rootPath());
1517
1518 auto node = d->node(path: longNewPath);
1519 QFileInfo newPathInfo;
1520 if (node && node->hasInformation())
1521 newPathInfo = node->fileInfo();
1522 else
1523 newPathInfo = QFileInfo(longNewPath);
1524
1525 bool showDrives = (longNewPath.isEmpty() || longNewPath == QFileSystemModelPrivate::myComputer());
1526 if (!showDrives && !newPathInfo.exists())
1527 return d->index(path: rootPath());
1528
1529 //We remove the watcher on the previous path
1530 if (!rootPath().isEmpty() && rootPath() != "."_L1) {
1531 //This remove the watcher for the old rootPath
1532#if QT_CONFIG(filesystemwatcher)
1533 d->fileInfoGatherer->removePath(path: rootPath());
1534#endif
1535 //This line "marks" the node as dirty, so the next fetchMore
1536 //call on the path will ask the gatherer to install a watcher again
1537 //But it doesn't re-fetch everything
1538 d->node(path: rootPath())->populatedChildren = false;
1539 }
1540
1541 // We have a new valid root path
1542 d->rootDir = QDir(longNewPath);
1543 QModelIndex newRootIndex;
1544 if (showDrives) {
1545 // otherwise dir will become '.'
1546 d->rootDir.setPath(""_L1);
1547 } else {
1548 newRootIndex = d->index(path: d->rootDir.path());
1549 }
1550 fetchMore(parent: newRootIndex);
1551 emit rootPathChanged(newPath: longNewPath);
1552 d->forceSort = true;
1553 d->delayedSort();
1554 return newRootIndex;
1555}
1556
1557/*!
1558 The currently set root path
1559
1560 \sa rootDirectory()
1561*/
1562QString QFileSystemModel::rootPath() const
1563{
1564 Q_D(const QFileSystemModel);
1565 return d->rootDir.path();
1566}
1567
1568/*!
1569 The currently set directory
1570
1571 \sa rootPath()
1572*/
1573QDir QFileSystemModel::rootDirectory() const
1574{
1575 Q_D(const QFileSystemModel);
1576 QDir dir(d->rootDir);
1577 dir.setNameFilters(nameFilters());
1578 dir.setFilter(filter());
1579 return dir;
1580}
1581
1582/*!
1583 Sets the \a provider of file icons for the directory model.
1584*/
1585void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider)
1586{
1587 Q_D(QFileSystemModel);
1588#if QT_CONFIG(filesystemwatcher)
1589 d->fileInfoGatherer->setIconProvider(provider);
1590#endif
1591 d->root.updateIcon(iconProvider: provider, path: QString());
1592}
1593
1594/*!
1595 Returns the file icon provider for this directory model.
1596*/
1597QAbstractFileIconProvider *QFileSystemModel::iconProvider() const
1598{
1599#if QT_CONFIG(filesystemwatcher)
1600 Q_D(const QFileSystemModel);
1601 return d->fileInfoGatherer->iconProvider();
1602#else
1603 return nullptr;
1604#endif
1605}
1606
1607/*!
1608 Sets the directory model's filter to that specified by \a filters.
1609
1610 Note that the filter you set should always include the QDir::AllDirs enum value,
1611 otherwise QFileSystemModel won't be able to read the directory structure.
1612
1613 \sa QDir::Filters
1614*/
1615void QFileSystemModel::setFilter(QDir::Filters filters)
1616{
1617 Q_D(QFileSystemModel);
1618 if (d->filters == filters)
1619 return;
1620 const bool changingCaseSensitivity =
1621 filters.testFlag(flag: QDir::CaseSensitive) != d->filters.testFlag(flag: QDir::CaseSensitive);
1622 d->filters = filters;
1623 if (changingCaseSensitivity)
1624 d->rebuildNameFilterRegexps();
1625 d->forceSort = true;
1626 d->delayedSort();
1627}
1628
1629/*!
1630 Returns the filter specified for the directory model.
1631
1632 If a filter has not been set, the default filter is QDir::AllEntries |
1633 QDir::NoDotAndDotDot | QDir::AllDirs.
1634
1635 \sa QDir::Filters
1636*/
1637QDir::Filters QFileSystemModel::filter() const
1638{
1639 Q_D(const QFileSystemModel);
1640 return d->filters;
1641}
1642
1643/*!
1644 \property QFileSystemModel::resolveSymlinks
1645 \brief Whether the directory model should resolve symbolic links
1646
1647 This is only relevant on Windows.
1648
1649 By default, this property is \c true.
1650
1651 \sa QFileSystemModel::Options
1652*/
1653void QFileSystemModel::setResolveSymlinks(bool enable)
1654{
1655#if QT_CONFIG(filesystemwatcher)
1656 Q_D(QFileSystemModel);
1657 d->fileInfoGatherer->setResolveSymlinks(enable);
1658#else
1659 Q_UNUSED(enable);
1660#endif
1661}
1662
1663bool QFileSystemModel::resolveSymlinks() const
1664{
1665#if QT_CONFIG(filesystemwatcher)
1666 Q_D(const QFileSystemModel);
1667 return d->fileInfoGatherer->resolveSymlinks();
1668#else
1669 return false;
1670#endif
1671}
1672
1673/*!
1674 \property QFileSystemModel::readOnly
1675 \brief Whether the directory model allows writing to the file system
1676
1677 If this property is set to false, the directory model will allow renaming, copying
1678 and deleting of files and directories.
1679
1680 This property is \c true by default
1681*/
1682void QFileSystemModel::setReadOnly(bool enable)
1683{
1684 Q_D(QFileSystemModel);
1685 d->readOnly = enable;
1686}
1687
1688bool QFileSystemModel::isReadOnly() const
1689{
1690 Q_D(const QFileSystemModel);
1691 return d->readOnly;
1692}
1693
1694/*!
1695 \property QFileSystemModel::nameFilterDisables
1696 \brief Whether files that don't pass the name filter are hidden or disabled
1697
1698 This property is \c true by default
1699*/
1700void QFileSystemModel::setNameFilterDisables(bool enable)
1701{
1702 Q_D(QFileSystemModel);
1703 if (d->nameFilterDisables == enable)
1704 return;
1705 d->nameFilterDisables = enable;
1706 d->forceSort = true;
1707 d->delayedSort();
1708}
1709
1710bool QFileSystemModel::nameFilterDisables() const
1711{
1712 Q_D(const QFileSystemModel);
1713 return d->nameFilterDisables;
1714}
1715
1716/*!
1717 Sets the name \a filters to apply against the existing files.
1718*/
1719void QFileSystemModel::setNameFilters(const QStringList &filters)
1720{
1721#if QT_CONFIG(regularexpression)
1722 Q_D(QFileSystemModel);
1723
1724 if (!d->bypassFilters.isEmpty()) {
1725 // update the bypass filter to only bypass the stuff that must be kept around
1726 d->bypassFilters.clear();
1727 // We guarantee that rootPath will stick around
1728 QPersistentModelIndex root(index(path: rootPath()));
1729 const QModelIndexList persistentList = persistentIndexList();
1730 for (const auto &persistentIndex : persistentList) {
1731 QFileSystemModelPrivate::QFileSystemNode *node = d->node(index: persistentIndex);
1732 while (node) {
1733 if (d->bypassFilters.contains(key: node))
1734 break;
1735 if (node->isDir())
1736 d->bypassFilters[node] = true;
1737 node = node->parent;
1738 }
1739 }
1740 }
1741
1742 d->nameFilters = filters;
1743 d->rebuildNameFilterRegexps();
1744 d->forceSort = true;
1745 d->delayedSort();
1746#else
1747 Q_UNUSED(filters);
1748#endif
1749}
1750
1751/*!
1752 Returns a list of filters applied to the names in the model.
1753*/
1754QStringList QFileSystemModel::nameFilters() const
1755{
1756#if QT_CONFIG(regularexpression)
1757 Q_D(const QFileSystemModel);
1758 return d->nameFilters;
1759#else
1760 return QStringList();
1761#endif
1762}
1763
1764/*!
1765 \reimp
1766*/
1767bool QFileSystemModel::event(QEvent *event)
1768{
1769#if QT_CONFIG(filesystemwatcher)
1770 Q_D(QFileSystemModel);
1771 if (event->type() == QEvent::LanguageChange) {
1772 d->root.retranslateStrings(iconProvider: d->fileInfoGatherer->iconProvider(), path: QString());
1773 return true;
1774 }
1775#endif
1776 return QAbstractItemModel::event(event);
1777}
1778
1779bool QFileSystemModel::rmdir(const QModelIndex &aindex)
1780{
1781 QString path = filePath(index: aindex);
1782 const bool success = QDir().rmdir(dirName: path);
1783#if QT_CONFIG(filesystemwatcher)
1784 if (success) {
1785 QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func());
1786 d->fileInfoGatherer->removePath(path);
1787 }
1788#endif
1789 return success;
1790}
1791
1792/*!
1793 \internal
1794
1795 Performed quick listing and see if any files have been added or removed,
1796 then fetch more information on visible files.
1797 */
1798void QFileSystemModelPrivate::directoryChanged(const QString &directory, const QStringList &files)
1799{
1800 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path: directory, fetch: false);
1801 if (parentNode->children.size() == 0)
1802 return;
1803 QStringList toRemove;
1804 QStringList newFiles = files;
1805 std::sort(first: newFiles.begin(), last: newFiles.end());
1806 for (auto i = parentNode->children.constBegin(), cend = parentNode->children.constEnd(); i != cend; ++i) {
1807 QStringList::iterator iterator = std::lower_bound(first: newFiles.begin(), last: newFiles.end(), val: i.value()->fileName);
1808 if ((iterator == newFiles.end()) || (i.value()->fileName < *iterator))
1809 toRemove.append(t: i.value()->fileName);
1810 }
1811 for (int i = 0 ; i < toRemove.size() ; ++i )
1812 removeNode(parentNode, name: toRemove[i]);
1813}
1814
1815#if defined(Q_OS_WIN)
1816static QString volumeName(const QString &path)
1817{
1818 IShellItem *item = nullptr;
1819 const QString native = QDir::toNativeSeparators(path);
1820 HRESULT hr = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
1821 nullptr, IID_IShellItem,
1822 reinterpret_cast<void **>(&item));
1823 if (FAILED(hr))
1824 return QString();
1825 LPWSTR name = nullptr;
1826 hr = item->GetDisplayName(SIGDN_NORMALDISPLAY, &name);
1827 if (FAILED(hr))
1828 return QString();
1829 QString result = QString::fromWCharArray(name);
1830 CoTaskMemFree(name);
1831 item->Release();
1832 return result;
1833}
1834#endif // Q_OS_WIN
1835
1836/*!
1837 \internal
1838
1839 Adds a new file to the children of parentNode
1840
1841 *WARNING* this will change the count of children
1842*/
1843QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1844{
1845 // In the common case, itemLocation == count() so check there first
1846 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1847#if QT_CONFIG(filesystemwatcher)
1848 node->populate(fileInfo: info);
1849#else
1850 Q_UNUSED(info);
1851#endif
1852#if defined(Q_OS_WIN)
1853 //The parentNode is "" so we are listing the drives
1854 if (parentNode->fileName.isEmpty())
1855 node->volumeName = volumeName(fileName);
1856#endif
1857 Q_ASSERT(!parentNode->children.contains(fileName));
1858 parentNode->children.insert(key: fileName, value: node);
1859 return node;
1860}
1861
1862/*!
1863 \internal
1864
1865 File at parentNode->children(itemLocation) has been removed, remove from the lists
1866 and emit signals if necessary
1867
1868 *WARNING* this will change the count of children and could change visibleChildren
1869 */
1870void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1871{
1872 Q_Q(QFileSystemModel);
1873 QModelIndex parent = index(node: parentNode);
1874 bool indexHidden = isHiddenByFilter(indexNode: parentNode, index: parent);
1875
1876 int vLocation = parentNode->visibleLocation(childName: name);
1877 if (vLocation >= 0 && !indexHidden)
1878 q->beginRemoveRows(parent, first: translateVisibleLocation(parent: parentNode, row: vLocation),
1879 last: translateVisibleLocation(parent: parentNode, row: vLocation));
1880 QFileSystemNode * node = parentNode->children.take(key: name);
1881 delete node;
1882 // cleanup sort files after removing rather then re-sorting which is O(n)
1883 if (vLocation >= 0)
1884 parentNode->visibleChildren.removeAt(i: vLocation);
1885 if (vLocation >= 0 && !indexHidden)
1886 q->endRemoveRows();
1887}
1888
1889/*!
1890 \internal
1891
1892 File at parentNode->children(itemLocation) was not visible before, but now should be
1893 and emit signals if necessary.
1894
1895 *WARNING* this will change the visible count
1896 */
1897void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1898{
1899 Q_Q(QFileSystemModel);
1900 QModelIndex parent = index(node: parentNode);
1901 bool indexHidden = isHiddenByFilter(indexNode: parentNode, index: parent);
1902 if (!indexHidden) {
1903 q->beginInsertRows(parent, first: parentNode->visibleChildren.size() , last: parentNode->visibleChildren.size() + newFiles.size() - 1);
1904 }
1905
1906 if (parentNode->dirtyChildrenIndex == -1)
1907 parentNode->dirtyChildrenIndex = parentNode->visibleChildren.size();
1908
1909 for (const auto &newFile : newFiles) {
1910 parentNode->visibleChildren.append(t: newFile);
1911 parentNode->children.value(key: newFile)->isVisible = true;
1912 }
1913 if (!indexHidden)
1914 q->endInsertRows();
1915}
1916
1917/*!
1918 \internal
1919
1920 File was visible before, but now should NOT be
1921
1922 *WARNING* this will change the visible count
1923 */
1924void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1925{
1926 Q_Q(QFileSystemModel);
1927 if (vLocation == -1)
1928 return;
1929 QModelIndex parent = index(node: parentNode);
1930 bool indexHidden = isHiddenByFilter(indexNode: parentNode, index: parent);
1931 if (!indexHidden)
1932 q->beginRemoveRows(parent, first: translateVisibleLocation(parent: parentNode, row: vLocation),
1933 last: translateVisibleLocation(parent: parentNode, row: vLocation));
1934 parentNode->children.value(key: parentNode->visibleChildren.at(i: vLocation))->isVisible = false;
1935 parentNode->visibleChildren.removeAt(i: vLocation);
1936 if (!indexHidden)
1937 q->endRemoveRows();
1938}
1939
1940/*!
1941 \internal
1942
1943 The thread has received new information about files,
1944 update and emit dataChanged if it has actually changed.
1945 */
1946void QFileSystemModelPrivate::fileSystemChanged(const QString &path,
1947 const QList<std::pair<QString, QFileInfo>> &updates)
1948{
1949#if QT_CONFIG(filesystemwatcher)
1950 Q_Q(QFileSystemModel);
1951 QList<QString> rowsToUpdate;
1952 QStringList newFiles;
1953 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, fetch: false);
1954 QModelIndex parentIndex = index(node: parentNode);
1955 for (const auto &update : updates) {
1956 QString fileName = update.first;
1957 Q_ASSERT(!fileName.isEmpty());
1958 QExtendedInformation info = fileInfoGatherer->getInfo(info: update.second);
1959 bool previouslyHere = parentNode->children.contains(key: fileName);
1960 if (!previouslyHere) {
1961#ifdef Q_OS_WIN
1962 chopSpaceAndDot(fileName);
1963 if (fileName.isEmpty())
1964 continue;
1965#endif
1966 addNode(parentNode, fileName, info: info.fileInfo());
1967 }
1968 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(key: fileName);
1969 bool isCaseSensitive = parentNode->caseSensitive();
1970 if (isCaseSensitive) {
1971 if (node->fileName != fileName)
1972 continue;
1973 } else {
1974 if (QString::compare(s1: node->fileName,s2: fileName,cs: Qt::CaseInsensitive) != 0)
1975 continue;
1976 }
1977 if (isCaseSensitive) {
1978 Q_ASSERT(node->fileName == fileName);
1979 } else {
1980 node->fileName = fileName;
1981 }
1982
1983 if (*node != info ) {
1984 node->populate(fileInfo: info);
1985 bypassFilters.remove(key: node);
1986 // brand new information.
1987 if (filtersAcceptsNode(node)) {
1988 if (!node->isVisible) {
1989 newFiles.append(t: fileName);
1990 } else {
1991 rowsToUpdate.append(t: fileName);
1992 }
1993 } else {
1994 if (node->isVisible) {
1995 int visibleLocation = parentNode->visibleLocation(childName: fileName);
1996 removeVisibleFile(parentNode, vLocation: visibleLocation);
1997 } else {
1998 // The file is not visible, don't do anything
1999 }
2000 }
2001 }
2002 }
2003
2004 // bundle up all of the changed signals into as few as possible.
2005 std::sort(first: rowsToUpdate.begin(), last: rowsToUpdate.end());
2006 QString min;
2007 QString max;
2008 for (const QString &value : std::as_const(t&: rowsToUpdate)) {
2009 //##TODO is there a way to bundle signals with QString as the content of the list?
2010 /*if (min.isEmpty()) {
2011 min = value;
2012 if (i != rowsToUpdate.count() - 1)
2013 continue;
2014 }
2015 if (i != rowsToUpdate.count() - 1) {
2016 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
2017 max = value;
2018 continue;
2019 }
2020 }*/
2021 max = value;
2022 min = value;
2023 int visibleMin = parentNode->visibleLocation(childName: min);
2024 int visibleMax = parentNode->visibleLocation(childName: max);
2025 if (visibleMin >= 0
2026 && visibleMin < parentNode->visibleChildren.size()
2027 && parentNode->visibleChildren.at(i: visibleMin) == min
2028 && visibleMax >= 0) {
2029 // don't use NumColumns here, a subclass might override columnCount
2030 const int lastColumn = q->columnCount(parent: parentIndex) - 1;
2031 const QModelIndex top = q->index(row: translateVisibleLocation(parent: parentNode, row: visibleMin),
2032 column: QFileSystemModelPrivate::NameColumn, parent: parentIndex);
2033 const QModelIndex bottom = q->index(row: translateVisibleLocation(parent: parentNode, row: visibleMax),
2034 column: lastColumn, parent: parentIndex);
2035 // We document that emitting dataChanged with indexes that don't have the
2036 // same parent is undefined behavior.
2037 Q_ASSERT(bottom.parent() == top.parent());
2038 emit q->dataChanged(topLeft: top, bottomRight: bottom);
2039 }
2040
2041 /*min = QString();
2042 max = QString();*/
2043 }
2044
2045 if (newFiles.size() > 0) {
2046 addVisibleFiles(parentNode, newFiles);
2047 }
2048
2049 if (newFiles.size() > 0 || (sortColumn != 0 && rowsToUpdate.size() > 0)) {
2050 forceSort = true;
2051 delayedSort();
2052 }
2053#else
2054 Q_UNUSED(path);
2055 Q_UNUSED(updates);
2056#endif // filesystemwatcher
2057}
2058
2059/*!
2060 \internal
2061*/
2062void QFileSystemModelPrivate::resolvedName(const QString &fileName, const QString &resolvedName)
2063{
2064 resolvedSymLinks[fileName] = resolvedName;
2065}
2066
2067#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
2068// Remove file system watchers at/below the index and return a list of previously
2069// watched files. This should be called prior to operations like rename/remove
2070// which might fail due to watchers on platforms like Windows. The watchers
2071// should be restored on failure.
2072QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index)
2073{
2074 const QFileSystemModelPrivate::QFileSystemNode *indexNode = node(index);
2075 if (indexNode == nullptr)
2076 return QStringList();
2077 const Qt::CaseSensitivity caseSensitivity = indexNode->caseSensitive()
2078 ? Qt::CaseSensitive : Qt::CaseInsensitive;
2079 const QString path = indexNode->fileInfo().absoluteFilePath();
2080
2081 QStringList result;
2082 const auto filter = [path, caseSensitivity] (const QString &watchedPath)
2083 {
2084 const int pathSize = path.size();
2085 if (pathSize == watchedPath.size()) {
2086 return path.compare(watchedPath, caseSensitivity) == 0;
2087 } else if (watchedPath.size() > pathSize) {
2088 return watchedPath.at(pathSize) == u'/'
2089 && watchedPath.startsWith(path, caseSensitivity);
2090 }
2091 return false;
2092 };
2093
2094 const QStringList &watchedFiles = fileInfoGatherer->watchedFiles();
2095 std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(),
2096 std::back_inserter(result), filter);
2097
2098 const QStringList &watchedDirectories = fileInfoGatherer->watchedDirectories();
2099 std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(),
2100 std::back_inserter(result), filter);
2101
2102 fileInfoGatherer->unwatchPaths(result);
2103 return result;
2104}
2105#endif // filesystemwatcher && Q_OS_WIN
2106
2107QFileSystemModelPrivate::QFileSystemModelPrivate()
2108#if QT_CONFIG(filesystemwatcher)
2109 : fileInfoGatherer(new QFileInfoGatherer)
2110#endif // filesystemwatcher
2111{
2112}
2113
2114QFileSystemModelPrivate::~QFileSystemModelPrivate()
2115{
2116#if QT_CONFIG(filesystemwatcher)
2117 fileInfoGatherer->requestAbort();
2118 if (!fileInfoGatherer->wait(time: 1000)) {
2119 // If the thread hangs, perhaps because the network was disconnected
2120 // while the gatherer was stat'ing a remote file, then don't block
2121 // shutting down the model (which might block a file dialog and the
2122 // main thread). Schedule the gatherer for later deletion; it's
2123 // destructor will wait for the thread to finish.
2124 auto *rawGatherer = fileInfoGatherer.release();
2125 rawGatherer->deleteLater();
2126 }
2127#endif // filesystemwatcher
2128}
2129
2130/*!
2131 \internal
2132*/
2133void QFileSystemModelPrivate::init()
2134{
2135 Q_Q(QFileSystemModel);
2136
2137 delayedSortTimer.setSingleShot(true);
2138
2139 qRegisterMetaType<QList<std::pair<QString, QFileInfo>>>();
2140#if QT_CONFIG(filesystemwatcher)
2141 QObjectPrivate::connect(sender: fileInfoGatherer.get(), signal: &QFileInfoGatherer::newListOfFiles,
2142 receiverPrivate: this, slot: &QFileSystemModelPrivate::directoryChanged);
2143 QObjectPrivate::connect(sender: fileInfoGatherer.get(), signal: &QFileInfoGatherer::updates,
2144 receiverPrivate: this, slot: &QFileSystemModelPrivate::fileSystemChanged);
2145 QObjectPrivate::connect(sender: fileInfoGatherer.get(), signal: &QFileInfoGatherer::nameResolved,
2146 receiverPrivate: this, slot: &QFileSystemModelPrivate::resolvedName);
2147 q->connect(sender: fileInfoGatherer.get(), signal: &QFileInfoGatherer::directoryLoaded,
2148 context: q, slot: &QFileSystemModel::directoryLoaded);
2149#endif // filesystemwatcher
2150 QObjectPrivate::connect(sender: &delayedSortTimer, signal: &QTimer::timeout,
2151 receiverPrivate: this, slot: &QFileSystemModelPrivate::performDelayedSort,
2152 type: Qt::QueuedConnection);
2153}
2154
2155/*!
2156 \internal
2157
2158 Returns \c false if node doesn't pass the filters otherwise true
2159
2160 QDir::Modified is not supported
2161 QDir::Drives is not supported
2162*/
2163bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
2164{
2165 // When the model is set to only show files, then a node representing a dir
2166 // should be hidden regardless of bypassFilters.
2167 // QTBUG-74471
2168 const bool hideDirs = (filters & (QDir::Dirs | QDir::AllDirs)) == 0;
2169 const bool shouldHideDirNode = hideDirs && node->isDir();
2170
2171 // always accept drives
2172 if (node->parent == &root || (!shouldHideDirNode && bypassFilters.contains(key: node)))
2173 return true;
2174
2175 // If we don't know anything yet don't accept it
2176 if (!node->hasInformation())
2177 return false;
2178
2179 const bool filterPermissions = ((filters & QDir::PermissionMask)
2180 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
2181 const bool hideFiles = !(filters & QDir::Files);
2182 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
2183 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
2184 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
2185 const bool hideHidden = !(filters & QDir::Hidden);
2186 const bool hideSystem = !(filters & QDir::System);
2187 const bool hideSymlinks = (filters & QDir::NoSymLinks);
2188 const bool hideDot = (filters & QDir::NoDot);
2189 const bool hideDotDot = (filters & QDir::NoDotDot);
2190
2191 // Note that we match the behavior of entryList and not QFileInfo on this.
2192 bool isDot = (node->fileName == "."_L1);
2193 bool isDotDot = (node->fileName == ".."_L1);
2194 if ( (hideHidden && !(isDot || isDotDot) && node->isHidden())
2195 || (hideSystem && node->isSystem())
2196 || (hideDirs && node->isDir())
2197 || (hideFiles && node->isFile())
2198 || (hideSymlinks && node->isSymLink())
2199 || (hideReadable && node->isReadable())
2200 || (hideWritable && node->isWritable())
2201 || (hideExecutable && node->isExecutable())
2202 || (hideDot && isDot)
2203 || (hideDotDot && isDotDot))
2204 return false;
2205
2206 return nameFilterDisables || passNameFilters(node);
2207}
2208
2209/*
2210 \internal
2211
2212 Returns \c true if node passes the name filters and should be visible.
2213 */
2214bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
2215{
2216#if QT_CONFIG(regularexpression)
2217 if (nameFilters.isEmpty())
2218 return true;
2219
2220 // Check the name regularexpression filters
2221 if (!(node->isDir() && (filters & QDir::AllDirs))) {
2222 const auto matchesNodeFileName = [node](const QRegularExpression &re)
2223 {
2224 return node->fileName.contains(re);
2225 };
2226 return std::any_of(first: nameFiltersRegexps.begin(),
2227 last: nameFiltersRegexps.end(),
2228 pred: matchesNodeFileName);
2229 }
2230#else
2231 Q_UNUSED(node);
2232#endif
2233 return true;
2234}
2235
2236#if QT_CONFIG(regularexpression)
2237void QFileSystemModelPrivate::rebuildNameFilterRegexps()
2238{
2239 nameFiltersRegexps.clear();
2240 nameFiltersRegexps.reserve(n: nameFilters.size());
2241 const auto cs = (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
2242 const auto convertWildcardToRegexp = [cs](const QString &nameFilter)
2243 {
2244 return QRegularExpression::fromWildcard(pattern: nameFilter, cs);
2245 };
2246 std::transform(first: nameFilters.constBegin(),
2247 last: nameFilters.constEnd(),
2248 result: std::back_inserter(x&: nameFiltersRegexps),
2249 unary_op: convertWildcardToRegexp);
2250}
2251#endif
2252
2253QT_END_NAMESPACE
2254
2255#include "moc_qfilesystemmodel.cpp"
2256

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/gui/itemmodels/qfilesystemmodel.cpp