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 | */ |
170 | 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 | |
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 | */ |
233 | QFileSystemModel::QFileSystemModel(QObject *parent) : |
234 | QFileSystemModel(*new QFileSystemModelPrivate, parent) |
235 | { |
236 | } |
237 | |
238 | /*! |
239 | \internal |
240 | */ |
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 | */ |
256 | 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 | */ |
281 | 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 | */ |
298 | 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 | */ |
310 | 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 |
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 | */ |
359 | 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 | */ |
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 | */ |
534 | 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 | */ |
549 | 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 | */ |
560 | 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 | */ |
571 | 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 | */ |
582 | 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 | */ |
608 | 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 | */ |
627 | 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 | */ |
644 | 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 | */ |
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 | */ |
673 | 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 | */ |
689 | 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 | */ |
699 | 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 | */ |
718 | 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 | */ |
770 | 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 | |
789 | QString QFileSystemModelPrivate::size(qint64 bytes) |
790 | { |
791 | return QLocale::system().formattedDataSize(bytes); |
792 | } |
793 | |
794 | /*! |
795 | \internal |
796 | */ |
797 | 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 | */ |
812 | 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 | */ |
822 | 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 | */ |
841 | 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 | */ |
854 | QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const |
855 | { |
856 | if (!index.isValid()) |
857 | return QIcon(); |
858 | return node(index)->icon(); |
859 | } |
860 | |
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 | */ |
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 | */ |
946 | QVariant QFileSystemModel::(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 | */ |
992 | 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 | */ |
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: |
1036 | inline QFileSystemModelSorter(int column) : sortColumn(column) |
1037 | { |
1038 | naturalCompare.setNumericMode(true); |
1039 | naturalCompare.setCaseSensitivity(Qt::CaseInsensitive); |
1040 | } |
1041 | |
1042 | 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 | |
1090 | 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 | */ |
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 | */ |
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 | */ |
1190 | 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 | */ |
1203 | 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 | */ |
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 | */ |
1266 | 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 | */ |
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 | */ |
1314 | 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 | */ |
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 | |
1355 | 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 | */ |
1376 | 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 | |
1395 | 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 | */ |
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 | */ |
1448 | 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 | */ |
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 | */ |
1532 | 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 | */ |
1543 | 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 | */ |
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 | */ |
1567 | 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 | */ |
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 | */ |
1605 | 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 | */ |
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 | |
1631 | 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 | */ |
1650 | void QFileSystemModel::setReadOnly(bool enable) |
1651 | { |
1652 | Q_D(QFileSystemModel); |
1653 | d->readOnly = enable; |
1654 | } |
1655 | |
1656 | 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 | */ |
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 | |
1678 | 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 | */ |
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 | */ |
1720 | 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 | */ |
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 | |
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 | */ |
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) |
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 | */ |
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 | */ |
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 | */ |
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 | */ |
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 | */ |
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 | */ |
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. |
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 | */ |
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 | */ |
2097 | 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 | */ |
2143 | 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 | |