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