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