1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include <QtCore/QUrl> |
52 | #include <QtCore/QVariant> |
53 | #include <QtXmlPatterns/QXmlNamePool> |
54 | #include "filetree.h" |
55 | |
56 | /* |
57 | The model has two types of nodes: elements & attributes. |
58 | |
59 | <directory name=""> |
60 | <file name=""> |
61 | </file> |
62 | </directory> |
63 | |
64 | In QXmlNodeModelIndex we store two values. QXmlNodeIndex::data() |
65 | is treated as a signed int, and it is an index into m_fileInfos |
66 | unless it is -1, in which case it has no meaning and the value |
67 | of QXmlNodeModelIndex::additionalData() is a Type name instead. |
68 | */ |
69 | |
70 | /*! |
71 | The constructor passes \a pool to the base class, then loads an |
72 | internal vector with an instance of QXmlName for each of the |
73 | strings "file", "directory", "fileName", "filePath", "size", |
74 | "mimeType", and "suffix". |
75 | */ |
76 | //! [2] |
77 | FileTree::FileTree(const QXmlNamePool& pool) |
78 | : QSimpleXmlNodeModel(pool), |
79 | m_filterAllowAll(QDir::AllEntries | |
80 | QDir::AllDirs | |
81 | QDir::NoDotAndDotDot | |
82 | QDir::Hidden), |
83 | m_sortFlags(QDir::Name) |
84 | { |
85 | QXmlNamePool np = namePool(); |
86 | m_names.resize(asize: 7); |
87 | m_names[File] = QXmlName(np, QLatin1String("file" )); |
88 | m_names[Directory] = QXmlName(np, QLatin1String("directory" )); |
89 | m_names[AttributeFileName] = QXmlName(np, QLatin1String("fileName" )); |
90 | m_names[AttributeFilePath] = QXmlName(np, QLatin1String("filePath" )); |
91 | m_names[AttributeSize] = QXmlName(np, QLatin1String("size" )); |
92 | m_names[AttributeMIMEType] = QXmlName(np, QLatin1String("mimeType" )); |
93 | m_names[AttributeSuffix] = QXmlName(np, QLatin1String("suffix" )); |
94 | } |
95 | //! [2] |
96 | |
97 | /*! |
98 | Returns the QXmlNodeModelIndex for the model node representing |
99 | the directory \a dirName. |
100 | |
101 | It calls QDir::cleanPath(), because an instance of QFileInfo |
102 | constructed for a path ending in '/' will return the empty string in |
103 | fileName(), instead of the directory name. |
104 | */ |
105 | QXmlNodeModelIndex FileTree::nodeFor(const QString& dirName) const |
106 | { |
107 | QFileInfo dirInfo(QDir::cleanPath(path: dirName)); |
108 | Q_ASSERT(dirInfo.exists()); |
109 | return toNodeIndex(index: dirInfo); |
110 | } |
111 | |
112 | /*! |
113 | Since the value will always be in m_fileInfos, it is safe for |
114 | us to return a const reference to it. |
115 | */ |
116 | //! [6] |
117 | const QFileInfo& |
118 | FileTree::toFileInfo(const QXmlNodeModelIndex &nodeIndex) const |
119 | { |
120 | return m_fileInfos.at(i: nodeIndex.data()); |
121 | } |
122 | //! [6] |
123 | |
124 | /*! |
125 | Returns the model node index for the node specified by the |
126 | QFileInfo and node Type. |
127 | */ |
128 | //! [1] |
129 | QXmlNodeModelIndex |
130 | FileTree::toNodeIndex(const QFileInfo &fileInfo, Type attributeName) const |
131 | { |
132 | const int indexOf = m_fileInfos.indexOf(t: fileInfo); |
133 | |
134 | if (indexOf == -1) { |
135 | m_fileInfos.append(t: fileInfo); |
136 | return createIndex(data: m_fileInfos.count()-1, additionalData: attributeName); |
137 | } |
138 | else |
139 | return createIndex(data: indexOf, additionalData: attributeName); |
140 | } |
141 | //! [1] |
142 | |
143 | /*! |
144 | Returns the model node index for the node specified by the |
145 | QFileInfo, which must be a Type::File or Type::Directory. |
146 | */ |
147 | //! [0] |
148 | QXmlNodeModelIndex FileTree::toNodeIndex(const QFileInfo &fileInfo) const |
149 | { |
150 | return toNodeIndex(fileInfo, attributeName: fileInfo.isDir() ? Directory : File); |
151 | } |
152 | //! [0] |
153 | |
154 | /*! |
155 | This private helper function is only called by nextFromSimpleAxis(). |
156 | It is called whenever nextFromSimpleAxis() is called with an axis |
157 | parameter of either \c{PreviousSibling} or \c{NextSibling}. |
158 | */ |
159 | //! [5] |
160 | QXmlNodeModelIndex FileTree::nextSibling(const QXmlNodeModelIndex &nodeIndex, |
161 | const QFileInfo &fileInfo, |
162 | qint8 offset) const |
163 | { |
164 | Q_ASSERT(offset == -1 || offset == 1); |
165 | |
166 | // Get the context node's parent. |
167 | const QXmlNodeModelIndex parent(nextFromSimpleAxis(Parent, nodeIndex)); |
168 | |
169 | if (parent.isNull()) |
170 | return QXmlNodeModelIndex(); |
171 | |
172 | // Get the parent's child list. |
173 | const QFileInfo parentFI(toFileInfo(nodeIndex: parent)); |
174 | Q_ASSERT(Type(parent.additionalData()) == Directory); |
175 | const QFileInfoList siblings(QDir(parentFI.absoluteFilePath()).entryInfoList(nameFilters: QStringList(), |
176 | filters: m_filterAllowAll, |
177 | sort: m_sortFlags)); |
178 | Q_ASSERT_X(!siblings.isEmpty(), Q_FUNC_INFO, "Can't happen! We started at a child." ); |
179 | |
180 | // Find the index of the child where we started. |
181 | const int indexOfMe = siblings.indexOf(t: fileInfo); |
182 | |
183 | // Apply the offset. |
184 | const int siblingIndex = indexOfMe + offset; |
185 | if (siblingIndex < 0 || siblingIndex > siblings.count() - 1) |
186 | return QXmlNodeModelIndex(); |
187 | else |
188 | return toNodeIndex(fileInfo: siblings.at(i: siblingIndex)); |
189 | } |
190 | //! [5] |
191 | |
192 | /*! |
193 | This function is called by the Qt XML Patterns query engine when it |
194 | wants to move to the next node in the model. It moves along an \a |
195 | axis, \e from the node specified by \a nodeIndex. |
196 | |
197 | This function is usually the one that requires the most design and |
198 | implementation work, because the implementation depends on the |
199 | perhaps unique structure of your non-XML data. |
200 | |
201 | There are \l {QAbstractXmlNodeModel::SimpleAxis} {four values} for |
202 | \a axis that the implementation must handle, but there are really |
203 | only two axes, i.e., vertical and horizontal. Two of the four values |
204 | specify direction on the vertical axis (\c{Parent} and |
205 | \c{FirstChild}), and the other two values specify direction on the |
206 | horizontal axis (\c{PreviousSibling} and \c{NextSibling}). |
207 | |
208 | The typical implementation will be a \c switch statement with |
209 | a case for each of the four \a axis values. |
210 | */ |
211 | //! [4] |
212 | QXmlNodeModelIndex |
213 | FileTree::nextFromSimpleAxis(SimpleAxis axis, const QXmlNodeModelIndex &nodeIndex) const |
214 | { |
215 | const QFileInfo fi(toFileInfo(nodeIndex)); |
216 | const Type type = Type(nodeIndex.additionalData()); |
217 | |
218 | if (type != File && type != Directory) { |
219 | Q_ASSERT_X(axis == Parent, Q_FUNC_INFO, "An attribute only has a parent!" ); |
220 | return toNodeIndex(fileInfo: fi, attributeName: Directory); |
221 | } |
222 | |
223 | switch (axis) { |
224 | case Parent: |
225 | return toNodeIndex(fileInfo: QFileInfo(fi.path()), attributeName: Directory); |
226 | |
227 | case FirstChild: |
228 | { |
229 | if (type == File) // A file has no children. |
230 | return QXmlNodeModelIndex(); |
231 | else { |
232 | Q_ASSERT(type == Directory); |
233 | Q_ASSERT_X(fi.isDir(), Q_FUNC_INFO, "It isn't really a directory!" ); |
234 | const QDir dir(fi.absoluteFilePath()); |
235 | Q_ASSERT(dir.exists()); |
236 | |
237 | const QFileInfoList children(dir.entryInfoList(nameFilters: QStringList(), |
238 | filters: m_filterAllowAll, |
239 | sort: m_sortFlags)); |
240 | if (children.isEmpty()) |
241 | return QXmlNodeModelIndex(); |
242 | const QFileInfo firstChild(children.first()); |
243 | return toNodeIndex(fileInfo: firstChild); |
244 | } |
245 | } |
246 | |
247 | case PreviousSibling: |
248 | return nextSibling(nodeIndex, fileInfo: fi, offset: -1); |
249 | |
250 | case NextSibling: |
251 | return nextSibling(nodeIndex, fileInfo: fi, offset: 1); |
252 | } |
253 | |
254 | Q_ASSERT_X(false, Q_FUNC_INFO, "Don't ever get here!" ); |
255 | return QXmlNodeModelIndex(); |
256 | } |
257 | //! [4] |
258 | |
259 | /*! |
260 | No matter what part of the file system we model (the whole file |
261 | tree or a subtree), \a node will always have \c{file:///} as |
262 | the document URI. |
263 | */ |
264 | QUrl FileTree::documentUri(const QXmlNodeModelIndex &node) const |
265 | { |
266 | Q_UNUSED(node); |
267 | return QUrl("file:///" ); |
268 | } |
269 | |
270 | /*! |
271 | This function returns QXmlNodeModelIndex::Element if \a node |
272 | is a directory or a file, and QXmlNodeModelIndex::Attribute |
273 | otherwise. |
274 | */ |
275 | QXmlNodeModelIndex::NodeKind |
276 | FileTree::kind(const QXmlNodeModelIndex &node) const |
277 | { |
278 | switch (Type(node.additionalData())) { |
279 | case Directory: |
280 | case File: |
281 | return QXmlNodeModelIndex::Element; |
282 | default: |
283 | return QXmlNodeModelIndex::Attribute; |
284 | } |
285 | } |
286 | |
287 | /*! |
288 | No order is defined for this example, so we always return |
289 | QXmlNodeModelIndex::Is, just to keep everyone happy. |
290 | */ |
291 | QXmlNodeModelIndex::DocumentOrder |
292 | FileTree::compareOrder(const QXmlNodeModelIndex&, |
293 | const QXmlNodeModelIndex&) const |
294 | { |
295 | return QXmlNodeModelIndex::Is; |
296 | } |
297 | |
298 | /*! |
299 | Returns the name of \a node. The caller guarantees that \a node is |
300 | not null and that it is contained in this node model. |
301 | */ |
302 | //! [3] |
303 | QXmlName FileTree::name(const QXmlNodeModelIndex &node) const |
304 | { |
305 | return m_names.at(i: node.additionalData()); |
306 | } |
307 | //! [3] |
308 | |
309 | /*! |
310 | Always returns the QXmlNodeModelIndex for the root of the |
311 | file system, i.e. "/". |
312 | */ |
313 | QXmlNodeModelIndex FileTree::root(const QXmlNodeModelIndex &node) const |
314 | { |
315 | Q_UNUSED(node); |
316 | return toNodeIndex(fileInfo: QFileInfo(QLatin1String("/" ))); |
317 | } |
318 | |
319 | /*! |
320 | Returns the typed value for \a node, which must be either an |
321 | attribute or an element. The QVariant returned represents the atomic |
322 | value of an attribute or the atomic value contained in an element. |
323 | |
324 | If the QVariant is returned as a default constructed variant, |
325 | it means that \a node has no typed value. |
326 | */ |
327 | QVariant FileTree::typedValue(const QXmlNodeModelIndex &node) const |
328 | { |
329 | const QFileInfo &fi = toFileInfo(nodeIndex: node); |
330 | |
331 | switch (Type(node.additionalData())) { |
332 | case Directory: |
333 | // deliberate fall through. |
334 | case File: |
335 | return QString(); |
336 | case AttributeFileName: |
337 | return fi.fileName(); |
338 | case AttributeFilePath: |
339 | return fi.filePath(); |
340 | case AttributeSize: |
341 | return fi.size(); |
342 | case AttributeMIMEType: |
343 | { |
344 | /* We don't have any MIME detection code currently, so return |
345 | * the most generic one. */ |
346 | return QLatin1String("application/octet-stream" ); |
347 | } |
348 | case AttributeSuffix: |
349 | return fi.suffix(); |
350 | } |
351 | |
352 | Q_ASSERT_X(false, Q_FUNC_INFO, "This line should never be reached." ); |
353 | return QString(); |
354 | } |
355 | |
356 | /*! |
357 | Returns the attributes of \a element. The caller guarantees |
358 | that \a element is an element in this node model. |
359 | */ |
360 | QVector<QXmlNodeModelIndex> |
361 | FileTree::attributes(const QXmlNodeModelIndex &element) const |
362 | { |
363 | QVector<QXmlNodeModelIndex> result; |
364 | |
365 | /* Both elements has this attribute. */ |
366 | const QFileInfo &forElement = toFileInfo(nodeIndex: element); |
367 | result.append(t: toNodeIndex(fileInfo: forElement, attributeName: AttributeFilePath)); |
368 | result.append(t: toNodeIndex(fileInfo: forElement, attributeName: AttributeFileName)); |
369 | |
370 | if (Type(element.additionalData() == File)) { |
371 | result.append(t: toNodeIndex(fileInfo: forElement, attributeName: AttributeSize)); |
372 | result.append(t: toNodeIndex(fileInfo: forElement, attributeName: AttributeSuffix)); |
373 | //result.append(toNodeIndex(forElement, AttributeMIMEType)); |
374 | } |
375 | else { |
376 | Q_ASSERT(element.additionalData() == Directory); |
377 | } |
378 | |
379 | return result; |
380 | } |
381 | |
382 | |