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

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