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