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