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]
FileTree(const QXmlNamePool & pool)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(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 */
nodeFor(const QString & dirName) const105 QXmlNodeModelIndex FileTree::nodeFor(const QString& dirName) const
106 {
107     QFileInfo dirInfo(QDir::cleanPath(dirName));
108     Q_ASSERT(dirInfo.exists());
109     return toNodeIndex(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&
toFileInfo(const QXmlNodeModelIndex & nodeIndex) const118 FileTree::toFileInfo(const QXmlNodeModelIndex &nodeIndex) const
119 {
120     return m_fileInfos.at(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
toNodeIndex(const QFileInfo & fileInfo,Type attributeName) const130 FileTree::toNodeIndex(const QFileInfo &fileInfo, Type attributeName) const
131 {
132     const int indexOf = m_fileInfos.indexOf(fileInfo);
133 
134     if (indexOf == -1) {
135         m_fileInfos.append(fileInfo);
136         return createIndex(m_fileInfos.count()-1, attributeName);
137     }
138     else
139         return createIndex(indexOf, 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]
toNodeIndex(const QFileInfo & fileInfo) const148 QXmlNodeModelIndex FileTree::toNodeIndex(const QFileInfo &fileInfo) const
149 {
150     return toNodeIndex(fileInfo, 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]
nextSibling(const QXmlNodeModelIndex & nodeIndex,const QFileInfo & fileInfo,qint8 offset) const160 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(parent));
174     Q_ASSERT(Type(parent.additionalData()) == Directory);
175     const QFileInfoList siblings(QDir(parentFI.absoluteFilePath()).entryInfoList(QStringList(),
176                                                                                  m_filterAllowAll,
177                                                                                  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(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(siblings.at(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
nextFromSimpleAxis(SimpleAxis axis,const QXmlNodeModelIndex & nodeIndex) const213 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(fi, Directory);
221     }
222 
223     switch (axis) {
224         case Parent:
225             return toNodeIndex(QFileInfo(fi.path()), 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(QStringList(),
238                                                                m_filterAllowAll,
239                                                                m_sortFlags));
240                 if (children.isEmpty())
241                     return QXmlNodeModelIndex();
242                 const QFileInfo firstChild(children.first());
243                 return toNodeIndex(firstChild);
244             }
245         }
246 
247         case PreviousSibling:
248             return nextSibling(nodeIndex, fi, -1);
249 
250         case NextSibling:
251             return nextSibling(nodeIndex, fi, 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  */
documentUri(const QXmlNodeModelIndex & node) const264 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
kind(const QXmlNodeModelIndex & node) const276 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
compareOrder(const QXmlNodeModelIndex &,const QXmlNodeModelIndex &) const292 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]
name(const QXmlNodeModelIndex & node) const303 QXmlName FileTree::name(const QXmlNodeModelIndex &node) const
304 {
305     return m_names.at(node.additionalData());
306 }
307 //! [3]
308 
309 /*!
310   Always returns the QXmlNodeModelIndex for the root of the
311   file system, i.e. "/".
312  */
root(const QXmlNodeModelIndex & node) const313 QXmlNodeModelIndex FileTree::root(const QXmlNodeModelIndex &node) const
314 {
315     Q_UNUSED(node);
316     return toNodeIndex(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  */
typedValue(const QXmlNodeModelIndex & node) const327 QVariant FileTree::typedValue(const QXmlNodeModelIndex &node) const
328 {
329     const QFileInfo &fi = toFileInfo(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>
attributes(const QXmlNodeModelIndex & element) const361 FileTree::attributes(const QXmlNodeModelIndex &element) const
362 {
363     QVector<QXmlNodeModelIndex> result;
364 
365     /* Both elements has this attribute. */
366     const QFileInfo &forElement = toFileInfo(element);
367     result.append(toNodeIndex(forElement, AttributeFilePath));
368     result.append(toNodeIndex(forElement, AttributeFileName));
369 
370     if (Type(element.additionalData() == File)) {
371         result.append(toNodeIndex(forElement, AttributeSize));
372         result.append(toNodeIndex(forElement, AttributeSuffix));
373         //result.append(toNodeIndex(forElement, AttributeMIMEType));
374     }
375     else {
376         Q_ASSERT(element.additionalData() == Directory);
377     }
378 
379     return result;
380 }
381 
382