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 #ifndef QFILESYSTEMMODEL_P_H
41 #define QFILESYSTEMMODEL_P_H
42
43 //
44 // W A R N I N G
45 // -------------
46 //
47 // This file is not part of the Qt API. It exists purely as an
48 // implementation detail. This header file may change from version to
49 // version without notice, or even be removed.
50 //
51 // We mean it.
52 //
53
54 #include <QtWidgets/private/qtwidgetsglobal_p.h>
55 #include "qfilesystemmodel.h"
56
57 #include <private/qabstractitemmodel_p.h>
58 #include <qabstractitemmodel.h>
59 #include "qfileinfogatherer_p.h"
60 #include <qpair.h>
61 #include <qdir.h>
62 #include <qicon.h>
63 #include <qfileinfo.h>
64 #include <qtimer.h>
65 #include <qhash.h>
66
67 QT_REQUIRE_CONFIG(filesystemmodel);
68
69 QT_BEGIN_NAMESPACE
70
71 class ExtendedInformation;
72 class QFileSystemModelPrivate;
73 class QFileIconProvider;
74
75 #if defined(Q_OS_WIN)
76 class QFileSystemModelNodePathKey : public QString
77 {
78 public:
QFileSystemModelNodePathKey()79 QFileSystemModelNodePathKey() {}
QFileSystemModelNodePathKey(const QString & other)80 QFileSystemModelNodePathKey(const QString &other) : QString(other) {}
QFileSystemModelNodePathKey(const QFileSystemModelNodePathKey & other)81 QFileSystemModelNodePathKey(const QFileSystemModelNodePathKey &other) : QString(other) {}
82 bool operator==(const QFileSystemModelNodePathKey &other) const { return !compare(other, Qt::CaseInsensitive); }
83 };
84
85 Q_DECLARE_TYPEINFO(QFileSystemModelNodePathKey, Q_MOVABLE_TYPE);
86
qHash(const QFileSystemModelNodePathKey & key)87 inline uint qHash(const QFileSystemModelNodePathKey &key) { return qHash(key.toCaseFolded()); }
88 #else // Q_OS_WIN
89 typedef QString QFileSystemModelNodePathKey;
90 #endif
91
92 class Q_AUTOTEST_EXPORT QFileSystemModelPrivate : public QAbstractItemModelPrivate
93 {
94 Q_DECLARE_PUBLIC(QFileSystemModel)
95
96 public:
97 enum { NumColumns = 4 };
98
99 class QFileSystemNode
100 {
101 public:
Q_DISABLE_COPY_MOVE(QFileSystemNode)102 Q_DISABLE_COPY_MOVE(QFileSystemNode)
103
104 explicit QFileSystemNode(const QString &filename = QString(), QFileSystemNode *p = nullptr)
105 : fileName(filename), parent(p) {}
~QFileSystemNode()106 ~QFileSystemNode() {
107 qDeleteAll(children);
108 delete info;
109 }
110
111 QString fileName;
112 #if defined(Q_OS_WIN)
113 QString volumeName;
114 #endif
115
size()116 inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; }
type()117 inline QString type() const { if (info) return info->displayType; return QLatin1String(""); }
lastModified()118 inline QDateTime lastModified() const { if (info) return info->lastModified(); return QDateTime(); }
permissions()119 inline QFile::Permissions permissions() const { if (info) return info->permissions(); return { }; }
isReadable()120 inline bool isReadable() const { return ((permissions() & QFile::ReadUser) != 0); }
isWritable()121 inline bool isWritable() const { return ((permissions() & QFile::WriteUser) != 0); }
isExecutable()122 inline bool isExecutable() const { return ((permissions() & QFile::ExeUser) != 0); }
isDir()123 inline bool isDir() const {
124 if (info)
125 return info->isDir();
126 if (children.count() > 0)
127 return true;
128 return false;
129 }
fileInfo()130 inline QFileInfo fileInfo() const { if (info) return info->fileInfo(); return QFileInfo(); }
isFile()131 inline bool isFile() const { if (info) return info->isFile(); return true; }
isSystem()132 inline bool isSystem() const { if (info) return info->isSystem(); return true; }
isHidden()133 inline bool isHidden() const { if (info) return info->isHidden(); return false; }
134 inline bool isSymLink(bool ignoreNtfsSymLinks = false) const { return info && info->isSymLink(ignoreNtfsSymLinks); }
caseSensitive()135 inline bool caseSensitive() const { if (info) return info->isCaseSensitive(); return false; }
icon()136 inline QIcon icon() const { if (info) return info->icon; return QIcon(); }
137
138 inline bool operator <(const QFileSystemNode &node) const {
139 if (caseSensitive() || node.caseSensitive())
140 return fileName < node.fileName;
141 return QString::compare(fileName, node.fileName, Qt::CaseInsensitive) < 0;
142 }
143 inline bool operator >(const QString &name) const {
144 if (caseSensitive())
145 return fileName > name;
146 return QString::compare(fileName, name, Qt::CaseInsensitive) > 0;
147 }
148 inline bool operator <(const QString &name) const {
149 if (caseSensitive())
150 return fileName < name;
151 return QString::compare(fileName, name, Qt::CaseInsensitive) < 0;
152 }
153 inline bool operator !=(const QExtendedInformation &fileInfo) const {
154 return !operator==(fileInfo);
155 }
156 bool operator ==(const QString &name) const {
157 if (caseSensitive())
158 return fileName == name;
159 return QString::compare(fileName, name, Qt::CaseInsensitive) == 0;
160 }
161 bool operator ==(const QExtendedInformation &fileInfo) const {
162 return info && (*info == fileInfo);
163 }
164
hasInformation()165 inline bool hasInformation() const { return info != nullptr; }
166
populate(const QExtendedInformation & fileInfo)167 void populate(const QExtendedInformation &fileInfo) {
168 if (!info)
169 info = new QExtendedInformation(fileInfo.fileInfo());
170 (*info) = fileInfo;
171 }
172
173 // children shouldn't normally be accessed directly, use node()
visibleLocation(const QString & childName)174 inline int visibleLocation(const QString &childName) {
175 return visibleChildren.indexOf(childName);
176 }
updateIcon(QFileIconProvider * iconProvider,const QString & path)177 void updateIcon(QFileIconProvider *iconProvider, const QString &path) {
178 if (info)
179 info->icon = iconProvider->icon(QFileInfo(path));
180 for (QFileSystemNode *child : qAsConst(children)) {
181 //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/)
182 if (!path.isEmpty()) {
183 if (path.endsWith(QLatin1Char('/')))
184 child->updateIcon(iconProvider, path + child->fileName);
185 else
186 child->updateIcon(iconProvider, path + QLatin1Char('/') + child->fileName);
187 } else
188 child->updateIcon(iconProvider, child->fileName);
189 }
190 }
191
retranslateStrings(QFileIconProvider * iconProvider,const QString & path)192 void retranslateStrings(QFileIconProvider *iconProvider, const QString &path) {
193 if (info)
194 info->displayType = iconProvider->type(QFileInfo(path));
195 for (QFileSystemNode *child : qAsConst(children)) {
196 //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/)
197 if (!path.isEmpty()) {
198 if (path.endsWith(QLatin1Char('/')))
199 child->retranslateStrings(iconProvider, path + child->fileName);
200 else
201 child->retranslateStrings(iconProvider, path + QLatin1Char('/') + child->fileName);
202 } else
203 child->retranslateStrings(iconProvider, child->fileName);
204 }
205 }
206
207 QHash<QFileSystemModelNodePathKey, QFileSystemNode *> children;
208 QList<QString> visibleChildren;
209 QExtendedInformation *info = nullptr;
210 QFileSystemNode *parent;
211 int dirtyChildrenIndex = -1;
212 bool populatedChildren = false;
213 bool isVisible = false;
214 };
215
216 QFileSystemModelPrivate() = default;
217 void init();
218 /*
219 \internal
220
221 Return true if index which is owned by node is hidden by the filter.
222 */
isHiddenByFilter(QFileSystemNode * indexNode,const QModelIndex & index)223 inline bool isHiddenByFilter(QFileSystemNode *indexNode, const QModelIndex &index) const
224 {
225 return (indexNode != &root && !index.isValid());
226 }
227 QFileSystemNode *node(const QModelIndex &index) const;
228 QFileSystemNode *node(const QString &path, bool fetch = true) const;
229 inline QModelIndex index(const QString &path, int column = 0) { return index(node(path), column); }
230 QModelIndex index(const QFileSystemNode *node, int column = 0) const;
231 bool filtersAcceptsNode(const QFileSystemNode *node) const;
232 bool passNameFilters(const QFileSystemNode *node) const;
233 void removeNode(QFileSystemNode *parentNode, const QString &name);
234 QFileSystemNode* addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo &info);
235 void addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles);
236 void removeVisibleFile(QFileSystemNode *parentNode, int visibleLocation);
237 void sortChildren(int column, const QModelIndex &parent);
238
translateVisibleLocation(QFileSystemNode * parent,int row)239 inline int translateVisibleLocation(QFileSystemNode *parent, int row) const {
240 if (sortOrder != Qt::AscendingOrder) {
241 if (parent->dirtyChildrenIndex == -1)
242 return parent->visibleChildren.count() - row - 1;
243
244 if (row < parent->dirtyChildrenIndex)
245 return parent->dirtyChildrenIndex - row - 1;
246 }
247
248 return row;
249 }
250
myComputer()251 inline static QString myComputer() {
252 // ### TODO We should query the system to find out what the string should be
253 // XP == "My Computer",
254 // Vista == "Computer",
255 // OS X == "Computer" (sometime user generated) "Benjamin's PowerBook G4"
256 #ifdef Q_OS_WIN
257 return QFileSystemModel::tr("My Computer");
258 #else
259 return QFileSystemModel::tr("Computer");
260 #endif
261 }
262
delayedSort()263 inline void delayedSort() {
264 if (!delayedSortTimer.isActive())
265 delayedSortTimer.start(0);
266 }
267
268 QIcon icon(const QModelIndex &index) const;
269 QString name(const QModelIndex &index) const;
270 QString displayName(const QModelIndex &index) const;
271 QString filePath(const QModelIndex &index) const;
272 QString size(const QModelIndex &index) const;
273 static QString size(qint64 bytes);
274 QString type(const QModelIndex &index) const;
275 QString time(const QModelIndex &index) const;
276
277 void _q_directoryChanged(const QString &directory, const QStringList &list);
278 void _q_performDelayedSort();
279 void _q_fileSystemChanged(const QString &path, const QVector<QPair<QString, QFileInfo> > &);
280 void _q_resolvedName(const QString &fileName, const QString &resolvedName);
281
282 QDir rootDir;
283 #if QT_CONFIG(filesystemwatcher)
284 # ifdef Q_OS_WIN
285 QStringList unwatchPathsAt(const QModelIndex &);
watchPaths(const QStringList & paths)286 void watchPaths(const QStringList &paths) { fileInfoGatherer.watchPaths(paths); }
287 # endif // Q_OS_WIN
288 QFileInfoGatherer fileInfoGatherer;
289 #endif // filesystemwatcher
290 QTimer delayedSortTimer;
291 QHash<const QFileSystemNode*, bool> bypassFilters;
292 #if QT_CONFIG(regularexpression)
293 QStringList nameFilters;
294 #endif
295 QHash<QString, QString> resolvedSymLinks;
296
297 QFileSystemNode root;
298
299 struct Fetching {
300 QString dir;
301 QString file;
302 const QFileSystemNode *node;
303 };
304 QVector<Fetching> toFetch;
305
306 QBasicTimer fetchingTimer;
307
308 QDir::Filters filters = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs;
309 int sortColumn = 0;
310 Qt::SortOrder sortOrder = Qt::AscendingOrder;
311 bool forceSort = true;
312 bool readOnly = true;
313 bool setRootPath = false;
314 bool nameFilterDisables = true; // false on windows, true on mac and unix
315 // This flag is an optimization for QFileDialog. It enables a sort which is
316 // not recursive, meaning we sort only what we see.
317 bool disableRecursiveSort = false;
318 };
319 Q_DECLARE_TYPEINFO(QFileSystemModelPrivate::Fetching, Q_MOVABLE_TYPE);
320
321 QT_END_NAMESPACE
322
323 #endif
324