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