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

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