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 "qdirmodel.h"
41
42#if QT_DEPRECATED_SINCE(5, 15)
43
44#include <qfile.h>
45#include <qfilesystemmodel.h>
46#include <qurl.h>
47#include <qmimedata.h>
48#include <qpair.h>
49#include <qvector.h>
50#include <qobject.h>
51#include <qdatetime.h>
52#include <qlocale.h>
53#include <qstyle.h>
54#include <qapplication.h>
55#include <private/qabstractitemmodel_p.h>
56#include <private/qfilesystementry_p.h>
57#include <qdebug.h>
58
59#include <stack>
60#include <vector>
61
62/*!
63 \enum QDirModel::Roles
64 \value FileIconRole
65 \value FilePathRole
66 \value FileNameRole
67*/
68
69QT_BEGIN_NAMESPACE
70
71class QDirModelPrivate : public QAbstractItemModelPrivate
72{
73 Q_DECLARE_PUBLIC(QDirModel)
74
75public:
76 struct QDirNode
77 {
78 QDirNode() : parent(nullptr), populated(false), stat(false) {}
79 QDirNode *parent;
80 QFileInfo info;
81 QIcon icon; // cache the icon
82 mutable QVector<QDirNode> children;
83 mutable bool populated; // have we read the children
84 mutable bool stat;
85 };
86
87 QDirModelPrivate()
88 : resolveSymlinks(true),
89 readOnly(true),
90 lazyChildCount(false),
91 allowAppendChild(true),
92 iconProvider(&defaultProvider),
93 shouldStat(true) // ### This is set to false by QFileDialog
94 { }
95
96 void init();
97 QDirNode *node(int row, QDirNode *parent) const;
98 QVector<QDirNode> children(QDirNode *parent, bool stat) const;
99
100 void _q_refresh();
101
102 void savePersistentIndexes();
103 void restorePersistentIndexes();
104
105 QFileInfoList entryInfoList(const QString &path) const;
106 QStringList entryList(const QString &path) const;
107
108 QString name(const QModelIndex &index) const;
109 QString size(const QModelIndex &index) const;
110 QString type(const QModelIndex &index) const;
111 QString time(const QModelIndex &index) const;
112
113 void appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const;
114 static QFileInfo resolvedInfo(QFileInfo info);
115
116 inline QDirNode *node(const QModelIndex &index) const;
117 inline void populate(QDirNode *parent) const;
118 inline void clear(QDirNode *parent) const;
119
120 void invalidate();
121
122 mutable QDirNode root;
123 bool resolveSymlinks;
124 bool readOnly;
125 bool lazyChildCount;
126 bool allowAppendChild;
127
128 QDir::Filters filters;
129 QDir::SortFlags sort;
130 QStringList nameFilters;
131
132 QFileIconProvider *iconProvider;
133 QFileIconProvider defaultProvider;
134
135 struct SavedPersistent {
136 QString path;
137 int column;
138 QPersistentModelIndexData *data;
139 QPersistentModelIndex index;
140 };
141 QVector<SavedPersistent> savedPersistent;
142 QPersistentModelIndex toBeRefreshed;
143
144 bool shouldStat; // use the "carefull not to stat directories" mode
145};
146Q_DECLARE_TYPEINFO(QDirModelPrivate::SavedPersistent, Q_MOVABLE_TYPE);
147
148void qt_setDirModelShouldNotStat(QDirModelPrivate *modelPrivate)
149{
150 modelPrivate->shouldStat = false;
151}
152
153QDirModelPrivate::QDirNode *QDirModelPrivate::node(const QModelIndex &index) const
154{
155 QDirModelPrivate::QDirNode *n =
156 static_cast<QDirModelPrivate::QDirNode*>(index.internalPointer());
157 Q_ASSERT(n);
158 return n;
159}
160
161void QDirModelPrivate::populate(QDirNode *parent) const
162{
163 Q_ASSERT(parent);
164 parent->children = children(parent, stat: parent->stat);
165 parent->populated = true;
166}
167
168void QDirModelPrivate::clear(QDirNode *parent) const
169{
170 Q_ASSERT(parent);
171 parent->children.clear();
172 parent->populated = false;
173}
174
175void QDirModelPrivate::invalidate()
176{
177 std::stack<const QDirNode*, std::vector<const QDirNode*> > nodes;
178 nodes.push(x: &root);
179 while (!nodes.empty()) {
180 const QDirNode *current = nodes.top();
181 nodes.pop();
182 current->stat = false;
183 const QVector<QDirNode> &children = current->children;
184 for (const auto &child : children)
185 nodes.push(x: &child);
186 }
187}
188
189/*!
190 \class QDirModel
191 \obsolete
192 \brief The QDirModel class provides a data model for the local filesystem.
193
194 \ingroup model-view
195 \inmodule QtWidgets
196
197 The usage of QDirModel is not recommended anymore. The
198 QFileSystemModel class is a more performant alternative.
199
200 This class provides access to the local filesystem, providing functions
201 for renaming and removing files and directories, and for creating new
202 directories. In the simplest case, it can be used with a suitable display
203 widget as part of a browser or filer.
204
205 QDirModel keeps a cache with file information. The cache needs to be
206 updated with refresh().
207
208 QDirModel can be accessed using the standard interface provided by
209 QAbstractItemModel, but it also provides some convenience functions
210 that are specific to a directory model. The fileInfo() and isDir()
211 functions provide information about the underlying files and directories
212 related to items in the model.
213
214 Directories can be created and removed using mkdir(), rmdir(), and the
215 model will be automatically updated to take the changes into account.
216
217 \note QDirModel requires an instance of \l QApplication.
218
219 \sa nameFilters(), setFilter(), filter(), QListView, QTreeView, QFileSystemModel,
220 {Dir View Example}, {Model Classes}
221*/
222
223/*!
224 Constructs a new directory model with the given \a parent.
225 Only those files matching the \a nameFilters and the
226 \a filters are included in the model. The sort order is given by the
227 \a sort flags.
228*/
229
230QDirModel::QDirModel(const QStringList &nameFilters,
231 QDir::Filters filters,
232 QDir::SortFlags sort,
233 QObject *parent)
234 : QAbstractItemModel(*new QDirModelPrivate, parent)
235{
236 Q_D(QDirModel);
237 // we always start with QDir::drives()
238 d->nameFilters = nameFilters.isEmpty() ? QStringList(QLatin1String("*")) : nameFilters;
239 d->filters = filters;
240 d->sort = sort;
241 d->root.parent = nullptr;
242 d->root.info = QFileInfo();
243 d->clear(parent: &d->root);
244}
245
246/*!
247 Constructs a directory model with the given \a parent.
248*/
249
250QDirModel::QDirModel(QObject *parent)
251 : QAbstractItemModel(*new QDirModelPrivate, parent)
252{
253 Q_D(QDirModel);
254 d->init();
255}
256
257/*!
258 \internal
259*/
260QDirModel::QDirModel(QDirModelPrivate &dd, QObject *parent)
261 : QAbstractItemModel(dd, parent)
262{
263 Q_D(QDirModel);
264 d->init();
265}
266
267/*!
268 Destroys this directory model.
269*/
270
271QDirModel::~QDirModel()
272{
273
274}
275
276/*!
277 Returns the model item index for the item in the \a parent with the
278 given \a row and \a column.
279
280*/
281
282QModelIndex QDirModel::index(int row, int column, const QModelIndex &parent) const
283{
284 Q_D(const QDirModel);
285 // note that rowCount does lazy population
286 if (column < 0 || column >= columnCount(parent) || row < 0 || parent.column() > 0)
287 return QModelIndex();
288 // make sure the list of children is up to date
289 QDirModelPrivate::QDirNode *p = (d->indexValid(index: parent) ? d->node(index: parent) : &d->root);
290 Q_ASSERT(p);
291 if (!p->populated)
292 d->populate(parent: p); // populate without stat'ing
293 if (row >= p->children.count())
294 return QModelIndex();
295 // now get the internal pointer for the index
296 QDirModelPrivate::QDirNode *n = d->node(row, parent: d->indexValid(index: parent) ? p : nullptr);
297 Q_ASSERT(n);
298
299 return createIndex(arow: row, acolumn: column, adata: n);
300}
301
302/*!
303 Return the parent of the given \a child model item.
304*/
305
306QModelIndex QDirModel::parent(const QModelIndex &child) const
307{
308 Q_D(const QDirModel);
309
310 if (!d->indexValid(index: child))
311 return QModelIndex();
312 QDirModelPrivate::QDirNode *node = d->node(index: child);
313 QDirModelPrivate::QDirNode *par = (node ? node->parent : nullptr);
314 if (par == nullptr) // parent is the root node
315 return QModelIndex();
316
317 // get the parent's row
318 const QVector<QDirModelPrivate::QDirNode> children =
319 par->parent ? par->parent->children : d->root.children;
320 Q_ASSERT(children.count() > 0);
321 int row = (par - &(children.at(i: 0)));
322 Q_ASSERT(row >= 0);
323
324 return createIndex(arow: row, acolumn: 0, adata: par);
325}
326
327/*!
328 Returns the number of rows in the \a parent model item.
329
330*/
331
332int QDirModel::rowCount(const QModelIndex &parent) const
333{
334 Q_D(const QDirModel);
335 if (parent.column() > 0)
336 return 0;
337
338 if (!parent.isValid()) {
339 if (!d->root.populated) // lazy population
340 d->populate(parent: &d->root);
341 return d->root.children.count();
342 }
343 if (parent.model() != this)
344 return 0;
345 QDirModelPrivate::QDirNode *p = d->node(index: parent);
346 if (p->info.isDir() && !p->populated) // lazy population
347 d->populate(parent: p);
348 return p->children.count();
349}
350
351/*!
352 Returns the number of columns in the \a parent model item.
353
354*/
355
356int QDirModel::columnCount(const QModelIndex &parent) const
357{
358 if (parent.column() > 0)
359 return 0;
360 return 4;
361}
362
363/*!
364 Returns the data for the model item \a index with the given \a role.
365*/
366QVariant QDirModel::data(const QModelIndex &index, int role) const
367{
368 Q_D(const QDirModel);
369 if (!d->indexValid(index))
370 return QVariant();
371
372 if (role == Qt::DisplayRole || role == Qt::EditRole) {
373 switch (index.column()) {
374 case 0: return d->name(index);
375 case 1: return d->size(index);
376 case 2: return d->type(index);
377 case 3: return d->time(index);
378 default:
379 qWarning(msg: "data: invalid display value column %d", index.column());
380 return QVariant();
381 }
382 }
383
384 if (index.column() == 0) {
385 if (role == FileIconRole)
386 return fileIcon(index);
387 if (role == FilePathRole)
388 return filePath(index);
389 if (role == FileNameRole)
390 return fileName(index);
391 }
392
393 if (index.column() == 1 && Qt::TextAlignmentRole == role) {
394 return Qt::AlignRight;
395 }
396 return QVariant();
397}
398
399/*!
400 Sets the data for the model item \a index with the given \a role to
401 the data referenced by the \a value. Returns \c true if successful;
402 otherwise returns \c false.
403
404 \sa Qt::ItemDataRole
405*/
406
407bool QDirModel::setData(const QModelIndex &index, const QVariant &value, int role)
408{
409 Q_D(QDirModel);
410 if (!d->indexValid(index) || index.column() != 0
411 || (flags(index) & Qt::ItemIsEditable) == 0 || role != Qt::EditRole)
412 return false;
413
414 QDirModelPrivate::QDirNode *node = d->node(index);
415 QDir dir = node->info.dir();
416 QString name = value.toString();
417 if (dir.rename(oldName: node->info.fileName(), newName: name)) {
418 node->info = QFileInfo(dir, name);
419 QModelIndex sibling = index.sibling(arow: index.row(), acolumn: 3);
420 emit dataChanged(topLeft: index, bottomRight: sibling);
421
422 d->toBeRefreshed = index.parent();
423 QMetaObject::invokeMethod(obj: this, member: "_q_refresh", type: Qt::QueuedConnection);
424
425 return true;
426 }
427
428 return false;
429}
430
431/*!
432 Returns the data stored under the given \a role for the specified \a section
433 of the header with the given \a orientation.
434*/
435
436QVariant QDirModel::headerData(int section, Qt::Orientation orientation, int role) const
437{
438 if (orientation == Qt::Horizontal) {
439 if (role != Qt::DisplayRole)
440 return QVariant();
441 switch (section) {
442 case 0: return tr(s: "Name");
443 case 1: return tr(s: "Size");
444 case 2: return
445#ifdef Q_OS_MAC
446 tr("Kind", "Match OS X Finder");
447#else
448 tr(s: "Type", c: "All other platforms");
449#endif
450 // Windows - Type
451 // OS X - Kind
452 // Konqueror - File Type
453 // Nautilus - Type
454 case 3: return tr(s: "Date Modified");
455 default: return QVariant();
456 }
457 }
458 return QAbstractItemModel::headerData(section, orientation, role);
459}
460
461/*!
462 Returns \c true if the \a parent model item has children; otherwise
463 returns \c false.
464*/
465
466bool QDirModel::hasChildren(const QModelIndex &parent) const
467{
468 Q_D(const QDirModel);
469 if (parent.column() > 0)
470 return false;
471
472 if (!parent.isValid()) // the invalid index is the "My Computer" item
473 return true; // the drives
474 QDirModelPrivate::QDirNode *p = d->node(index: parent);
475 Q_ASSERT(p);
476
477 if (d->lazyChildCount) // optimization that only checks for children if the node has been populated
478 return p->info.isDir();
479 return p->info.isDir() && rowCount(parent) > 0;
480}
481
482/*!
483 Returns the item flags for the given \a index in the model.
484
485 \sa Qt::ItemFlags
486*/
487Qt::ItemFlags QDirModel::flags(const QModelIndex &index) const
488{
489 Q_D(const QDirModel);
490 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
491 if (!d->indexValid(index))
492 return flags;
493 flags |= Qt::ItemIsDragEnabled;
494 if (d->readOnly)
495 return flags;
496 QDirModelPrivate::QDirNode *node = d->node(index);
497 if ((index.column() == 0) && node->info.isWritable()) {
498 flags |= Qt::ItemIsEditable;
499 if (fileInfo(index).isDir()) // is directory and is editable
500 flags |= Qt::ItemIsDropEnabled;
501 }
502 return flags;
503}
504
505/*!
506 Sort the model items in the \a column using the \a order given.
507 The order is a value defined in \l Qt::SortOrder.
508*/
509
510void QDirModel::sort(int column, Qt::SortOrder order)
511{
512 QDir::SortFlags sort = QDir::DirsFirst | QDir::IgnoreCase;
513 if (order == Qt::DescendingOrder)
514 sort |= QDir::Reversed;
515
516 switch (column) {
517 case 0:
518 sort |= QDir::Name;
519 break;
520 case 1:
521 sort |= QDir::Size;
522 break;
523 case 2:
524 sort |= QDir::Type;
525 break;
526 case 3:
527 sort |= QDir::Time;
528 break;
529 default:
530 break;
531 }
532
533 setSorting(sort);
534}
535
536/*!
537 Returns a list of MIME types that can be used to describe a list of items
538 in the model.
539*/
540
541QStringList QDirModel::mimeTypes() const
542{
543 return QStringList(QLatin1String("text/uri-list"));
544}
545
546/*!
547 Returns an object that contains a serialized description of the specified
548 \a indexes. The format used to describe the items corresponding to the
549 indexes is obtained from the mimeTypes() function.
550
551 If the list of indexes is empty, 0 is returned rather than a serialized
552 empty list.
553*/
554
555QMimeData *QDirModel::mimeData(const QModelIndexList &indexes) const
556{
557 QList<QUrl> urls;
558 QList<QModelIndex>::const_iterator it = indexes.begin();
559 for (; it != indexes.end(); ++it)
560 if ((*it).column() == 0)
561 urls << QUrl::fromLocalFile(localfile: filePath(index: *it));
562 QMimeData *data = new QMimeData();
563 data->setUrls(urls);
564 return data;
565}
566
567/*!
568 Handles the \a data supplied by a drag and drop operation that ended with
569 the given \a action over the row in the model specified by the \a row and
570 \a column and by the \a parent index.
571
572 Returns \c true if the drop was successful, and false otherwise.
573
574 \sa supportedDropActions()
575*/
576
577bool QDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
578 int /* row */, int /* column */, const QModelIndex &parent)
579{
580 Q_D(QDirModel);
581 if (!d->indexValid(index: parent) || isReadOnly())
582 return false;
583
584 bool success = true;
585 QString to = filePath(index: parent) + QDir::separator();
586 QModelIndex _parent = parent;
587
588 QList<QUrl> urls = data->urls();
589 QList<QUrl>::const_iterator it = urls.constBegin();
590
591 switch (action) {
592 case Qt::CopyAction:
593 for (; it != urls.constEnd(); ++it) {
594 QString path = (*it).toLocalFile();
595 success = QFile::copy(fileName: path, newName: to + QFileInfo(path).fileName()) && success;
596 }
597 break;
598 case Qt::LinkAction:
599 for (; it != urls.constEnd(); ++it) {
600 QString path = (*it).toLocalFile();
601 success = QFile::link(oldname: path, newName: to + QFileInfo(path).fileName()) && success;
602 }
603 break;
604 case Qt::MoveAction:
605 for (; it != urls.constEnd(); ++it) {
606 QString path = (*it).toLocalFile();
607 if (QFile::copy(fileName: path, newName: to + QFileInfo(path).fileName())
608 && QFile::remove(fileName: path)) {
609 QModelIndex idx=index(path: QFileInfo(path).path());
610 if (idx.isValid()) {
611 refresh(parent: idx);
612 //the previous call to refresh may invalidate the _parent. so recreate a new QModelIndex
613 _parent = index(path: to);
614 }
615 } else {
616 success = false;
617 }
618 }
619 break;
620 default:
621 return false;
622 }
623
624 if (success)
625 refresh(parent: _parent);
626
627 return success;
628}
629
630/*!
631 Returns the drop actions supported by this model.
632
633 \sa Qt::DropActions
634*/
635
636Qt::DropActions QDirModel::supportedDropActions() const
637{
638 return Qt::CopyAction | Qt::MoveAction; // FIXME: LinkAction is not supported yet
639}
640
641/*!
642 Sets the \a provider of file icons for the directory model.
643
644*/
645
646void QDirModel::setIconProvider(QFileIconProvider *provider)
647{
648 Q_D(QDirModel);
649 d->iconProvider = provider;
650}
651
652/*!
653 Returns the file icon provider for this directory model.
654*/
655
656QFileIconProvider *QDirModel::iconProvider() const
657{
658 Q_D(const QDirModel);
659 return d->iconProvider;
660}
661
662/*!
663 Sets the name \a filters for the directory model.
664*/
665
666void QDirModel::setNameFilters(const QStringList &filters)
667{
668 Q_D(QDirModel);
669 d->nameFilters = filters;
670 emit layoutAboutToBeChanged();
671 if (d->shouldStat)
672 refresh(parent: QModelIndex());
673 else
674 d->invalidate();
675 emit layoutChanged();
676}
677
678/*!
679 Returns a list of filters applied to the names in the model.
680*/
681
682QStringList QDirModel::nameFilters() const
683{
684 Q_D(const QDirModel);
685 return d->nameFilters;
686}
687
688/*!
689 Sets the directory model's filter to that specified by \a filters.
690
691 Note that the filter you set should always include the QDir::AllDirs enum value,
692 otherwise QDirModel won't be able to read the directory structure.
693
694 \sa QDir::Filters
695*/
696
697void QDirModel::setFilter(QDir::Filters filters)
698{
699 Q_D(QDirModel);
700 d->filters = filters;
701 emit layoutAboutToBeChanged();
702 if (d->shouldStat)
703 refresh(parent: QModelIndex());
704 else
705 d->invalidate();
706 emit layoutChanged();
707}
708
709/*!
710 Returns the filter specification for the directory model.
711
712 \sa QDir::Filters
713*/
714
715QDir::Filters QDirModel::filter() const
716{
717 Q_D(const QDirModel);
718 return d->filters;
719}
720
721/*!
722 Sets the directory model's sorting order to that specified by \a sort.
723
724 \sa QDir::SortFlags
725*/
726
727void QDirModel::setSorting(QDir::SortFlags sort)
728{
729 Q_D(QDirModel);
730 d->sort = sort;
731 emit layoutAboutToBeChanged();
732 if (d->shouldStat)
733 refresh(parent: QModelIndex());
734 else
735 d->invalidate();
736 emit layoutChanged();
737}
738
739/*!
740 Returns the sorting method used for the directory model.
741
742 \sa QDir::SortFlags
743*/
744
745QDir::SortFlags QDirModel::sorting() const
746{
747 Q_D(const QDirModel);
748 return d->sort;
749}
750
751/*!
752 \property QDirModel::resolveSymlinks
753 \brief Whether the directory model should resolve symbolic links
754
755 This is only relevant on operating systems that support symbolic
756 links.
757*/
758void QDirModel::setResolveSymlinks(bool enable)
759{
760 Q_D(QDirModel);
761 d->resolveSymlinks = enable;
762}
763
764bool QDirModel::resolveSymlinks() const
765{
766 Q_D(const QDirModel);
767 return d->resolveSymlinks;
768}
769
770/*!
771 \property QDirModel::readOnly
772 \brief Whether the directory model allows writing to the file system
773
774 If this property is set to false, the directory model will allow renaming, copying
775 and deleting of files and directories.
776
777 This property is \c true by default
778*/
779
780void QDirModel::setReadOnly(bool enable)
781{
782 Q_D(QDirModel);
783 d->readOnly = enable;
784}
785
786bool QDirModel::isReadOnly() const
787{
788 Q_D(const QDirModel);
789 return d->readOnly;
790}
791
792/*!
793 \property QDirModel::lazyChildCount
794 \brief Whether the directory model optimizes the hasChildren function
795 to only check if the item is a directory.
796
797 If this property is set to false, the directory model will make sure that a directory
798 actually containes any files before reporting that it has children.
799 Otherwise the directory model will report that an item has children if the item
800 is a directory.
801
802 This property is \c false by default
803*/
804
805void QDirModel::setLazyChildCount(bool enable)
806{
807 Q_D(QDirModel);
808 d->lazyChildCount = enable;
809}
810
811bool QDirModel::lazyChildCount() const
812{
813 Q_D(const QDirModel);
814 return d->lazyChildCount;
815}
816
817/*!
818 QDirModel caches file information. This function updates the
819 cache. The \a parent parameter is the directory from which the
820 model is updated; the default value will update the model from
821 root directory of the file system (the entire model).
822*/
823
824void QDirModel::refresh(const QModelIndex &parent)
825{
826 Q_D(QDirModel);
827
828 QDirModelPrivate::QDirNode *n = d->indexValid(index: parent) ? d->node(index: parent) : &(d->root);
829
830 int rows = n->children.count();
831 if (rows == 0) {
832 emit layoutAboutToBeChanged();
833 n->stat = true; // make sure that next time we read all the info
834 n->populated = false;
835 emit layoutChanged();
836 return;
837 }
838
839 emit layoutAboutToBeChanged();
840 d->savePersistentIndexes();
841 d->rowsAboutToBeRemoved(parent, first: 0, last: rows - 1);
842 n->stat = true; // make sure that next time we read all the info
843 d->clear(parent: n);
844 d->rowsRemoved(parent, first: 0, last: rows - 1);
845 d->restorePersistentIndexes();
846 emit layoutChanged();
847}
848
849/*!
850 \overload
851
852 Returns the model item index for the given \a path.
853*/
854
855QModelIndex QDirModel::index(const QString &path, int column) const
856{
857 Q_D(const QDirModel);
858
859 if (path.isEmpty() || path == QCoreApplication::translate(context: "QFileDialog", key: "My Computer"))
860 return QModelIndex();
861
862 QString absolutePath = QDir(path).absolutePath();
863#if defined(Q_OS_WIN)
864 absolutePath = absolutePath.toLower();
865 // On Windows, "filename......." and "filename" are equivalent
866 if (absolutePath.endsWith(QLatin1Char('.'))) {
867 int i;
868 for (i = absolutePath.count() - 1; i >= 0; --i) {
869 if (absolutePath.at(i) != QLatin1Char('.'))
870 break;
871 }
872 absolutePath = absolutePath.left(i+1);
873 }
874#endif
875
876 QStringList pathElements = absolutePath.split(sep: QLatin1Char('/'), behavior: Qt::SkipEmptyParts);
877 if ((pathElements.isEmpty() || !QFileInfo::exists(file: path))
878#if !defined(Q_OS_WIN)
879 && path != QLatin1String("/")
880#endif
881 )
882 return QModelIndex();
883
884 QModelIndex idx; // start with "My Computer"
885 if (!d->root.populated) // make sure the root is populated
886 d->populate(parent: &d->root);
887
888#if defined(Q_OS_WIN)
889 if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path
890 QString host = pathElements.constFirst();
891 int r = 0;
892 for (; r < d->root.children.count(); ++r)
893 if (d->root.children.at(r).info.fileName() == host)
894 break;
895 bool childAppended = false;
896 if (r >= d->root.children.count() && d->allowAppendChild) {
897 d->appendChild(&d->root, QLatin1String("//") + host);
898 childAppended = true;
899 }
900 idx = index(r, 0, QModelIndex());
901 pathElements.pop_front();
902 if (childAppended)
903 emit const_cast<QDirModel*>(this)->layoutChanged();
904 } else
905#endif
906#if defined(Q_OS_WIN)
907 if (pathElements.at(0).endsWith(QLatin1Char(':'))) {
908 pathElements[0] += QLatin1Char('/');
909 }
910#else
911 // add the "/" item, since it is a valid path element on unix
912 pathElements.prepend(t: QLatin1String("/"));
913#endif
914
915 for (int i = 0; i < pathElements.count(); ++i) {
916 Q_ASSERT(!pathElements.at(i).isEmpty());
917 QString element = pathElements.at(i);
918 QDirModelPrivate::QDirNode *parent = (idx.isValid() ? d->node(index: idx) : &d->root);
919
920 Q_ASSERT(parent);
921 if (!parent->populated)
922 d->populate(parent);
923
924 // search for the element in the child nodes first
925 int row = -1;
926 for (int j = parent->children.count() - 1; j >= 0; --j) {
927 const QFileInfo& fi = parent->children.at(i: j).info;
928 QString childFileName;
929 childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath();
930#if defined(Q_OS_WIN)
931 childFileName = childFileName.toLower();
932#endif
933 if (childFileName == element) {
934 if (i == pathElements.count() - 1)
935 parent->children[j].stat = true;
936 row = j;
937 break;
938 }
939 }
940
941 // we couldn't find the path element, we create a new node since we _know_ that the path is valid
942 if (row == -1) {
943 QString newPath = parent->info.absoluteFilePath() + QLatin1Char('/') + element;
944 if (!d->allowAppendChild || !QFileInfo(newPath).isDir())
945 return QModelIndex();
946 d->appendChild(parent, path: newPath);
947 row = parent->children.count() - 1;
948 if (i == pathElements.count() - 1) // always stat children of the last element
949 parent->children[row].stat = true;
950 emit const_cast<QDirModel*>(this)->layoutChanged();
951 }
952
953 Q_ASSERT(row >= 0);
954 idx = createIndex(arow: row, acolumn: 0, adata: static_cast<void*>(&parent->children[row]));
955 Q_ASSERT(idx.isValid());
956 }
957
958 if (column != 0)
959 return idx.sibling(arow: idx.row(), acolumn: column);
960 return idx;
961}
962
963/*!
964 Returns \c true if the model item \a index represents a directory;
965 otherwise returns \c false.
966*/
967
968bool QDirModel::isDir(const QModelIndex &index) const
969{
970 Q_D(const QDirModel);
971 Q_ASSERT(d->indexValid(index));
972 QDirModelPrivate::QDirNode *node = d->node(index);
973 return node->info.isDir();
974}
975
976/*!
977 Create a directory with the \a name in the \a parent model item.
978*/
979
980QModelIndex QDirModel::mkdir(const QModelIndex &parent, const QString &name)
981{
982 Q_D(QDirModel);
983 if (!d->indexValid(index: parent) || isReadOnly())
984 return QModelIndex();
985
986 QDirModelPrivate::QDirNode *p = d->node(index: parent);
987 QString path = p->info.absoluteFilePath();
988 // For the indexOf() method to work, the new directory has to be a direct child of
989 // the parent directory.
990
991 QDir newDir(name);
992 QDir dir(path);
993 if (newDir.isRelative())
994 newDir = QDir(path + QLatin1Char('/') + name);
995 QString childName = newDir.dirName(); // Get the singular name of the directory
996 newDir.cdUp();
997
998 if (newDir.absolutePath() != dir.absolutePath() || !dir.mkdir(dirName: name))
999 return QModelIndex(); // nothing happened
1000
1001 refresh(parent);
1002
1003 QStringList entryList = d->entryList(path);
1004 int r = entryList.indexOf(t: childName);
1005 QModelIndex i = index(row: r, column: 0, parent); // return an invalid index
1006
1007 return i;
1008}
1009
1010/*!
1011 Removes the directory corresponding to the model item \a index in the
1012 directory model and \b{deletes the corresponding directory from the
1013 file system}, returning true if successful. If the directory cannot be
1014 removed, false is returned.
1015
1016 \warning This function deletes directories from the file system; it does
1017 \b{not} move them to a location where they can be recovered.
1018
1019 \sa remove()
1020*/
1021
1022bool QDirModel::rmdir(const QModelIndex &index)
1023{
1024 Q_D(QDirModel);
1025 if (!d->indexValid(index) || isReadOnly())
1026 return false;
1027
1028 QDirModelPrivate::QDirNode *n = d_func()->node(index);
1029 if (Q_UNLIKELY(!n->info.isDir())) {
1030 qWarning(msg: "rmdir: the node is not a directory");
1031 return false;
1032 }
1033
1034 QModelIndex par = parent(child: index);
1035 QDirModelPrivate::QDirNode *p = d_func()->node(index: par);
1036 QDir dir = p->info.dir(); // parent dir
1037 QString path = n->info.absoluteFilePath();
1038 if (!dir.rmdir(dirName: path))
1039 return false;
1040
1041 refresh(parent: par);
1042
1043 return true;
1044}
1045
1046/*!
1047 Removes the model item \a index from the directory model and \b{deletes the
1048 corresponding file from the file system}, returning true if successful. If the
1049 item cannot be removed, false is returned.
1050
1051 \warning This function deletes files from the file system; it does \b{not}
1052 move them to a location where they can be recovered.
1053
1054 \sa rmdir()
1055*/
1056
1057bool QDirModel::remove(const QModelIndex &index)
1058{
1059 Q_D(QDirModel);
1060 if (!d->indexValid(index) || isReadOnly())
1061 return false;
1062
1063 QDirModelPrivate::QDirNode *n = d_func()->node(index);
1064 if (n->info.isDir())
1065 return false;
1066
1067 QModelIndex par = parent(child: index);
1068 QDirModelPrivate::QDirNode *p = d_func()->node(index: par);
1069 QDir dir = p->info.dir(); // parent dir
1070 QString path = n->info.absoluteFilePath();
1071 if (!dir.remove(fileName: path))
1072 return false;
1073
1074 refresh(parent: par);
1075
1076 return true;
1077}
1078
1079/*!
1080 Returns the path of the item stored in the model under the
1081 \a index given.
1082
1083*/
1084
1085QString QDirModel::filePath(const QModelIndex &index) const
1086{
1087 Q_D(const QDirModel);
1088 if (d->indexValid(index)) {
1089 QFileInfo fi = fileInfo(index);
1090 if (d->resolveSymlinks && fi.isSymLink())
1091 fi = QDirModelPrivate::resolvedInfo(info: fi);
1092 return QDir::cleanPath(path: fi.absoluteFilePath());
1093 }
1094 return QString(); // root path
1095}
1096
1097/*!
1098 Returns the name of the item stored in the model under the
1099 \a index given.
1100
1101*/
1102
1103QString QDirModel::fileName(const QModelIndex &index) const
1104{
1105 Q_D(const QDirModel);
1106 if (!d->indexValid(index))
1107 return QString();
1108 QFileInfo info = fileInfo(index);
1109 const QString &path = info.absoluteFilePath();
1110 if (QFileSystemEntry::isRootPath(path))
1111 return path;
1112 if (d->resolveSymlinks && info.isSymLink())
1113 info = QDirModelPrivate::resolvedInfo(info);
1114 return info.fileName();
1115}
1116
1117/*!
1118 Returns the icons for the item stored in the model under the given
1119 \a index.
1120*/
1121
1122QIcon QDirModel::fileIcon(const QModelIndex &index) const
1123{
1124 Q_D(const QDirModel);
1125 if (!d->indexValid(index))
1126 return d->iconProvider->icon(type: QFileIconProvider::Computer);
1127 QDirModelPrivate::QDirNode *node = d->node(index);
1128 if (node->icon.isNull())
1129 node->icon = d->iconProvider->icon(info: node->info);
1130 return node->icon;
1131}
1132
1133/*!
1134 Returns the file information for the specified model \a index.
1135
1136 \b{Note:} If the model index represents a symbolic link in the
1137 underlying filing system, the file information returned will contain
1138 information about the symbolic link itself, regardless of whether
1139 resolveSymlinks is enabled or not.
1140
1141 \sa QFileInfo::symLinkTarget()
1142*/
1143
1144QFileInfo QDirModel::fileInfo(const QModelIndex &index) const
1145{
1146 Q_D(const QDirModel);
1147 Q_ASSERT(d->indexValid(index));
1148
1149 QDirModelPrivate::QDirNode *node = d->node(index);
1150 return node->info;
1151}
1152
1153/*
1154 The root node is never seen outside the model.
1155*/
1156
1157void QDirModelPrivate::init()
1158{
1159 filters = QDir::AllEntries | QDir::NoDotAndDotDot;
1160 sort = QDir::Name;
1161 nameFilters << QLatin1String("*");
1162 root.parent = nullptr;
1163 root.info = QFileInfo();
1164 clear(parent: &root);
1165 roleNames.insert(key: QDirModel::FileIconRole, QByteArrayLiteral("fileIcon")); // == Qt::decoration
1166 roleNames.insert(key: QDirModel::FilePathRole, QByteArrayLiteral("filePath"));
1167 roleNames.insert(key: QDirModel::FileNameRole, QByteArrayLiteral("fileName"));
1168}
1169
1170QDirModelPrivate::QDirNode *QDirModelPrivate::node(int row, QDirNode *parent) const
1171{
1172 if (row < 0)
1173 return nullptr;
1174
1175 bool isDir = !parent || parent->info.isDir();
1176 QDirNode *p = (parent ? parent : &root);
1177 if (isDir && !p->populated)
1178 populate(parent: p); // will also resolve symlinks
1179
1180 if (Q_UNLIKELY(row >= p->children.count())) {
1181 qWarning(msg: "node: the row does not exist");
1182 return nullptr;
1183 }
1184
1185 return const_cast<QDirNode*>(&p->children.at(i: row));
1186}
1187
1188QVector<QDirModelPrivate::QDirNode> QDirModelPrivate::children(QDirNode *parent, bool stat) const
1189{
1190 Q_ASSERT(parent);
1191 QFileInfoList infoList;
1192 if (parent == &root) {
1193 parent = nullptr;
1194 infoList = QDir::drives();
1195 } else if (parent->info.isDir()) {
1196 //resolve directory links only if requested.
1197 if (parent->info.isSymLink() && resolveSymlinks) {
1198 QString link = parent->info.symLinkTarget();
1199 if (link.size() > 1 && link.at(i: link.size() - 1) == QDir::separator())
1200 link.chop(n: 1);
1201 if (stat)
1202 infoList = entryInfoList(path: link);
1203 else
1204 infoList = QDir(link).entryInfoList(nameFilters, filters: QDir::AllEntries | QDir::System);
1205 } else {
1206 if (stat)
1207 infoList = entryInfoList(path: parent->info.absoluteFilePath());
1208 else
1209 infoList = QDir(parent->info.absoluteFilePath()).entryInfoList(nameFilters, filters: QDir::AllEntries | QDir::System);
1210 }
1211 }
1212
1213 QVector<QDirNode> nodes(infoList.count());
1214 for (int i = 0; i < infoList.count(); ++i) {
1215 QDirNode &node = nodes[i];
1216 node.parent = parent;
1217 node.info = infoList.at(i);
1218 node.populated = false;
1219 node.stat = shouldStat;
1220 }
1221
1222 return nodes;
1223}
1224
1225void QDirModelPrivate::_q_refresh()
1226{
1227 Q_Q(QDirModel);
1228 q->refresh(parent: toBeRefreshed);
1229 toBeRefreshed = QModelIndex();
1230}
1231
1232void QDirModelPrivate::savePersistentIndexes()
1233{
1234 Q_Q(QDirModel);
1235 savedPersistent.clear();
1236 savedPersistent.reserve(size: persistent.indexes.size());
1237 foreach (QPersistentModelIndexData *data, persistent.indexes) {
1238 QModelIndex index = data->index;
1239 SavedPersistent saved = {
1240 .path: q->filePath(index),
1241 .column: index.column(),
1242 .data: data,
1243 .index: index,
1244 };
1245 savedPersistent.push_back(t: std::move(saved));
1246 }
1247}
1248
1249void QDirModelPrivate::restorePersistentIndexes()
1250{
1251 Q_Q(QDirModel);
1252 bool allow = allowAppendChild;
1253 allowAppendChild = false;
1254 for (const SavedPersistent &sp : qAsConst(t&: savedPersistent)) {
1255 QPersistentModelIndexData *data = sp.data;
1256 QModelIndex idx = q->index(path: sp.path, column: sp.column);
1257 if (idx != data->index || data->index.model() == nullptr) {
1258 //data->model may be equal to 0 if the model is getting destroyed
1259 persistent.indexes.remove(key: data->index);
1260 data->index = idx;
1261 if (idx.isValid())
1262 persistent.indexes.insert(key: idx, value: data);
1263 }
1264 }
1265 savedPersistent.clear();
1266 allowAppendChild = allow;
1267}
1268
1269QFileInfoList QDirModelPrivate::entryInfoList(const QString &path) const
1270{
1271 const QDir dir(path);
1272 return dir.entryInfoList(nameFilters, filters, sort);
1273}
1274
1275QStringList QDirModelPrivate::entryList(const QString &path) const
1276{
1277 const QDir dir(path);
1278 return dir.entryList(nameFilters, filters, sort);
1279}
1280
1281QString QDirModelPrivate::name(const QModelIndex &index) const
1282{
1283 const QDirNode *n = node(index);
1284 const QFileInfo info = n->info;
1285 QString name = info.absoluteFilePath();
1286 if (QFileSystemEntry::isRootPath(path: name)) {
1287#if defined(Q_OS_WIN)
1288 if (name.startsWith(QLatin1Char('/'))) // UNC host
1289 return info.fileName();
1290 if (name.endsWith(QLatin1Char('/')))
1291 name.chop(1);
1292#endif
1293 return name;
1294 }
1295 return info.fileName();
1296}
1297
1298QString QDirModelPrivate::size(const QModelIndex &index) const
1299{
1300 const QDirNode *n = node(index);
1301 if (n->info.isDir()) {
1302#ifdef Q_OS_MAC
1303 return QLatin1String("--");
1304#else
1305 return QLatin1String("");
1306#endif
1307 // Windows - ""
1308 // OS X - "--"
1309 // Konqueror - "4 KB"
1310 // Nautilus - "9 items" (the number of children)
1311 }
1312
1313 return QLocale::system().formattedDataSize(bytes: n->info.size());
1314}
1315
1316QString QDirModelPrivate::type(const QModelIndex &index) const
1317{
1318 return iconProvider->type(info: node(index)->info);
1319}
1320
1321QString QDirModelPrivate::time(const QModelIndex &index) const
1322{
1323#if QT_CONFIG(datestring)
1324 return QLocale::system().toString(dateTime: node(index)->info.lastModified(), format: QLocale::ShortFormat);
1325#else
1326 Q_UNUSED(index);
1327 return QString();
1328#endif
1329}
1330
1331void QDirModelPrivate::appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const
1332{
1333 QDirModelPrivate::QDirNode node;
1334 node.populated = false;
1335 node.stat = shouldStat;
1336 node.parent = (parent == &root ? nullptr : parent);
1337 node.info = QFileInfo(path);
1338 node.info.setCaching(true);
1339
1340 // The following append(node) may reallocate the vector, thus
1341 // we need to update the pointers to the childnodes parent.
1342 QDirModelPrivate *that = const_cast<QDirModelPrivate *>(this);
1343 that->savePersistentIndexes();
1344 parent->children.append(t: node);
1345 for (int i = 0; i < parent->children.count(); ++i) {
1346 QDirNode *childNode = &parent->children[i];
1347 for (int j = 0; j < childNode->children.count(); ++j)
1348 childNode->children[j].parent = childNode;
1349 }
1350 that->restorePersistentIndexes();
1351}
1352
1353QFileInfo QDirModelPrivate::resolvedInfo(QFileInfo info)
1354{
1355#ifdef Q_OS_WIN
1356 // On windows, we cannot create a shortcut to a shortcut.
1357 return QFileInfo(info.symLinkTarget());
1358#else
1359 QStringList paths;
1360 do {
1361 QFileInfo link(info.symLinkTarget());
1362 if (link.isRelative())
1363 info.setFile(dir: info.absolutePath(), file: link.filePath());
1364 else
1365 info = link;
1366 if (paths.contains(str: info.absoluteFilePath()))
1367 return QFileInfo();
1368 paths.append(t: info.absoluteFilePath());
1369 } while (info.isSymLink());
1370 return info;
1371#endif
1372}
1373
1374QT_END_NAMESPACE
1375
1376#include "moc_qdirmodel.cpp"
1377
1378#endif // QT_DEPRECATED_SINCE(5, 15)
1379

source code of qtbase/src/widgets/itemviews/qdirmodel.cpp