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