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 && !elementPath.endsWith(separator))
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 QString ePath = elementPath;
448#ifdef Q_OS_WIN
449 // Special case: elementPath is a drive root path (C:). If we do not have the trailing
450 // '/' it will be read as a relative path (QTBUG-133746)
451 if (ePath.length() == 2 && ePath.at(0).isLetter() && ePath.at(1) == u':')
452 ePath.append(u'/');
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(ePath);
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_MACOS
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_MACOS
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_MACOS
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 {
1134 const QFileSystemModelSorter ms(column);
1135 std::sort(values.begin(), values.end(), std::cref(ms));
1136 }
1137 // First update the new visible list
1138 indexNode->visibleChildren.clear();
1139 //No more dirty item we reset our internal dirty index
1140 indexNode->dirtyChildrenIndex = -1;
1141 indexNode->visibleChildren.reserve(values.size());
1142 for (QFileSystemNode *node : std::as_const(values)) {
1143 indexNode->visibleChildren.append(node->fileName);
1144 node->isVisible = true;
1145 }
1146
1147 if (!disableRecursiveSort) {
1148 for (int i = 0; i < q->rowCount(parent); ++i) {
1149 const QModelIndex childIndex = q->index(i, 0, parent);
1150 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(childIndex);
1151 //Only do a recursive sort on visible nodes
1152 if (indexNode->isVisible)
1153 sortChildren(column, childIndex);
1154 }
1155 }
1156}
1157
1158/*!
1159 \reimp
1160*/
1161void QFileSystemModel::sort(int column, Qt::SortOrder order)
1162{
1163 Q_D(QFileSystemModel);
1164 if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
1165 return;
1166
1167 emit layoutAboutToBeChanged();
1168 QModelIndexList oldList = persistentIndexList();
1169 QList<std::pair<QFileSystemModelPrivate::QFileSystemNode *, int>> oldNodes;
1170 oldNodes.reserve(oldList.size());
1171 for (const QModelIndex &oldNode : oldList)
1172 oldNodes.emplace_back(d->node(oldNode), oldNode.column());
1173
1174 if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) {
1175 //we sort only from where we are, don't need to sort all the model
1176 d->sortChildren(column, index(rootPath()));
1177 d->sortColumn = column;
1178 d->forceSort = false;
1179 }
1180 d->sortOrder = order;
1181
1182 QModelIndexList newList;
1183 newList.reserve(oldNodes.size());
1184 for (const auto &[node, col]: std::as_const(oldNodes))
1185 newList.append(d->index(node, col));
1186
1187 changePersistentIndexList(oldList, newList);
1188 emit layoutChanged({}, VerticalSortHint);
1189}
1190
1191/*!
1192 Returns a list of MIME types that can be used to describe a list of items
1193 in the model.
1194*/
1195QStringList QFileSystemModel::mimeTypes() const
1196{
1197 return QStringList("text/uri-list"_L1);
1198}
1199
1200/*!
1201 Returns an object that contains a serialized description of the specified
1202 \a indexes. The format used to describe the items corresponding to the
1203 indexes is obtained from the mimeTypes() function.
1204
1205 If the list of indexes is empty, \nullptr is returned rather than a
1206 serialized empty list.
1207*/
1208QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const
1209{
1210 QList<QUrl> urls;
1211 QList<QModelIndex>::const_iterator it = indexes.begin();
1212 for (; it != indexes.end(); ++it)
1213 if ((*it).column() == QFileSystemModelPrivate::NameColumn)
1214 urls << QUrl::fromLocalFile(filePath(*it));
1215 QMimeData *data = new QMimeData();
1216 data->setUrls(urls);
1217 return data;
1218}
1219
1220/*!
1221 Handles the \a data supplied by a drag and drop operation that ended with
1222 the given \a action over the row in the model specified by the \a row and
1223 \a column and by the \a parent index. Returns true if the operation was
1224 successful.
1225
1226 \sa supportedDropActions()
1227*/
1228bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
1229 int row, int column, const QModelIndex &parent)
1230{
1231 Q_UNUSED(row);
1232 Q_UNUSED(column);
1233 if (!parent.isValid() || isReadOnly())
1234 return false;
1235
1236 bool success = true;
1237 QString to = filePath(parent) + QDir::separator();
1238
1239 QList<QUrl> urls = data->urls();
1240 QList<QUrl>::const_iterator it = urls.constBegin();
1241
1242 switch (action) {
1243 case Qt::CopyAction:
1244 for (; it != urls.constEnd(); ++it) {
1245 QString path = (*it).toLocalFile();
1246 success = QFile::copy(path, to + QFileInfo(path).fileName()) && success;
1247 }
1248 break;
1249 case Qt::LinkAction:
1250 for (; it != urls.constEnd(); ++it) {
1251 QString path = (*it).toLocalFile();
1252 success = QFile::link(path, to + QFileInfo(path).fileName()) && success;
1253 }
1254 break;
1255 case Qt::MoveAction:
1256 for (; it != urls.constEnd(); ++it) {
1257 QString path = (*it).toLocalFile();
1258 success = QFile::rename(path, to + QFileInfo(path).fileName()) && success;
1259 }
1260 break;
1261 default:
1262 return false;
1263 }
1264
1265 return success;
1266}
1267
1268/*!
1269 \reimp
1270*/
1271Qt::DropActions QFileSystemModel::supportedDropActions() const
1272{
1273 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
1274}
1275
1276/*!
1277 \reimp
1278*/
1279QHash<int, QByteArray> QFileSystemModel::roleNames() const
1280{
1281 static auto ret = [] {
1282 auto ret = QAbstractItemModelPrivate::defaultRoleNames();
1283 ret.insert(QFileSystemModel::FileIconRole, "fileIcon"_ba); // == Qt::decoration
1284 ret.insert(QFileSystemModel::FilePathRole, "filePath"_ba);
1285 ret.insert(QFileSystemModel::FileNameRole, "fileName"_ba);
1286 ret.insert(QFileSystemModel::FilePermissions, "filePermissions"_ba);
1287 ret.insert(QFileSystemModel::FileInfoRole, "fileInfo"_ba);
1288 return ret;
1289 }();
1290 return ret;
1291}
1292
1293/*!
1294 \enum QFileSystemModel::Option
1295 \since 5.14
1296
1297 \value DontWatchForChanges Do not add file watchers to the paths.
1298 This reduces overhead when using the model for simple tasks
1299 like line edit completion.
1300
1301 \value DontResolveSymlinks Don't resolve symlinks in the file
1302 system model. By default, symlinks are resolved.
1303
1304 \value DontUseCustomDirectoryIcons Always use the default directory icon.
1305 Some platforms allow the user to set a different icon. Custom icon lookup
1306 causes a big performance impact over network or removable drives.
1307 This sets the QFileIconProvider::DontUseCustomDirectoryIcons
1308 option in the icon provider accordingly.
1309
1310 \sa resolveSymlinks
1311*/
1312
1313/*!
1314 \since 5.14
1315 Sets the given \a option to be enabled if \a on is true; otherwise,
1316 clears the given \a option.
1317
1318 Options should be set before changing properties.
1319
1320 \sa options, testOption()
1321*/
1322void QFileSystemModel::setOption(Option option, bool on)
1323{
1324 QFileSystemModel::Options previousOptions = options();
1325 setOptions(previousOptions.setFlag(option, on));
1326}
1327
1328/*!
1329 \since 5.14
1330
1331 Returns \c true if the given \a option is enabled; otherwise, returns
1332 false.
1333
1334 \sa options, setOption()
1335*/
1336bool QFileSystemModel::testOption(Option option) const
1337{
1338 return options().testFlag(option);
1339}
1340
1341/*!
1342 \property QFileSystemModel::options
1343 \brief the various options that affect the model
1344 \since 5.14
1345
1346 By default, all options are disabled.
1347
1348 Options should be set before changing properties.
1349
1350 \sa setOption(), testOption()
1351*/
1352void QFileSystemModel::setOptions(Options options)
1353{
1354 const Options changed = (options ^ QFileSystemModel::options());
1355
1356 if (changed.testFlag(DontResolveSymlinks))
1357 setResolveSymlinks(!options.testFlag(DontResolveSymlinks));
1358
1359#if QT_CONFIG(filesystemwatcher)
1360 Q_D(QFileSystemModel);
1361 if (changed.testFlag(DontWatchForChanges))
1362 d->fileInfoGatherer->setWatching(!options.testFlag(DontWatchForChanges));
1363#endif
1364
1365 if (changed.testFlag(DontUseCustomDirectoryIcons)) {
1366 if (auto provider = iconProvider()) {
1367 QAbstractFileIconProvider::Options providerOptions = provider->options();
1368 providerOptions.setFlag(QAbstractFileIconProvider::DontUseCustomDirectoryIcons,
1369 options.testFlag(QFileSystemModel::DontUseCustomDirectoryIcons));
1370 provider->setOptions(providerOptions);
1371 } else {
1372 qWarning("Setting QFileSystemModel::DontUseCustomDirectoryIcons has no effect when no provider is used");
1373 }
1374 }
1375}
1376
1377QFileSystemModel::Options QFileSystemModel::options() const
1378{
1379 QFileSystemModel::Options result;
1380 result.setFlag(DontResolveSymlinks, !resolveSymlinks());
1381#if QT_CONFIG(filesystemwatcher)
1382 Q_D(const QFileSystemModel);
1383 result.setFlag(DontWatchForChanges, !d->fileInfoGatherer->isWatching());
1384#else
1385 result.setFlag(DontWatchForChanges);
1386#endif
1387 if (auto provider = iconProvider()) {
1388 result.setFlag(DontUseCustomDirectoryIcons,
1389 provider->options().testFlag(QAbstractFileIconProvider::DontUseCustomDirectoryIcons));
1390 }
1391 return result;
1392}
1393
1394/*!
1395 Returns the path of the item stored in the model under the
1396 \a index given.
1397*/
1398QString QFileSystemModel::filePath(const QModelIndex &index) const
1399{
1400 Q_D(const QFileSystemModel);
1401 QString fullPath = d->filePath(index);
1402 QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
1403 if (dirNode->isSymLink()
1404#if QT_CONFIG(filesystemwatcher)
1405 && d->fileInfoGatherer->resolveSymlinks()
1406#endif
1407 && d->resolvedSymLinks.contains(fullPath)
1408 && dirNode->isDir()) {
1409 QFileInfo fullPathInfo(dirNode->fileInfo());
1410 if (!dirNode->hasInformation())
1411 fullPathInfo = QFileInfo(fullPath);
1412 QString canonicalPath = fullPathInfo.canonicalFilePath();
1413 auto *canonicalNode = d->node(fullPathInfo.canonicalFilePath(), false);
1414 QFileInfo resolvedInfo = canonicalNode->fileInfo();
1415 if (!canonicalNode->hasInformation())
1416 resolvedInfo = QFileInfo(canonicalPath);
1417 if (resolvedInfo.exists())
1418 return resolvedInfo.filePath();
1419 }
1420 return fullPath;
1421}
1422
1423QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const
1424{
1425 Q_Q(const QFileSystemModel);
1426 Q_UNUSED(q);
1427 if (!index.isValid())
1428 return QString();
1429 Q_ASSERT(index.model() == q);
1430
1431 QStringList path;
1432 QModelIndex idx = index;
1433 while (idx.isValid()) {
1434 QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx);
1435 if (dirNode)
1436 path.prepend(dirNode->fileName);
1437 idx = idx.parent();
1438 }
1439 QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator()));
1440#if !defined(Q_OS_WIN)
1441 if ((fullPath.size() > 2) && fullPath[0] == u'/' && fullPath[1] == u'/')
1442 fullPath = fullPath.mid(1);
1443#else
1444 if (fullPath.length() == 2 && fullPath.endsWith(u':'))
1445 fullPath.append(u'/');
1446#endif
1447 return fullPath;
1448}
1449
1450/*!
1451 Create a directory with the \a name in the \a parent model index.
1452*/
1453QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name)
1454{
1455 Q_D(QFileSystemModel);
1456 if (!parent.isValid())
1457 return parent;
1458
1459 QString fileName = name;
1460#ifdef Q_OS_WIN
1461 chopSpaceAndDot(fileName);
1462 if (fileName.isEmpty())
1463 return QModelIndex();
1464#endif
1465
1466 QDir dir(filePath(parent));
1467 if (!dir.mkdir(fileName))
1468 return QModelIndex();
1469 QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
1470 d->addNode(parentNode, fileName, QFileInfo());
1471 Q_ASSERT(parentNode->children.contains(fileName));
1472 QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[fileName];
1473#if QT_CONFIG(filesystemwatcher)
1474 node->populate(d->fileInfoGatherer->getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + fileName)));
1475#endif
1476 d->addVisibleFiles(parentNode, QStringList(fileName));
1477 return d->index(node);
1478}
1479
1480/*!
1481 Returns the complete OR-ed together combination of QFile::Permission for the \a index.
1482 */
1483QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const
1484{
1485 Q_D(const QFileSystemModel);
1486 return d->node(index)->permissions();
1487}
1488
1489/*!
1490 Sets the directory that is being watched by the model to \a newPath by
1491 installing a \l{QFileSystemWatcher}{file system watcher} on it. Any
1492 changes to files and directories within this directory will be
1493 reflected in the model.
1494
1495 If the path is changed, the rootPathChanged() signal will be emitted.
1496
1497 \note This function does not change the structure of the model or
1498 modify the data available to views. In other words, the "root" of
1499 the model is \e not changed to include only files and directories
1500 within the directory specified by \a newPath in the file system.
1501
1502 \sa {QTreeView::setRootIndex()}, {QtQuick::}{TreeView::rootIndex}
1503 */
1504QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
1505{
1506 Q_D(QFileSystemModel);
1507#ifdef Q_OS_WIN
1508#ifdef Q_OS_WIN32
1509 QString longNewPath = qt_GetLongPathName(newPath);
1510#else
1511 QString longNewPath = QDir::fromNativeSeparators(newPath);
1512#endif
1513#else
1514 QString longNewPath = newPath;
1515#endif
1516 //we remove .. and . from the given path if exist
1517 if (!newPath.isEmpty())
1518 longNewPath = QDir::cleanPath(longNewPath);
1519
1520 d->setRootPath = true;
1521
1522 //user don't ask for the root path ("") but the conversion failed
1523 if (!newPath.isEmpty() && longNewPath.isEmpty())
1524 return d->index(rootPath());
1525
1526 if (d->rootDir.path() == longNewPath)
1527 return d->index(rootPath());
1528
1529 auto node = d->node(longNewPath);
1530 QFileInfo newPathInfo;
1531 if (node && node->hasInformation())
1532 newPathInfo = node->fileInfo();
1533 else
1534 newPathInfo = QFileInfo(longNewPath);
1535
1536 bool showDrives = (longNewPath.isEmpty() || longNewPath == QFileSystemModelPrivate::myComputer());
1537 if (!showDrives && !newPathInfo.exists())
1538 return d->index(rootPath());
1539
1540 //We remove the watcher on the previous path
1541 if (!rootPath().isEmpty() && rootPath() != "."_L1) {
1542 //This remove the watcher for the old rootPath
1543#if QT_CONFIG(filesystemwatcher)
1544 d->fileInfoGatherer->removePath(rootPath());
1545#endif
1546 //This line "marks" the node as dirty, so the next fetchMore
1547 //call on the path will ask the gatherer to install a watcher again
1548 //But it doesn't re-fetch everything
1549 d->node(rootPath())->populatedChildren = false;
1550 }
1551
1552 // We have a new valid root path
1553 d->rootDir = QDir(longNewPath);
1554 QModelIndex newRootIndex;
1555 if (showDrives) {
1556 // otherwise dir will become '.'
1557 d->rootDir.setPath(""_L1);
1558 } else {
1559 newRootIndex = d->index(d->rootDir.path());
1560 }
1561 fetchMore(newRootIndex);
1562 emit rootPathChanged(longNewPath);
1563 d->forceSort = true;
1564 d->delayedSort();
1565 return newRootIndex;
1566}
1567
1568/*!
1569 The currently set root path
1570
1571 \sa rootDirectory()
1572*/
1573QString QFileSystemModel::rootPath() const
1574{
1575 Q_D(const QFileSystemModel);
1576 return d->rootDir.path();
1577}
1578
1579/*!
1580 The currently set directory
1581
1582 \sa rootPath()
1583*/
1584QDir QFileSystemModel::rootDirectory() const
1585{
1586 Q_D(const QFileSystemModel);
1587 QDir dir(d->rootDir);
1588 dir.setNameFilters(nameFilters());
1589 dir.setFilter(filter());
1590 return dir;
1591}
1592
1593/*!
1594 Sets the \a provider of file icons for the directory model.
1595*/
1596void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider)
1597{
1598 Q_D(QFileSystemModel);
1599#if QT_CONFIG(filesystemwatcher)
1600 d->fileInfoGatherer->setIconProvider(provider);
1601#endif
1602 d->root.updateIcon(provider, QString());
1603}
1604
1605/*!
1606 Returns the file icon provider for this directory model.
1607*/
1608QAbstractFileIconProvider *QFileSystemModel::iconProvider() const
1609{
1610#if QT_CONFIG(filesystemwatcher)
1611 Q_D(const QFileSystemModel);
1612 return d->fileInfoGatherer->iconProvider();
1613#else
1614 return nullptr;
1615#endif
1616}
1617
1618/*!
1619 Sets the directory model's filter to that specified by \a filters.
1620
1621 Note that the filter you set should always include the QDir::AllDirs enum value,
1622 otherwise QFileSystemModel won't be able to read the directory structure.
1623
1624 \sa QDir::Filters
1625*/
1626void QFileSystemModel::setFilter(QDir::Filters filters)
1627{
1628 Q_D(QFileSystemModel);
1629 if (d->filters == filters)
1630 return;
1631 const bool changingCaseSensitivity =
1632 filters.testFlag(QDir::CaseSensitive) != d->filters.testFlag(QDir::CaseSensitive);
1633 d->filters = filters;
1634 if (changingCaseSensitivity)
1635 d->rebuildNameFilterRegexps();
1636 d->forceSort = true;
1637 d->delayedSort();
1638}
1639
1640/*!
1641 Returns the filter specified for the directory model.
1642
1643 If a filter has not been set, the default filter is QDir::AllEntries |
1644 QDir::NoDotAndDotDot | QDir::AllDirs.
1645
1646 \sa QDir::Filters
1647*/
1648QDir::Filters QFileSystemModel::filter() const
1649{
1650 Q_D(const QFileSystemModel);
1651 return d->filters;
1652}
1653
1654/*!
1655 \property QFileSystemModel::resolveSymlinks
1656 \brief Whether the directory model should resolve symbolic links
1657
1658 This is only relevant on Windows.
1659
1660 By default, this property is \c true.
1661
1662 \sa QFileSystemModel::Options
1663*/
1664void QFileSystemModel::setResolveSymlinks(bool enable)
1665{
1666#if QT_CONFIG(filesystemwatcher)
1667 Q_D(QFileSystemModel);
1668 d->fileInfoGatherer->setResolveSymlinks(enable);
1669#else
1670 Q_UNUSED(enable);
1671#endif
1672}
1673
1674bool QFileSystemModel::resolveSymlinks() const
1675{
1676#if QT_CONFIG(filesystemwatcher)
1677 Q_D(const QFileSystemModel);
1678 return d->fileInfoGatherer->resolveSymlinks();
1679#else
1680 return false;
1681#endif
1682}
1683
1684/*!
1685 \property QFileSystemModel::readOnly
1686 \brief Whether the directory model allows writing to the file system
1687
1688 If this property is set to false, the directory model will allow renaming, copying
1689 and deleting of files and directories.
1690
1691 This property is \c true by default
1692*/
1693void QFileSystemModel::setReadOnly(bool enable)
1694{
1695 Q_D(QFileSystemModel);
1696 d->readOnly = enable;
1697}
1698
1699bool QFileSystemModel::isReadOnly() const
1700{
1701 Q_D(const QFileSystemModel);
1702 return d->readOnly;
1703}
1704
1705/*!
1706 \property QFileSystemModel::nameFilterDisables
1707 \brief Whether files that don't pass the name filter are hidden or disabled
1708
1709 This property is \c true by default
1710*/
1711void QFileSystemModel::setNameFilterDisables(bool enable)
1712{
1713 Q_D(QFileSystemModel);
1714 if (d->nameFilterDisables == enable)
1715 return;
1716 d->nameFilterDisables = enable;
1717 d->forceSort = true;
1718 d->delayedSort();
1719}
1720
1721bool QFileSystemModel::nameFilterDisables() const
1722{
1723 Q_D(const QFileSystemModel);
1724 return d->nameFilterDisables;
1725}
1726
1727/*!
1728 Sets the name \a filters to apply against the existing files.
1729*/
1730void QFileSystemModel::setNameFilters(const QStringList &filters)
1731{
1732#if QT_CONFIG(regularexpression)
1733 Q_D(QFileSystemModel);
1734
1735 if (!d->bypassFilters.isEmpty()) {
1736 // update the bypass filter to only bypass the stuff that must be kept around
1737 d->bypassFilters.clear();
1738 // We guarantee that rootPath will stick around
1739 QPersistentModelIndex root(index(rootPath()));
1740 const QModelIndexList persistentList = persistentIndexList();
1741 for (const auto &persistentIndex : persistentList) {
1742 QFileSystemModelPrivate::QFileSystemNode *node = d->node(persistentIndex);
1743 while (node) {
1744 if (d->bypassFilters.contains(node))
1745 break;
1746 if (node->isDir())
1747 d->bypassFilters[node] = true;
1748 node = node->parent;
1749 }
1750 }
1751 }
1752
1753 d->nameFilters = filters;
1754 d->rebuildNameFilterRegexps();
1755 d->forceSort = true;
1756 d->delayedSort();
1757#else
1758 Q_UNUSED(filters);
1759#endif
1760}
1761
1762/*!
1763 Returns a list of filters applied to the names in the model.
1764*/
1765QStringList QFileSystemModel::nameFilters() const
1766{
1767#if QT_CONFIG(regularexpression)
1768 Q_D(const QFileSystemModel);
1769 return d->nameFilters;
1770#else
1771 return QStringList();
1772#endif
1773}
1774
1775/*!
1776 \reimp
1777*/
1778bool QFileSystemModel::event(QEvent *event)
1779{
1780#if QT_CONFIG(filesystemwatcher)
1781 Q_D(QFileSystemModel);
1782 if (event->type() == QEvent::LanguageChange) {
1783 d->root.retranslateStrings(d->fileInfoGatherer->iconProvider(), QString());
1784 return true;
1785 }
1786#endif
1787 return QAbstractItemModel::event(event);
1788}
1789
1790bool QFileSystemModel::rmdir(const QModelIndex &aindex)
1791{
1792 Q_D(QFileSystemModel);
1793
1794 QString path = filePath(aindex);
1795 const bool success = QDir().rmdir(path);
1796 if (success) {
1797#if QT_CONFIG(filesystemwatcher)
1798 d->fileInfoGatherer->removePath(path);
1799#endif
1800 QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(aindex.parent());
1801 d->removeNode(parentNode, fileName(aindex));
1802 }
1803 return success;
1804}
1805
1806/*!
1807 \internal
1808
1809 Performed quick listing and see if any files have been added or removed,
1810 then fetch more information on visible files.
1811 */
1812void QFileSystemModelPrivate::directoryChanged(const QString &directory, const QStringList &files)
1813{
1814 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
1815 if (parentNode->children.size() == 0)
1816 return;
1817 QStringList toRemove;
1818 QStringList newFiles = files;
1819 std::sort(newFiles.begin(), newFiles.end());
1820 for (auto i = parentNode->children.constBegin(), cend = parentNode->children.constEnd(); i != cend; ++i) {
1821 QStringList::iterator iterator = std::lower_bound(newFiles.begin(), newFiles.end(), i.value()->fileName);
1822 if ((iterator == newFiles.end()) || (i.value()->fileName < *iterator))
1823 toRemove.append(i.value()->fileName);
1824 }
1825 for (int i = 0 ; i < toRemove.size() ; ++i )
1826 removeNode(parentNode, toRemove[i]);
1827}
1828
1829#if defined(Q_OS_WIN)
1830static QString volumeName(const QString &path)
1831{
1832 IShellItem *item = nullptr;
1833 const QString native = QDir::toNativeSeparators(path);
1834 HRESULT hr = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
1835 nullptr, IID_IShellItem,
1836 reinterpret_cast<void **>(&item));
1837 if (FAILED(hr))
1838 return QString();
1839 LPWSTR name = nullptr;
1840 hr = item->GetDisplayName(SIGDN_NORMALDISPLAY, &name);
1841 if (FAILED(hr))
1842 return QString();
1843 QString result = QString::fromWCharArray(name);
1844 CoTaskMemFree(name);
1845 item->Release();
1846 return result;
1847}
1848#endif // Q_OS_WIN
1849
1850/*!
1851 \internal
1852
1853 Adds a new file to the children of parentNode
1854
1855 *WARNING* this will change the count of children
1856*/
1857QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1858{
1859 // In the common case, itemLocation == count() so check there first
1860 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1861#if QT_CONFIG(filesystemwatcher)
1862 node->populate(info);
1863#else
1864 Q_UNUSED(info);
1865#endif
1866#if defined(Q_OS_WIN)
1867 //The parentNode is "" so we are listing the drives
1868 if (parentNode->fileName.isEmpty())
1869 node->volumeName = volumeName(fileName);
1870#endif
1871 Q_ASSERT(!parentNode->children.contains(fileName));
1872 parentNode->children.insert(fileName, node);
1873 return node;
1874}
1875
1876/*!
1877 \internal
1878
1879 File at parentNode->children(itemLocation) has been removed, remove from the lists
1880 and emit signals if necessary
1881
1882 *WARNING* this will change the count of children and could change visibleChildren
1883 */
1884void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1885{
1886 Q_Q(QFileSystemModel);
1887 QModelIndex parent = index(parentNode);
1888 bool indexHidden = isHiddenByFilter(parentNode, parent);
1889
1890 int vLocation = parentNode->visibleLocation(name);
1891 if (vLocation >= 0 && !indexHidden)
1892 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1893 translateVisibleLocation(parentNode, vLocation));
1894 QFileSystemNode * node = parentNode->children.take(name);
1895 delete node;
1896 // cleanup sort files after removing rather then re-sorting which is O(n)
1897 if (vLocation >= 0) {
1898 parentNode->visibleChildren.removeAt(vLocation);
1899 if (vLocation < parentNode->dirtyChildrenIndex)
1900 --parentNode->dirtyChildrenIndex;
1901 }
1902 if (vLocation >= 0 && !indexHidden)
1903 q->endRemoveRows();
1904}
1905
1906/*!
1907 \internal
1908
1909 File at parentNode->children(itemLocation) was not visible before, but now should be
1910 and emit signals if necessary.
1911
1912 *WARNING* this will change the visible count
1913 */
1914void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1915{
1916 Q_Q(QFileSystemModel);
1917 QModelIndex parent = index(parentNode);
1918 bool indexHidden = isHiddenByFilter(parentNode, parent);
1919 if (!indexHidden) {
1920 q->beginInsertRows(parent, parentNode->visibleChildren.size() , parentNode->visibleChildren.size() + newFiles.size() - 1);
1921 }
1922
1923 if (parentNode->dirtyChildrenIndex == -1)
1924 parentNode->dirtyChildrenIndex = parentNode->visibleChildren.size();
1925
1926 for (const auto &newFile : newFiles) {
1927 parentNode->visibleChildren.append(newFile);
1928 parentNode->children.value(newFile)->isVisible = true;
1929 }
1930 if (!indexHidden)
1931 q->endInsertRows();
1932}
1933
1934/*!
1935 \internal
1936
1937 File was visible before, but now should NOT be
1938
1939 *WARNING* this will change the visible count
1940 */
1941void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1942{
1943 Q_Q(QFileSystemModel);
1944 if (vLocation == -1)
1945 return;
1946 QModelIndex parent = index(parentNode);
1947 bool indexHidden = isHiddenByFilter(parentNode, parent);
1948 if (!indexHidden)
1949 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1950 translateVisibleLocation(parentNode, vLocation));
1951 parentNode->children.value(parentNode->visibleChildren.at(vLocation))->isVisible = false;
1952 parentNode->visibleChildren.removeAt(vLocation);
1953 if (vLocation < parentNode->dirtyChildrenIndex)
1954 --parentNode->dirtyChildrenIndex;
1955 if (!indexHidden)
1956 q->endRemoveRows();
1957}
1958
1959/*!
1960 \internal
1961
1962 The thread has received new information about files,
1963 update and emit dataChanged if it has actually changed.
1964 */
1965void QFileSystemModelPrivate::fileSystemChanged(const QString &path,
1966 const QList<std::pair<QString, QFileInfo>> &updates)
1967{
1968#if QT_CONFIG(filesystemwatcher)
1969 Q_Q(QFileSystemModel);
1970 QList<QString> rowsToUpdate;
1971 QStringList newFiles;
1972 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false);
1973 QModelIndex parentIndex = index(parentNode);
1974 for (const auto &update : updates) {
1975 QString fileName = update.first;
1976 Q_ASSERT(!fileName.isEmpty());
1977 QExtendedInformation info = fileInfoGatherer->getInfo(update.second);
1978 bool previouslyHere = parentNode->children.contains(fileName);
1979 if (!previouslyHere) {
1980#ifdef Q_OS_WIN
1981 chopSpaceAndDot(fileName);
1982 if (fileName.isEmpty())
1983 continue;
1984#endif
1985 addNode(parentNode, fileName, info.fileInfo());
1986 }
1987 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName);
1988 bool isCaseSensitive = parentNode->caseSensitive();
1989 if (isCaseSensitive) {
1990 if (node->fileName != fileName)
1991 continue;
1992 } else {
1993 if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0)
1994 continue;
1995 }
1996 if (isCaseSensitive) {
1997 Q_ASSERT(node->fileName == fileName);
1998 } else {
1999 node->fileName = fileName;
2000 }
2001
2002 if (*node != info ) {
2003 node->populate(info);
2004 bypassFilters.remove(node);
2005 // brand new information.
2006 if (filtersAcceptsNode(node)) {
2007 if (!node->isVisible) {
2008 newFiles.append(fileName);
2009 } else {
2010 rowsToUpdate.append(fileName);
2011 }
2012 } else {
2013 if (node->isVisible) {
2014 int visibleLocation = parentNode->visibleLocation(fileName);
2015 removeVisibleFile(parentNode, visibleLocation);
2016 } else {
2017 // The file is not visible, don't do anything
2018 }
2019 }
2020 }
2021 }
2022
2023 // bundle up all of the changed signals into as few as possible.
2024 std::sort(rowsToUpdate.begin(), rowsToUpdate.end());
2025 QString min;
2026 QString max;
2027 for (const QString &value : std::as_const(rowsToUpdate)) {
2028 //##TODO is there a way to bundle signals with QString as the content of the list?
2029 /*if (min.isEmpty()) {
2030 min = value;
2031 if (i != rowsToUpdate.count() - 1)
2032 continue;
2033 }
2034 if (i != rowsToUpdate.count() - 1) {
2035 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
2036 max = value;
2037 continue;
2038 }
2039 }*/
2040 max = value;
2041 min = value;
2042 int visibleMin = parentNode->visibleLocation(min);
2043 int visibleMax = parentNode->visibleLocation(max);
2044 if (visibleMin >= 0
2045 && visibleMin < parentNode->visibleChildren.size()
2046 && parentNode->visibleChildren.at(visibleMin) == min
2047 && visibleMax >= 0) {
2048 // don't use NumColumns here, a subclass might override columnCount
2049 const int lastColumn = q->columnCount(parentIndex) - 1;
2050 const QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMin),
2051 QFileSystemModelPrivate::NameColumn, parentIndex);
2052 const QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMax),
2053 lastColumn, parentIndex);
2054 // We document that emitting dataChanged with indexes that don't have the
2055 // same parent is undefined behavior.
2056 Q_ASSERT(bottom.parent() == top.parent());
2057 emit q->dataChanged(top, bottom);
2058 }
2059
2060 /*min = QString();
2061 max = QString();*/
2062 }
2063
2064 if (newFiles.size() > 0) {
2065 addVisibleFiles(parentNode, newFiles);
2066 }
2067
2068 if (newFiles.size() > 0 || (sortColumn != 0 && rowsToUpdate.size() > 0)) {
2069 forceSort = true;
2070 delayedSort();
2071 }
2072#else
2073 Q_UNUSED(path);
2074 Q_UNUSED(updates);
2075#endif // filesystemwatcher
2076}
2077
2078/*!
2079 \internal
2080*/
2081void QFileSystemModelPrivate::resolvedName(const QString &fileName, const QString &resolvedName)
2082{
2083 resolvedSymLinks[fileName] = resolvedName;
2084}
2085
2086#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
2087// Remove file system watchers at/below the index and return a list of previously
2088// watched files. This should be called prior to operations like rename/remove
2089// which might fail due to watchers on platforms like Windows. The watchers
2090// should be restored on failure.
2091QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index)
2092{
2093 const QFileSystemModelPrivate::QFileSystemNode *indexNode = node(index);
2094 if (indexNode == nullptr)
2095 return QStringList();
2096 const Qt::CaseSensitivity caseSensitivity = indexNode->caseSensitive()
2097 ? Qt::CaseSensitive : Qt::CaseInsensitive;
2098 const QString path = indexNode->fileInfo().absoluteFilePath();
2099
2100 QStringList result;
2101 const auto filter = [path, caseSensitivity] (const QString &watchedPath)
2102 {
2103 const int pathSize = path.size();
2104 if (pathSize == watchedPath.size()) {
2105 return path.compare(watchedPath, caseSensitivity) == 0;
2106 } else if (watchedPath.size() > pathSize) {
2107 return watchedPath.at(pathSize) == u'/'
2108 && watchedPath.startsWith(path, caseSensitivity);
2109 }
2110 return false;
2111 };
2112
2113 const QStringList &watchedFiles = fileInfoGatherer->watchedFiles();
2114 std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(),
2115 std::back_inserter(result), filter);
2116
2117 const QStringList &watchedDirectories = fileInfoGatherer->watchedDirectories();
2118 std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(),
2119 std::back_inserter(result), filter);
2120
2121 fileInfoGatherer->unwatchPaths(result);
2122 return result;
2123}
2124#endif // filesystemwatcher && Q_OS_WIN
2125
2126QFileSystemModelPrivate::QFileSystemModelPrivate()
2127#if QT_CONFIG(filesystemwatcher)
2128 : fileInfoGatherer(new QFileInfoGatherer)
2129#endif // filesystemwatcher
2130{
2131}
2132
2133QFileSystemModelPrivate::~QFileSystemModelPrivate()
2134{
2135#if QT_CONFIG(filesystemwatcher)
2136 fileInfoGatherer->requestAbort();
2137 if (!fileInfoGatherer->wait(1000)) {
2138 // If the thread hangs, perhaps because the network was disconnected
2139 // while the gatherer was stat'ing a remote file, then don't block
2140 // shutting down the model (which might block a file dialog and the
2141 // main thread). Schedule the gatherer for later deletion; it's
2142 // destructor will wait for the thread to finish.
2143 auto *rawGatherer = fileInfoGatherer.release();
2144 rawGatherer->deleteLater();
2145 }
2146#endif // filesystemwatcher
2147}
2148
2149/*!
2150 \internal
2151*/
2152void QFileSystemModelPrivate::init()
2153{
2154 delayedSortTimer.setSingleShot(true);
2155
2156 qRegisterMetaType<QList<std::pair<QString, QFileInfo>>>();
2157#if QT_CONFIG(filesystemwatcher)
2158 QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::newListOfFiles,
2159 this, &QFileSystemModelPrivate::directoryChanged);
2160 QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::updates,
2161 this, &QFileSystemModelPrivate::fileSystemChanged);
2162 QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::nameResolved,
2163 this, &QFileSystemModelPrivate::resolvedName);
2164 Q_Q(QFileSystemModel);
2165 q->connect(fileInfoGatherer.get(), &QFileInfoGatherer::directoryLoaded,
2166 q, &QFileSystemModel::directoryLoaded);
2167#endif // filesystemwatcher
2168 QObjectPrivate::connect(&delayedSortTimer, &QTimer::timeout,
2169 this, &QFileSystemModelPrivate::performDelayedSort,
2170 Qt::QueuedConnection);
2171}
2172
2173/*!
2174 \internal
2175
2176 Returns \c false if node doesn't pass the filters otherwise true
2177
2178 QDir::Modified is not supported
2179 QDir::Drives is not supported
2180*/
2181bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
2182{
2183 // When the model is set to only show files, then a node representing a dir
2184 // should be hidden regardless of bypassFilters.
2185 // QTBUG-74471
2186 const bool hideDirs = (filters & (QDir::Dirs | QDir::AllDirs)) == 0;
2187 const bool shouldHideDirNode = hideDirs && node->isDir();
2188
2189 // always accept drives
2190 if (node->parent == &root || (!shouldHideDirNode && bypassFilters.contains(node)))
2191 return true;
2192
2193 // If we don't know anything yet don't accept it
2194 if (!node->hasInformation())
2195 return false;
2196
2197 const bool filterPermissions = ((filters & QDir::PermissionMask)
2198 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
2199 const bool hideFiles = !(filters & QDir::Files);
2200 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
2201 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
2202 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
2203 const bool hideHidden = !(filters & QDir::Hidden);
2204 const bool hideSystem = !(filters & QDir::System);
2205 const bool hideSymlinks = (filters & QDir::NoSymLinks);
2206 const bool hideDot = (filters & QDir::NoDot);
2207 const bool hideDotDot = (filters & QDir::NoDotDot);
2208
2209 // Note that we match the behavior of entryList and not QFileInfo on this.
2210 bool isDot = (node->fileName == "."_L1);
2211 bool isDotDot = (node->fileName == ".."_L1);
2212 if ( (hideHidden && !(isDot || isDotDot) && node->isHidden())
2213 || (hideSystem && node->isSystem())
2214 || (hideDirs && node->isDir())
2215 || (hideFiles && node->isFile())
2216 || (hideSymlinks && node->isSymLink())
2217 || (hideReadable && node->isReadable())
2218 || (hideWritable && node->isWritable())
2219 || (hideExecutable && node->isExecutable())
2220 || (hideDot && isDot)
2221 || (hideDotDot && isDotDot))
2222 return false;
2223
2224 return nameFilterDisables || passNameFilters(node);
2225}
2226
2227/*
2228 \internal
2229
2230 Returns \c true if node passes the name filters and should be visible.
2231 */
2232bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
2233{
2234#if QT_CONFIG(regularexpression)
2235 if (nameFilters.isEmpty())
2236 return true;
2237
2238 // Check the name regularexpression filters
2239 if (!(node->isDir() && (filters & QDir::AllDirs))) {
2240 const auto matchesNodeFileName = [node](const QRegularExpression &re)
2241 {
2242 return node->fileName.contains(re);
2243 };
2244 return std::any_of(nameFiltersRegexps.begin(),
2245 nameFiltersRegexps.end(),
2246 matchesNodeFileName);
2247 }
2248#else
2249 Q_UNUSED(node);
2250#endif
2251 return true;
2252}
2253
2254#if QT_CONFIG(regularexpression)
2255void QFileSystemModelPrivate::rebuildNameFilterRegexps()
2256{
2257 nameFiltersRegexps.clear();
2258 nameFiltersRegexps.reserve(nameFilters.size());
2259 const auto cs = (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
2260 const auto convertWildcardToRegexp = [cs](const QString &nameFilter)
2261 {
2262 return QRegularExpression::fromWildcard(nameFilter, cs);
2263 };
2264 std::transform(nameFilters.constBegin(),
2265 nameFilters.constEnd(),
2266 std::back_inserter(nameFiltersRegexps),
2267 convertWildcardToRegexp);
2268}
2269#endif
2270
2271QT_END_NAMESPACE
2272
2273#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