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