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 \section1 Example Usage
59
60 A directory model that displays the contents of a default directory
61 is usually constructed with a parent object:
62
63 \snippet shareddirmodel/main.cpp 2
64
65 A tree view can be used to display the contents of the model
66
67 \snippet shareddirmodel/main.cpp 4
68
69 and the contents of a particular directory can be displayed by
70 setting the tree view's root index:
71
72 \snippet shareddirmodel/main.cpp 7
73
74 The view's root index can be used to control how much of a
75 hierarchical model is displayed. QFileSystemModel provides a convenience
76 function that returns a suitable model index for a path to a
77 directory within the model.
78
79 \section1 Caching and Performance
80
81 QFileSystemModel uses a separate thread to populate itself, so it will not
82 cause the main thread to hang as the file system is being queried. Calls to
83 rowCount() will return \c 0 until the model populates a directory. The thread
84 in which the QFileSystemModel lives needs to run an event loop to process
85 the incoming data.
86
87 QFileSystemModel will not start populating itself until setRootPath() is
88 called. This prevents any unnecessary querying of the system's root file
89 system, such as enumerating the drives on Windows, until that point.
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(parents: {}, hint: VerticalSortHint);
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
1493 \sa {QTreeView::setRootIndex()}, {QtQuick::}{TreeView::rootIndex}
1494 */
1495QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
1496{
1497 Q_D(QFileSystemModel);
1498#ifdef Q_OS_WIN
1499#ifdef Q_OS_WIN32
1500 QString longNewPath = qt_GetLongPathName(newPath);
1501#else
1502 QString longNewPath = QDir::fromNativeSeparators(newPath);
1503#endif
1504#else
1505 QString longNewPath = newPath;
1506#endif
1507 //we remove .. and . from the given path if exist
1508 if (!newPath.isEmpty())
1509 longNewPath = QDir::cleanPath(path: longNewPath);
1510
1511 d->setRootPath = true;
1512
1513 //user don't ask for the root path ("") but the conversion failed
1514 if (!newPath.isEmpty() && longNewPath.isEmpty())
1515 return d->index(path: rootPath());
1516
1517 if (d->rootDir.path() == longNewPath)
1518 return d->index(path: rootPath());
1519
1520 auto node = d->node(path: longNewPath);
1521 QFileInfo newPathInfo;
1522 if (node && node->hasInformation())
1523 newPathInfo = node->fileInfo();
1524 else
1525 newPathInfo = QFileInfo(longNewPath);
1526
1527 bool showDrives = (longNewPath.isEmpty() || longNewPath == QFileSystemModelPrivate::myComputer());
1528 if (!showDrives && !newPathInfo.exists())
1529 return d->index(path: rootPath());
1530
1531 //We remove the watcher on the previous path
1532 if (!rootPath().isEmpty() && rootPath() != "."_L1) {
1533 //This remove the watcher for the old rootPath
1534#if QT_CONFIG(filesystemwatcher)
1535 d->fileInfoGatherer->removePath(path: rootPath());
1536#endif
1537 //This line "marks" the node as dirty, so the next fetchMore
1538 //call on the path will ask the gatherer to install a watcher again
1539 //But it doesn't re-fetch everything
1540 d->node(path: rootPath())->populatedChildren = false;
1541 }
1542
1543 // We have a new valid root path
1544 d->rootDir = QDir(longNewPath);
1545 QModelIndex newRootIndex;
1546 if (showDrives) {
1547 // otherwise dir will become '.'
1548 d->rootDir.setPath(""_L1);
1549 } else {
1550 newRootIndex = d->index(path: d->rootDir.path());
1551 }
1552 fetchMore(parent: newRootIndex);
1553 emit rootPathChanged(newPath: longNewPath);
1554 d->forceSort = true;
1555 d->delayedSort();
1556 return newRootIndex;
1557}
1558
1559/*!
1560 The currently set root path
1561
1562 \sa rootDirectory()
1563*/
1564QString QFileSystemModel::rootPath() const
1565{
1566 Q_D(const QFileSystemModel);
1567 return d->rootDir.path();
1568}
1569
1570/*!
1571 The currently set directory
1572
1573 \sa rootPath()
1574*/
1575QDir QFileSystemModel::rootDirectory() const
1576{
1577 Q_D(const QFileSystemModel);
1578 QDir dir(d->rootDir);
1579 dir.setNameFilters(nameFilters());
1580 dir.setFilter(filter());
1581 return dir;
1582}
1583
1584/*!
1585 Sets the \a provider of file icons for the directory model.
1586*/
1587void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider)
1588{
1589 Q_D(QFileSystemModel);
1590#if QT_CONFIG(filesystemwatcher)
1591 d->fileInfoGatherer->setIconProvider(provider);
1592#endif
1593 d->root.updateIcon(iconProvider: provider, path: QString());
1594}
1595
1596/*!
1597 Returns the file icon provider for this directory model.
1598*/
1599QAbstractFileIconProvider *QFileSystemModel::iconProvider() const
1600{
1601#if QT_CONFIG(filesystemwatcher)
1602 Q_D(const QFileSystemModel);
1603 return d->fileInfoGatherer->iconProvider();
1604#else
1605 return nullptr;
1606#endif
1607}
1608
1609/*!
1610 Sets the directory model's filter to that specified by \a filters.
1611
1612 Note that the filter you set should always include the QDir::AllDirs enum value,
1613 otherwise QFileSystemModel won't be able to read the directory structure.
1614
1615 \sa QDir::Filters
1616*/
1617void QFileSystemModel::setFilter(QDir::Filters filters)
1618{
1619 Q_D(QFileSystemModel);
1620 if (d->filters == filters)
1621 return;
1622 const bool changingCaseSensitivity =
1623 filters.testFlag(flag: QDir::CaseSensitive) != d->filters.testFlag(flag: QDir::CaseSensitive);
1624 d->filters = filters;
1625 if (changingCaseSensitivity)
1626 d->rebuildNameFilterRegexps();
1627 d->forceSort = true;
1628 d->delayedSort();
1629}
1630
1631/*!
1632 Returns the filter specified for the directory model.
1633
1634 If a filter has not been set, the default filter is QDir::AllEntries |
1635 QDir::NoDotAndDotDot | QDir::AllDirs.
1636
1637 \sa QDir::Filters
1638*/
1639QDir::Filters QFileSystemModel::filter() const
1640{
1641 Q_D(const QFileSystemModel);
1642 return d->filters;
1643}
1644
1645/*!
1646 \property QFileSystemModel::resolveSymlinks
1647 \brief Whether the directory model should resolve symbolic links
1648
1649 This is only relevant on Windows.
1650
1651 By default, this property is \c true.
1652
1653 \sa QFileSystemModel::Options
1654*/
1655void QFileSystemModel::setResolveSymlinks(bool enable)
1656{
1657#if QT_CONFIG(filesystemwatcher)
1658 Q_D(QFileSystemModel);
1659 d->fileInfoGatherer->setResolveSymlinks(enable);
1660#else
1661 Q_UNUSED(enable);
1662#endif
1663}
1664
1665bool QFileSystemModel::resolveSymlinks() const
1666{
1667#if QT_CONFIG(filesystemwatcher)
1668 Q_D(const QFileSystemModel);
1669 return d->fileInfoGatherer->resolveSymlinks();
1670#else
1671 return false;
1672#endif
1673}
1674
1675/*!
1676 \property QFileSystemModel::readOnly
1677 \brief Whether the directory model allows writing to the file system
1678
1679 If this property is set to false, the directory model will allow renaming, copying
1680 and deleting of files and directories.
1681
1682 This property is \c true by default
1683*/
1684void QFileSystemModel::setReadOnly(bool enable)
1685{
1686 Q_D(QFileSystemModel);
1687 d->readOnly = enable;
1688}
1689
1690bool QFileSystemModel::isReadOnly() const
1691{
1692 Q_D(const QFileSystemModel);
1693 return d->readOnly;
1694}
1695
1696/*!
1697 \property QFileSystemModel::nameFilterDisables
1698 \brief Whether files that don't pass the name filter are hidden or disabled
1699
1700 This property is \c true by default
1701*/
1702void QFileSystemModel::setNameFilterDisables(bool enable)
1703{
1704 Q_D(QFileSystemModel);
1705 if (d->nameFilterDisables == enable)
1706 return;
1707 d->nameFilterDisables = enable;
1708 d->forceSort = true;
1709 d->delayedSort();
1710}
1711
1712bool QFileSystemModel::nameFilterDisables() const
1713{
1714 Q_D(const QFileSystemModel);
1715 return d->nameFilterDisables;
1716}
1717
1718/*!
1719 Sets the name \a filters to apply against the existing files.
1720*/
1721void QFileSystemModel::setNameFilters(const QStringList &filters)
1722{
1723#if QT_CONFIG(regularexpression)
1724 Q_D(QFileSystemModel);
1725
1726 if (!d->bypassFilters.isEmpty()) {
1727 // update the bypass filter to only bypass the stuff that must be kept around
1728 d->bypassFilters.clear();
1729 // We guarantee that rootPath will stick around
1730 QPersistentModelIndex root(index(path: rootPath()));
1731 const QModelIndexList persistentList = persistentIndexList();
1732 for (const auto &persistentIndex : persistentList) {
1733 QFileSystemModelPrivate::QFileSystemNode *node = d->node(index: persistentIndex);
1734 while (node) {
1735 if (d->bypassFilters.contains(key: node))
1736 break;
1737 if (node->isDir())
1738 d->bypassFilters[node] = true;
1739 node = node->parent;
1740 }
1741 }
1742 }
1743
1744 d->nameFilters = filters;
1745 d->rebuildNameFilterRegexps();
1746 d->forceSort = true;
1747 d->delayedSort();
1748#else
1749 Q_UNUSED(filters);
1750#endif
1751}
1752
1753/*!
1754 Returns a list of filters applied to the names in the model.
1755*/
1756QStringList QFileSystemModel::nameFilters() const
1757{
1758#if QT_CONFIG(regularexpression)
1759 Q_D(const QFileSystemModel);
1760 return d->nameFilters;
1761#else
1762 return QStringList();
1763#endif
1764}
1765
1766/*!
1767 \reimp
1768*/
1769bool QFileSystemModel::event(QEvent *event)
1770{
1771#if QT_CONFIG(filesystemwatcher)
1772 Q_D(QFileSystemModel);
1773 if (event->type() == QEvent::LanguageChange) {
1774 d->root.retranslateStrings(iconProvider: d->fileInfoGatherer->iconProvider(), path: QString());
1775 return true;
1776 }
1777#endif
1778 return QAbstractItemModel::event(event);
1779}
1780
1781bool QFileSystemModel::rmdir(const QModelIndex &aindex)
1782{
1783 QString path = filePath(index: aindex);
1784 const bool success = QDir().rmdir(dirName: path);
1785#if QT_CONFIG(filesystemwatcher)
1786 if (success) {
1787 QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func());
1788 d->fileInfoGatherer->removePath(path);
1789 }
1790#endif
1791 return success;
1792}
1793
1794/*!
1795 \internal
1796
1797 Performed quick listing and see if any files have been added or removed,
1798 then fetch more information on visible files.
1799 */
1800void QFileSystemModelPrivate::directoryChanged(const QString &directory, const QStringList &files)
1801{
1802 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path: directory, fetch: false);
1803 if (parentNode->children.size() == 0)
1804 return;
1805 QStringList toRemove;
1806 QStringList newFiles = files;
1807 std::sort(first: newFiles.begin(), last: newFiles.end());
1808 for (auto i = parentNode->children.constBegin(), cend = parentNode->children.constEnd(); i != cend; ++i) {
1809 QStringList::iterator iterator = std::lower_bound(first: newFiles.begin(), last: newFiles.end(), val: i.value()->fileName);
1810 if ((iterator == newFiles.end()) || (i.value()->fileName < *iterator))
1811 toRemove.append(t: i.value()->fileName);
1812 }
1813 for (int i = 0 ; i < toRemove.size() ; ++i )
1814 removeNode(parentNode, name: toRemove[i]);
1815}
1816
1817#if defined(Q_OS_WIN)
1818static QString volumeName(const QString &path)
1819{
1820 IShellItem *item = nullptr;
1821 const QString native = QDir::toNativeSeparators(path);
1822 HRESULT hr = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
1823 nullptr, IID_IShellItem,
1824 reinterpret_cast<void **>(&item));
1825 if (FAILED(hr))
1826 return QString();
1827 LPWSTR name = nullptr;
1828 hr = item->GetDisplayName(SIGDN_NORMALDISPLAY, &name);
1829 if (FAILED(hr))
1830 return QString();
1831 QString result = QString::fromWCharArray(name);
1832 CoTaskMemFree(name);
1833 item->Release();
1834 return result;
1835}
1836#endif // Q_OS_WIN
1837
1838/*!
1839 \internal
1840
1841 Adds a new file to the children of parentNode
1842
1843 *WARNING* this will change the count of children
1844*/
1845QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1846{
1847 // In the common case, itemLocation == count() so check there first
1848 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1849#if QT_CONFIG(filesystemwatcher)
1850 node->populate(fileInfo: info);
1851#else
1852 Q_UNUSED(info);
1853#endif
1854#if defined(Q_OS_WIN)
1855 //The parentNode is "" so we are listing the drives
1856 if (parentNode->fileName.isEmpty())
1857 node->volumeName = volumeName(fileName);
1858#endif
1859 Q_ASSERT(!parentNode->children.contains(fileName));
1860 parentNode->children.insert(key: fileName, value: node);
1861 return node;
1862}
1863
1864/*!
1865 \internal
1866
1867 File at parentNode->children(itemLocation) has been removed, remove from the lists
1868 and emit signals if necessary
1869
1870 *WARNING* this will change the count of children and could change visibleChildren
1871 */
1872void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1873{
1874 Q_Q(QFileSystemModel);
1875 QModelIndex parent = index(node: parentNode);
1876 bool indexHidden = isHiddenByFilter(indexNode: parentNode, index: parent);
1877
1878 int vLocation = parentNode->visibleLocation(childName: name);
1879 if (vLocation >= 0 && !indexHidden)
1880 q->beginRemoveRows(parent, first: translateVisibleLocation(parent: parentNode, row: vLocation),
1881 last: translateVisibleLocation(parent: parentNode, row: vLocation));
1882 QFileSystemNode * node = parentNode->children.take(key: name);
1883 delete node;
1884 // cleanup sort files after removing rather then re-sorting which is O(n)
1885 if (vLocation >= 0)
1886 parentNode->visibleChildren.removeAt(i: vLocation);
1887 if (vLocation >= 0 && !indexHidden)
1888 q->endRemoveRows();
1889}
1890
1891/*!
1892 \internal
1893
1894 File at parentNode->children(itemLocation) was not visible before, but now should be
1895 and emit signals if necessary.
1896
1897 *WARNING* this will change the visible count
1898 */
1899void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1900{
1901 Q_Q(QFileSystemModel);
1902 QModelIndex parent = index(node: parentNode);
1903 bool indexHidden = isHiddenByFilter(indexNode: parentNode, index: parent);
1904 if (!indexHidden) {
1905 q->beginInsertRows(parent, first: parentNode->visibleChildren.size() , last: parentNode->visibleChildren.size() + newFiles.size() - 1);
1906 }
1907
1908 if (parentNode->dirtyChildrenIndex == -1)
1909 parentNode->dirtyChildrenIndex = parentNode->visibleChildren.size();
1910
1911 for (const auto &newFile : newFiles) {
1912 parentNode->visibleChildren.append(t: newFile);
1913 parentNode->children.value(key: newFile)->isVisible = true;
1914 }
1915 if (!indexHidden)
1916 q->endInsertRows();
1917}
1918
1919/*!
1920 \internal
1921
1922 File was visible before, but now should NOT be
1923
1924 *WARNING* this will change the visible count
1925 */
1926void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1927{
1928 Q_Q(QFileSystemModel);
1929 if (vLocation == -1)
1930 return;
1931 QModelIndex parent = index(node: parentNode);
1932 bool indexHidden = isHiddenByFilter(indexNode: parentNode, index: parent);
1933 if (!indexHidden)
1934 q->beginRemoveRows(parent, first: translateVisibleLocation(parent: parentNode, row: vLocation),
1935 last: translateVisibleLocation(parent: parentNode, row: vLocation));
1936 parentNode->children.value(key: parentNode->visibleChildren.at(i: vLocation))->isVisible = false;
1937 parentNode->visibleChildren.removeAt(i: vLocation);
1938 if (!indexHidden)
1939 q->endRemoveRows();
1940}
1941
1942/*!
1943 \internal
1944
1945 The thread has received new information about files,
1946 update and emit dataChanged if it has actually changed.
1947 */
1948void QFileSystemModelPrivate::fileSystemChanged(const QString &path,
1949 const QList<std::pair<QString, QFileInfo>> &updates)
1950{
1951#if QT_CONFIG(filesystemwatcher)
1952 Q_Q(QFileSystemModel);
1953 QList<QString> rowsToUpdate;
1954 QStringList newFiles;
1955 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, fetch: false);
1956 QModelIndex parentIndex = index(node: parentNode);
1957 for (const auto &update : updates) {
1958 QString fileName = update.first;
1959 Q_ASSERT(!fileName.isEmpty());
1960 QExtendedInformation info = fileInfoGatherer->getInfo(info: update.second);
1961 bool previouslyHere = parentNode->children.contains(key: fileName);
1962 if (!previouslyHere) {
1963#ifdef Q_OS_WIN
1964 chopSpaceAndDot(fileName);
1965 if (fileName.isEmpty())
1966 continue;
1967#endif
1968 addNode(parentNode, fileName, info: info.fileInfo());
1969 }
1970 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(key: fileName);
1971 bool isCaseSensitive = parentNode->caseSensitive();
1972 if (isCaseSensitive) {
1973 if (node->fileName != fileName)
1974 continue;
1975 } else {
1976 if (QString::compare(s1: node->fileName,s2: fileName,cs: Qt::CaseInsensitive) != 0)
1977 continue;
1978 }
1979 if (isCaseSensitive) {
1980 Q_ASSERT(node->fileName == fileName);
1981 } else {
1982 node->fileName = fileName;
1983 }
1984
1985 if (*node != info ) {
1986 node->populate(fileInfo: info);
1987 bypassFilters.remove(key: node);
1988 // brand new information.
1989 if (filtersAcceptsNode(node)) {
1990 if (!node->isVisible) {
1991 newFiles.append(t: fileName);
1992 } else {
1993 rowsToUpdate.append(t: fileName);
1994 }
1995 } else {
1996 if (node->isVisible) {
1997 int visibleLocation = parentNode->visibleLocation(childName: fileName);
1998 removeVisibleFile(parentNode, vLocation: visibleLocation);
1999 } else {
2000 // The file is not visible, don't do anything
2001 }
2002 }
2003 }
2004 }
2005
2006 // bundle up all of the changed signals into as few as possible.
2007 std::sort(first: rowsToUpdate.begin(), last: rowsToUpdate.end());
2008 QString min;
2009 QString max;
2010 for (const QString &value : std::as_const(t&: rowsToUpdate)) {
2011 //##TODO is there a way to bundle signals with QString as the content of the list?
2012 /*if (min.isEmpty()) {
2013 min = value;
2014 if (i != rowsToUpdate.count() - 1)
2015 continue;
2016 }
2017 if (i != rowsToUpdate.count() - 1) {
2018 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
2019 max = value;
2020 continue;
2021 }
2022 }*/
2023 max = value;
2024 min = value;
2025 int visibleMin = parentNode->visibleLocation(childName: min);
2026 int visibleMax = parentNode->visibleLocation(childName: max);
2027 if (visibleMin >= 0
2028 && visibleMin < parentNode->visibleChildren.size()
2029 && parentNode->visibleChildren.at(i: visibleMin) == min
2030 && visibleMax >= 0) {
2031 // don't use NumColumns here, a subclass might override columnCount
2032 const int lastColumn = q->columnCount(parent: parentIndex) - 1;
2033 const QModelIndex top = q->index(row: translateVisibleLocation(parent: parentNode, row: visibleMin),
2034 column: QFileSystemModelPrivate::NameColumn, parent: parentIndex);
2035 const QModelIndex bottom = q->index(row: translateVisibleLocation(parent: parentNode, row: visibleMax),
2036 column: lastColumn, parent: parentIndex);
2037 // We document that emitting dataChanged with indexes that don't have the
2038 // same parent is undefined behavior.
2039 Q_ASSERT(bottom.parent() == top.parent());
2040 emit q->dataChanged(topLeft: top, bottomRight: bottom);
2041 }
2042
2043 /*min = QString();
2044 max = QString();*/
2045 }
2046
2047 if (newFiles.size() > 0) {
2048 addVisibleFiles(parentNode, newFiles);
2049 }
2050
2051 if (newFiles.size() > 0 || (sortColumn != 0 && rowsToUpdate.size() > 0)) {
2052 forceSort = true;
2053 delayedSort();
2054 }
2055#else
2056 Q_UNUSED(path);
2057 Q_UNUSED(updates);
2058#endif // filesystemwatcher
2059}
2060
2061/*!
2062 \internal
2063*/
2064void QFileSystemModelPrivate::resolvedName(const QString &fileName, const QString &resolvedName)
2065{
2066 resolvedSymLinks[fileName] = resolvedName;
2067}
2068
2069#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
2070// Remove file system watchers at/below the index and return a list of previously
2071// watched files. This should be called prior to operations like rename/remove
2072// which might fail due to watchers on platforms like Windows. The watchers
2073// should be restored on failure.
2074QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index)
2075{
2076 const QFileSystemModelPrivate::QFileSystemNode *indexNode = node(index);
2077 if (indexNode == nullptr)
2078 return QStringList();
2079 const Qt::CaseSensitivity caseSensitivity = indexNode->caseSensitive()
2080 ? Qt::CaseSensitive : Qt::CaseInsensitive;
2081 const QString path = indexNode->fileInfo().absoluteFilePath();
2082
2083 QStringList result;
2084 const auto filter = [path, caseSensitivity] (const QString &watchedPath)
2085 {
2086 const int pathSize = path.size();
2087 if (pathSize == watchedPath.size()) {
2088 return path.compare(watchedPath, caseSensitivity) == 0;
2089 } else if (watchedPath.size() > pathSize) {
2090 return watchedPath.at(pathSize) == u'/'
2091 && watchedPath.startsWith(path, caseSensitivity);
2092 }
2093 return false;
2094 };
2095
2096 const QStringList &watchedFiles = fileInfoGatherer->watchedFiles();
2097 std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(),
2098 std::back_inserter(result), filter);
2099
2100 const QStringList &watchedDirectories = fileInfoGatherer->watchedDirectories();
2101 std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(),
2102 std::back_inserter(result), filter);
2103
2104 fileInfoGatherer->unwatchPaths(result);
2105 return result;
2106}
2107#endif // filesystemwatcher && Q_OS_WIN
2108
2109QFileSystemModelPrivate::QFileSystemModelPrivate()
2110#if QT_CONFIG(filesystemwatcher)
2111 : fileInfoGatherer(new QFileInfoGatherer)
2112#endif // filesystemwatcher
2113{
2114}
2115
2116QFileSystemModelPrivate::~QFileSystemModelPrivate()
2117{
2118#if QT_CONFIG(filesystemwatcher)
2119 fileInfoGatherer->requestAbort();
2120 if (!fileInfoGatherer->wait(time: 1000)) {
2121 // If the thread hangs, perhaps because the network was disconnected
2122 // while the gatherer was stat'ing a remote file, then don't block
2123 // shutting down the model (which might block a file dialog and the
2124 // main thread). Schedule the gatherer for later deletion; it's
2125 // destructor will wait for the thread to finish.
2126 auto *rawGatherer = fileInfoGatherer.release();
2127 rawGatherer->deleteLater();
2128 }
2129#endif // filesystemwatcher
2130}
2131
2132/*!
2133 \internal
2134*/
2135void QFileSystemModelPrivate::init()
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_Q(QFileSystemModel);
2148 q->connect(sender: fileInfoGatherer.get(), signal: &QFileInfoGatherer::directoryLoaded,
2149 context: q, slot: &QFileSystemModel::directoryLoaded);
2150#endif // filesystemwatcher
2151 QObjectPrivate::connect(sender: &delayedSortTimer, signal: &QTimer::timeout,
2152 receiverPrivate: this, slot: &QFileSystemModelPrivate::performDelayedSort,
2153 type: Qt::QueuedConnection);
2154}
2155
2156/*!
2157 \internal
2158
2159 Returns \c false if node doesn't pass the filters otherwise true
2160
2161 QDir::Modified is not supported
2162 QDir::Drives is not supported
2163*/
2164bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
2165{
2166 // When the model is set to only show files, then a node representing a dir
2167 // should be hidden regardless of bypassFilters.
2168 // QTBUG-74471
2169 const bool hideDirs = (filters & (QDir::Dirs | QDir::AllDirs)) == 0;
2170 const bool shouldHideDirNode = hideDirs && node->isDir();
2171
2172 // always accept drives
2173 if (node->parent == &root || (!shouldHideDirNode && bypassFilters.contains(key: node)))
2174 return true;
2175
2176 // If we don't know anything yet don't accept it
2177 if (!node->hasInformation())
2178 return false;
2179
2180 const bool filterPermissions = ((filters & QDir::PermissionMask)
2181 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
2182 const bool hideFiles = !(filters & QDir::Files);
2183 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
2184 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
2185 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
2186 const bool hideHidden = !(filters & QDir::Hidden);
2187 const bool hideSystem = !(filters & QDir::System);
2188 const bool hideSymlinks = (filters & QDir::NoSymLinks);
2189 const bool hideDot = (filters & QDir::NoDot);
2190 const bool hideDotDot = (filters & QDir::NoDotDot);
2191
2192 // Note that we match the behavior of entryList and not QFileInfo on this.
2193 bool isDot = (node->fileName == "."_L1);
2194 bool isDotDot = (node->fileName == ".."_L1);
2195 if ( (hideHidden && !(isDot || isDotDot) && node->isHidden())
2196 || (hideSystem && node->isSystem())
2197 || (hideDirs && node->isDir())
2198 || (hideFiles && node->isFile())
2199 || (hideSymlinks && node->isSymLink())
2200 || (hideReadable && node->isReadable())
2201 || (hideWritable && node->isWritable())
2202 || (hideExecutable && node->isExecutable())
2203 || (hideDot && isDot)
2204 || (hideDotDot && isDotDot))
2205 return false;
2206
2207 return nameFilterDisables || passNameFilters(node);
2208}
2209
2210/*
2211 \internal
2212
2213 Returns \c true if node passes the name filters and should be visible.
2214 */
2215bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
2216{
2217#if QT_CONFIG(regularexpression)
2218 if (nameFilters.isEmpty())
2219 return true;
2220
2221 // Check the name regularexpression filters
2222 if (!(node->isDir() && (filters & QDir::AllDirs))) {
2223 const auto matchesNodeFileName = [node](const QRegularExpression &re)
2224 {
2225 return node->fileName.contains(re);
2226 };
2227 return std::any_of(first: nameFiltersRegexps.begin(),
2228 last: nameFiltersRegexps.end(),
2229 pred: matchesNodeFileName);
2230 }
2231#else
2232 Q_UNUSED(node);
2233#endif
2234 return true;
2235}
2236
2237#if QT_CONFIG(regularexpression)
2238void QFileSystemModelPrivate::rebuildNameFilterRegexps()
2239{
2240 nameFiltersRegexps.clear();
2241 nameFiltersRegexps.reserve(n: nameFilters.size());
2242 const auto cs = (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
2243 const auto convertWildcardToRegexp = [cs](const QString &nameFilter)
2244 {
2245 return QRegularExpression::fromWildcard(pattern: nameFilter, cs);
2246 };
2247 std::transform(first: nameFilters.constBegin(),
2248 last: nameFilters.constEnd(),
2249 result: std::back_inserter(x&: nameFiltersRegexps),
2250 unary_op: convertWildcardToRegexp);
2251}
2252#endif
2253
2254QT_END_NAMESPACE
2255
2256#include "moc_qfilesystemmodel.cpp"
2257

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