source: trunk/src/gui/dialogs/qfilesystemmodel.cpp@ 1119

Last change on this file since 1119 was 1119, checked in by Dmitry A. Kuminov, 12 years ago

gui: Make sure we don't skip symlinks in file dialogs by accident (just in case).

File size: 65.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qfilesystemmodel_p.h"
43#include "qfilesystemmodel.h"
44#include <qlocale.h>
45#include <qmime.h>
46#include <qurl.h>
47#include <qdebug.h>
48#include <qmessagebox.h>
49#include <qapplication.h>
50
51#ifdef Q_OS_WIN
52#include <qt_windows.h>
53#endif
54#ifdef Q_OS_WIN32
55#include <QtCore/QVarLengthArray>
56#endif
57
58QT_BEGIN_NAMESPACE
59
60#ifndef QT_NO_FILESYSTEMMODEL
61
62/*!
63 \enum QFileSystemModel::Roles
64 \value FileIconRole
65 \value FilePathRole
66 \value FileNameRole
67 \value FilePermissions
68*/
69
70/*!
71 \class QFileSystemModel
72 \since 4.4
73
74 \brief The QFileSystemModel class provides a data model for the local filesystem.
75
76 \ingroup model-view
77
78 This class provides access to the local filesystem, providing functions
79 for renaming and removing files and directories, and for creating new
80 directories. In the simplest case, it can be used with a suitable display
81 widget as part of a browser or filter.
82
83 QFileSystemModel can be accessed using the standard interface provided by
84 QAbstractItemModel, but it also provides some convenience functions that are
85 specific to a directory model.
86 The fileInfo(), isDir(), name(), and path() functions provide information
87 about the underlying files and directories related to items in the model.
88 Directories can be created and removed using mkdir(), rmdir().
89
90 \note QFileSystemModel requires an instance of a GUI application.
91
92 \section1 Example Usage
93
94 A directory model that displays the contents of a default directory
95 is usually constructed with a parent object:
96
97 \snippet doc/src/snippets/shareddirmodel/main.cpp 2
98
99 A tree view can be used to display the contents of the model
100
101 \snippet doc/src/snippets/shareddirmodel/main.cpp 4
102
103 and the contents of a particular directory can be displayed by
104 setting the tree view's root index:
105
106 \snippet doc/src/snippets/shareddirmodel/main.cpp 7
107
108 The view's root index can be used to control how much of a
109 hierarchical model is displayed. QDirModel provides a convenience
110 function that returns a suitable model index for a path to a
111 directory within the model.
112
113 \section1 Caching and Performance
114
115 QFileSystemModel will not fetch any files or directories until setRootPath()
116 is called. This will prevent any unnecessary querying on the file system
117 until that point such as listing the drives on Windows.
118
119 Unlike QDirModel, QFileSystemModel uses a separate thread to populate
120 itself so it will not cause the main thread to hang as the file system
121 is being queried. Calls to rowCount() will return 0 until the model
122 populates a directory.
123
124 QFileSystemModel keeps a cache with file information. The cache is
125 automatically kept up to date using the QFileSystemWatcher.
126
127 \sa {Model Classes}
128*/
129
130/*!
131 \fn bool QFileSystemModel::rmdir(const QModelIndex &index) const
132
133 Removes the directory corresponding to the model item \a index in the
134 file system model and \bold{deletes the corresponding directory from the
135 file system}, returning true if successful. If the directory cannot be
136 removed, false is returned.
137
138 \warning This function deletes directories from the file system; it does
139 \bold{not} move them to a location where they can be recovered.
140
141 \sa remove()
142*/
143
144/*!
145 \fn QIcon QFileSystemModel::fileName(const QModelIndex &index) const
146
147 Returns the file name for the item stored in the model under the given
148 \a index.
149*/
150
151/*!
152 \fn QIcon QFileSystemModel::fileIcon(const QModelIndex &index) const
153
154 Returns the icon for the item stored in the model under the given
155 \a index.
156*/
157
158/*!
159 \fn QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const
160
161 Returns the QFileInfo for the item stored in the model under the given
162 \a index.
163*/
164
165/*!
166 \fn void QFileSystemModel::rootPathChanged(const QString &newPath);
167
168 This signal is emitted whenever the root path has been changed to a \a newPath.
169*/
170
171/*!
172 \fn void QFileSystemModel::fileRenamed(const QString &path, const QString &oldName, const QString &newName)
173
174 This signal is emitted whenever a file with the \a oldName is successfully
175 renamed to \a newName. The file is located in in the directory \a path.
176*/
177
178/*!
179 \since 4.7
180 \fn void QFileSystemModel::directoryLoaded(const QString &path)
181
182 This signal is emitted when the gatherer thread has finished to load the \a path.
183
184*/
185
186/*!
187 \fn bool QFileSystemModel::remove(const QModelIndex &index) const
188
189 Removes the model item \a index from the file system model and \bold{deletes the
190 corresponding file from the file system}, returning true if successful. If the
191 item cannot be removed, false is returned.
192
193 \warning This function deletes files from the file system; it does \bold{not}
194 move them to a location where they can be recovered.
195
196 \sa rmdir()
197*/
198
199bool QFileSystemModel::remove(const QModelIndex &aindex) const
200{
201 //### TODO optim
202 QString path = filePath(aindex);
203 QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func());
204 d->fileInfoGatherer.removePath(path);
205 QDirIterator it(path,
206 QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot,
207 QDirIterator::Subdirectories);
208 QStringList children;
209 while (it.hasNext())
210 children.prepend(it.next());
211 children.append(path);
212
213 bool error = false;
214 for (int i = 0; i < children.count(); ++i) {
215 QFileInfo info(children.at(i));
216 QModelIndex modelIndex = index(children.at(i));
217 if (info.isDir()) {
218 QDir dir;
219 if (children.at(i) != path)
220 error |= remove(modelIndex);
221 error |= rmdir(modelIndex);
222 } else {
223 error |= QFile::remove(filePath(modelIndex));
224 }
225 }
226 return error;
227}
228
229/*!
230 Constructs a file system model with the given \a parent.
231*/
232QFileSystemModel::QFileSystemModel(QObject *parent)
233 : QAbstractItemModel(*new QFileSystemModelPrivate, parent)
234{
235 Q_D(QFileSystemModel);
236 d->init();
237}
238
239/*!
240 \internal
241*/
242QFileSystemModel::QFileSystemModel(QFileSystemModelPrivate &dd, QObject *parent)
243 : QAbstractItemModel(dd, parent)
244{
245 Q_D(QFileSystemModel);
246 d->init();
247}
248
249/*!
250 Destroys this file system model.
251*/
252QFileSystemModel::~QFileSystemModel()
253{
254}
255
256/*!
257 \reimp
258*/
259QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent) const
260{
261 Q_D(const QFileSystemModel);
262 if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
263 return QModelIndex();
264
265 // get the parent node
266 QFileSystemModelPrivate::QFileSystemNode *parentNode = (d->indexValid(parent) ? d->node(parent) :
267 const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&d->root));
268 Q_ASSERT(parentNode);
269
270 // now get the internal pointer for the index
271 QString childName = parentNode->visibleChildren[d->translateVisibleLocation(parentNode, row)];
272 const QFileSystemModelPrivate::QFileSystemNode *indexNode = parentNode->children.value(childName);
273 Q_ASSERT(indexNode);
274
275 return createIndex(row, column, const_cast<QFileSystemModelPrivate::QFileSystemNode*>(indexNode));
276}
277
278/*!
279 \overload
280
281 Returns the model item index for the given \a path and \a column.
282*/
283QModelIndex QFileSystemModel::index(const QString &path, int column) const
284{
285 Q_D(const QFileSystemModel);
286 QFileSystemModelPrivate::QFileSystemNode *node = d->node(path, false);
287 QModelIndex idx = d->index(node);
288 if (idx.column() != column)
289 idx = idx.sibling(idx.row(), column);
290 return idx;
291}
292
293/*!
294 \internal
295
296 Return the QFileSystemNode that goes to index.
297 */
298QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QModelIndex &index) const
299{
300 if (!index.isValid())
301 return const_cast<QFileSystemNode*>(&root);
302 QFileSystemModelPrivate::QFileSystemNode *indexNode = static_cast<QFileSystemModelPrivate::QFileSystemNode*>(index.internalPointer());
303 Q_ASSERT(indexNode);
304 return indexNode;
305}
306
307#ifdef Q_OS_WIN32
308static QString qt_GetLongPathName(const QString &strShortPath)
309{
310 if (strShortPath.isEmpty()
311 || strShortPath == QLatin1String(".") || strShortPath == QLatin1String(".."))
312 return strShortPath;
313 if (strShortPath.length() == 2 && strShortPath.endsWith(QLatin1Char(':')))
314 return strShortPath.toUpper();
315 const QString absPath = QDir(strShortPath).absolutePath();
316 if (absPath.startsWith(QLatin1String("//"))
317 || absPath.startsWith(QLatin1String("\\\\"))) // unc
318 return QDir::fromNativeSeparators(absPath);
319 if (absPath.startsWith(QLatin1Char('/')))
320 return QString();
321 const QString inputString = QLatin1String("\\\\?\\") + QDir::toNativeSeparators(absPath);
322 QVarLengthArray<TCHAR, MAX_PATH> buffer(MAX_PATH);
323 DWORD result = ::GetLongPathName((wchar_t*)inputString.utf16(),
324 buffer.data(),
325 buffer.size());
326 if (result > DWORD(buffer.size())) {
327 buffer.resize(result);
328 result = ::GetLongPathName((wchar_t*)inputString.utf16(),
329 buffer.data(),
330 buffer.size());
331 }
332 if (result > 4) {
333 QString longPath = QString::fromWCharArray(buffer.data() + 4); // ignoring prefix
334 longPath[0] = longPath.at(0).toUpper(); // capital drive letters
335 return QDir::fromNativeSeparators(longPath);
336 } else {
337 return QDir::fromNativeSeparators(strShortPath);
338 }
339}
340#endif
341
342/*!
343 \internal
344
345 Given a path return the matching QFileSystemNode or &root if invalid
346*/
347QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const
348{
349 Q_Q(const QFileSystemModel);
350 Q_UNUSED(q);
351 if (path.isEmpty() || path == myComputer() || path.startsWith(QLatin1Char(':')))
352 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
353
354 // Construct the nodes up to the new root path if they need to be built
355 QString absolutePath;
356#ifdef Q_OS_WIN32
357 QString longPath = qt_GetLongPathName(path);
358#else
359 QString longPath = path;
360#endif
361 if (longPath == rootDir.path())
362 absolutePath = rootDir.absolutePath();
363 else
364 absolutePath = QDir(longPath).absolutePath();
365
366 QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts);
367 if ((pathElements.isEmpty())
368#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN) && !defined(Q_OS_OS2)
369 && QDir::fromNativeSeparators(longPath) != QLatin1String("/")
370#endif
371 )
372 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
373 QModelIndex index = QModelIndex(); // start with "My Computer"
374#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_OS2)
375 if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path
376 QString host = QLatin1String("\\\\") + pathElements.first();
377 if (absolutePath == QDir::fromNativeSeparators(host))
378 absolutePath.append(QLatin1Char('/'));
379 if (longPath.endsWith(QLatin1Char('/')) && !absolutePath.endsWith(QLatin1Char('/')))
380 absolutePath.append(QLatin1Char('/'));
381 int r = 0;
382 QFileSystemModelPrivate::QFileSystemNode *rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
383 if (!root.children.contains(host.toLower())) {
384 if (pathElements.count() == 1 && !absolutePath.endsWith(QLatin1Char('/')))
385 return rootNode;
386 QFileInfo info(host);
387 if (!info.exists())
388 return rootNode;
389 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
390 p->addNode(rootNode, host,info);
391 p->addVisibleFiles(rootNode, QStringList(host));
392 }
393 r = rootNode->visibleLocation(host);
394 r = translateVisibleLocation(rootNode, r);
395 index = q->index(r, 0, QModelIndex());
396 pathElements.pop_front();
397 } else
398#endif
399
400#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) || defined(Q_OS_OS2)
401 {
402 if (!pathElements.at(0).contains(QLatin1String(":"))) {
403 // The reason we express it like this instead of with anonymous, temporary
404 // variables, is to workaround a compiler crash with Q_CC_NOKIAX86.
405 QString rootPath = QDir(longPath).rootPath();
406 pathElements.prepend(rootPath);
407 }
408 if (pathElements.at(0).endsWith(QLatin1Char('/')))
409 pathElements[0].chop(1);
410 }
411#else
412 // add the "/" item, since it is a valid path element on Unix
413 if (absolutePath[0] == QLatin1Char('/'))
414 pathElements.prepend(QLatin1String("/"));
415#endif
416
417 QFileSystemModelPrivate::QFileSystemNode *parent = node(index);
418
419 for (int i = 0; i < pathElements.count(); ++i) {
420 QString element = pathElements.at(i);
421#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
422 // On Windows and OS/2, "filename......." and "filename" are equivalent Task #133928
423 while (element.endsWith(QLatin1Char('.')))
424 element.chop(1);
425#endif
426 bool alreadyExisted = parent->children.contains(element);
427
428 // we couldn't find the path element, we create a new node since we
429 // _know_ that the path is valid
430 if (alreadyExisted) {
431 if ((parent->children.count() == 0)
432 || (parent->caseSensitive()
433 && parent->children.value(element)->fileName != element)
434 || (!parent->caseSensitive()
435 && parent->children.value(element)->fileName.toLower() != element.toLower()))
436 alreadyExisted = false;
437 }
438
439 QFileSystemModelPrivate::QFileSystemNode *node;
440 if (!alreadyExisted) {
441 // Someone might call ::index("file://cookie/monster/doesn't/like/veggies"),
442 // a path that doesn't exists, I.E. don't blindly create directories.
443 QFileInfo info(absolutePath);
444 if (!info.exists())
445 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
446 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
447 node = p->addNode(parent, element,info);
448#ifndef QT_NO_FILESYSTEMWATCHER
449 node->populate(fileInfoGatherer.getInfo(info));
450#endif
451 } else {
452 node = parent->children.value(element);
453 }
454
455 Q_ASSERT(node);
456 if (!node->isVisible) {
457 // It has been filtered out
458 if (alreadyExisted && node->hasInformation() && !fetch)
459 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
460
461 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
462 p->addVisibleFiles(parent, QStringList(element));
463 if (!p->bypassFilters.contains(node))
464 p->bypassFilters[node] = 1;
465 QString dir = q->filePath(this->index(parent));
466 if (!node->hasInformation() && fetch) {
467 Fetching f;
468 f.dir = dir;
469 f.file = element;
470 f.node = node;
471 p->toFetch.append(f);
472 p->fetchingTimer.start(0, const_cast<QFileSystemModel*>(q));
473 }
474 }
475 parent = node;
476 }
477
478 return parent;
479}
480
481/*!
482 \reimp
483*/
484void QFileSystemModel::timerEvent(QTimerEvent *event)
485{
486 Q_D(QFileSystemModel);
487 if (event->timerId() == d->fetchingTimer.timerId()) {
488 d->fetchingTimer.stop();
489#ifndef QT_NO_FILESYSTEMWATCHER
490 for (int i = 0; i < d->toFetch.count(); ++i) {
491 const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node;
492 if (!node->hasInformation()) {
493 d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir,
494 QStringList(d->toFetch.at(i).file));
495 } else {
496 // qDebug() << "yah!, you saved a little gerbil soul";
497 }
498 }
499#endif
500 d->toFetch.clear();
501 }
502}
503
504/*!
505 Returns true if the model item \a index represents a directory;
506 otherwise returns false.
507*/
508bool QFileSystemModel::isDir(const QModelIndex &index) const
509{
510 // This function is for public usage only because it could create a file info
511 Q_D(const QFileSystemModel);
512 if (!index.isValid())
513 return true;
514 QFileSystemModelPrivate::QFileSystemNode *n = d->node(index);
515 if (n->hasInformation())
516 return n->isDir();
517 return fileInfo(index).isDir();
518}
519
520/*!
521 Returns the size in bytes of \a index. If the file does not exist, 0 is returned.
522 */
523qint64 QFileSystemModel::size(const QModelIndex &index) const
524{
525 Q_D(const QFileSystemModel);
526 if (!index.isValid())
527 return 0;
528 return d->node(index)->size();
529}
530
531/*!
532 Returns the type of file \a index such as "Directory" or "JPEG file".
533 */
534QString QFileSystemModel::type(const QModelIndex &index) const
535{
536 Q_D(const QFileSystemModel);
537 if (!index.isValid())
538 return QString();
539 return d->node(index)->type();
540}
541
542/*!
543 Returns the date and time when \a index was last modified.
544 */
545QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const
546{
547 Q_D(const QFileSystemModel);
548 if (!index.isValid())
549 return QDateTime();
550 return d->node(index)->lastModified();
551}
552
553/*!
554 \reimp
555*/
556QModelIndex QFileSystemModel::parent(const QModelIndex &index) const
557{
558 Q_D(const QFileSystemModel);
559 if (!d->indexValid(index))
560 return QModelIndex();
561
562 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
563 Q_ASSERT(indexNode != 0);
564 QFileSystemModelPrivate::QFileSystemNode *parentNode = (indexNode ? indexNode->parent : 0);
565 if (parentNode == 0 || parentNode == &d->root)
566 return QModelIndex();
567
568 // get the parent's row
569 QFileSystemModelPrivate::QFileSystemNode *grandParentNode = parentNode->parent;
570 Q_ASSERT(grandParentNode->children.contains(parentNode->fileName));
571 int visualRow = d->translateVisibleLocation(grandParentNode, grandParentNode->visibleLocation(grandParentNode->children.value(parentNode->fileName)->fileName));
572 if (visualRow == -1)
573 return QModelIndex();
574 return createIndex(visualRow, 0, parentNode);
575}
576
577/*
578 \internal
579
580 return the index for node
581*/
582QModelIndex QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode *node) const
583{
584 Q_Q(const QFileSystemModel);
585 QFileSystemModelPrivate::QFileSystemNode *parentNode = (node ? node->parent : 0);
586 if (node == &root || !parentNode)
587 return QModelIndex();
588
589 // get the parent's row
590 Q_ASSERT(node);
591 if (!node->isVisible)
592 return QModelIndex();
593
594 int visualRow = translateVisibleLocation(parentNode, parentNode->visibleLocation(node->fileName));
595 return q->createIndex(visualRow, 0, const_cast<QFileSystemNode*>(node));
596}
597
598/*!
599 \reimp
600*/
601bool QFileSystemModel::hasChildren(const QModelIndex &parent) const
602{
603 Q_D(const QFileSystemModel);
604 if (parent.column() > 0)
605 return false;
606
607 if (!parent.isValid()) // drives
608 return true;
609
610 const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
611 Q_ASSERT(indexNode);
612 return (indexNode->isDir());
613}
614
615/*!
616 \reimp
617 */
618bool QFileSystemModel::canFetchMore(const QModelIndex &parent) const
619{
620 Q_D(const QFileSystemModel);
621 const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
622 return (!indexNode->populatedChildren);
623}
624
625/*!
626 \reimp
627 */
628void QFileSystemModel::fetchMore(const QModelIndex &parent)
629{
630 Q_D(QFileSystemModel);
631 if (!d->setRootPath)
632 return;
633 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
634 if (indexNode->populatedChildren)
635 return;
636 indexNode->populatedChildren = true;
637 d->fileInfoGatherer.list(filePath(parent));
638}
639
640/*!
641 \reimp
642*/
643int QFileSystemModel::rowCount(const QModelIndex &parent) const
644{
645 Q_D(const QFileSystemModel);
646 if (parent.column() > 0)
647 return 0;
648
649 if (!parent.isValid())
650 return d->root.visibleChildren.count();
651
652 const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
653 return parentNode->visibleChildren.count();
654}
655
656/*!
657 \reimp
658*/
659int QFileSystemModel::columnCount(const QModelIndex &parent) const
660{
661 return (parent.column() > 0) ? 0 : 4;
662}
663
664/*!
665 Returns the data stored under the given \a role for the item "My Computer".
666
667 \sa Qt::ItemDataRole
668 */
669QVariant QFileSystemModel::myComputer(int role) const
670{
671 Q_D(const QFileSystemModel);
672 switch (role) {
673 case Qt::DisplayRole:
674 return d->myComputer();
675 case Qt::DecorationRole:
676 return d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Computer);
677 }
678 return QVariant();
679}
680
681/*!
682 \reimp
683*/
684QVariant QFileSystemModel::data(const QModelIndex &index, int role) const
685{
686 Q_D(const QFileSystemModel);
687 if (!index.isValid() || index.model() != this)
688 return QVariant();
689
690 switch (role) {
691 case Qt::EditRole:
692 case Qt::DisplayRole:
693 switch (index.column()) {
694 case 0: return d->displayName(index);
695 case 1: return d->size(index);
696 case 2: return d->type(index);
697 case 3: return d->time(index);
698 default:
699 qWarning("data: invalid display value column %d", index.column());
700 break;
701 }
702 break;
703 case FilePathRole:
704 return filePath(index);
705 case FileNameRole:
706 return d->name(index);
707 case Qt::DecorationRole:
708 if (index.column() == 0) {
709 QIcon icon = d->icon(index);
710 if (icon.isNull()) {
711 if (d->node(index)->isDir())
712 icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Folder);
713 else
714 icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::File);
715 }
716 return icon;
717 }
718 break;
719 case Qt::TextAlignmentRole:
720 if (index.column() == 1)
721 return Qt::AlignRight;
722 break;
723 case FilePermissions:
724 int p = permissions(index);
725 return p;
726 }
727
728 return QVariant();
729}
730
731/*!
732 \internal
733*/
734QString QFileSystemModelPrivate::size(const QModelIndex &index) const
735{
736 if (!index.isValid())
737 return QString();
738 const QFileSystemNode *n = node(index);
739 if (n->isDir()) {
740#ifdef Q_OS_MAC
741 return QLatin1String("--");
742#else
743 return QLatin1String("");
744#endif
745 // Windows - ""
746 // OS X - "--"
747 // Konqueror - "4 KB"
748 // Nautilus - "9 items" (the number of children)
749 }
750 return size(n->size());
751}
752
753QString QFileSystemModelPrivate::size(qint64 bytes)
754{
755 // According to the Si standard KB is 1000 bytes, KiB is 1024
756 // but on windows sizes are calculated by dividing by 1024 so we do what they do.
757 const qint64 kb = 1024;
758 const qint64 mb = 1024 * kb;
759 const qint64 gb = 1024 * mb;
760 const qint64 tb = 1024 * gb;
761 if (bytes >= tb)
762 return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3));
763 if (bytes >= gb)
764 return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2));
765 if (bytes >= mb)
766 return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1));
767 if (bytes >= kb)
768 return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb));
769 return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes));
770}
771
772/*!
773 \internal
774*/
775QString QFileSystemModelPrivate::time(const QModelIndex &index) const
776{
777 if (!index.isValid())
778 return QString();
779#ifndef QT_NO_DATESTRING
780 return node(index)->lastModified().toString(Qt::SystemLocaleDate);
781#else
782 Q_UNUSED(index);
783 return QString();
784#endif
785}
786
787/*
788 \internal
789*/
790QString QFileSystemModelPrivate::type(const QModelIndex &index) const
791{
792 if (!index.isValid())
793 return QString();
794 return node(index)->type();
795}
796
797/*!
798 \internal
799*/
800QString QFileSystemModelPrivate::name(const QModelIndex &index) const
801{
802 if (!index.isValid())
803 return QString();
804 QFileSystemNode *dirNode = node(index);
805 if (dirNode->isSymLink() && fileInfoGatherer.resolveSymlinks()) {
806 QString fullPath = QDir::fromNativeSeparators(filePath(index));
807 if (resolvedSymLinks.contains(fullPath))
808 return resolvedSymLinks[fullPath];
809 }
810 return dirNode->fileName;
811}
812
813/*!
814 \internal
815*/
816QString QFileSystemModelPrivate::displayName(const QModelIndex &index) const
817{
818#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
819 QFileSystemNode *dirNode = node(index);
820 if (!dirNode->volumeName.isNull())
821 return dirNode->volumeName + QLatin1String(" (") + name(index) + QLatin1Char(')');
822#endif
823 return name(index);
824}
825
826/*!
827 \internal
828*/
829QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const
830{
831 if (!index.isValid())
832 return QIcon();
833 return node(index)->icon();
834}
835
836/*!
837 \reimp
838*/
839bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, int role)
840{
841 Q_D(QFileSystemModel);
842 if (!idx.isValid()
843 || idx.column() != 0
844 || role != Qt::EditRole
845 || (flags(idx) & Qt::ItemIsEditable) == 0) {
846 return false;
847 }
848
849 QString newName = value.toString();
850 QString oldName = idx.data().toString();
851 if (newName == idx.data().toString())
852 return true;
853
854 if (newName.isEmpty()
855 || newName.contains(QDir::separator())
856 || !QDir(filePath(parent(idx))).rename(oldName, newName)) {
857#ifndef QT_NO_MESSAGEBOX
858 QMessageBox::information(0, QFileSystemModel::tr("Invalid filename"),
859 QFileSystemModel::tr("<b>The name \"%1\" can not be used.</b><p>Try using another name, with fewer characters or no punctuations marks.")
860 .arg(newName),
861 QMessageBox::Ok);
862#endif // QT_NO_MESSAGEBOX
863 return false;
864 } else {
865 /*
866 *After re-naming something we don't want the selection to change*
867 - can't remove rows and later insert
868 - can't quickly remove and insert
869 - index pointer can't change because treeview doesn't use persistant index's
870
871 - if this get any more complicated think of changing it to just
872 use layoutChanged
873 */
874
875 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(idx);
876 QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent;
877 int visibleLocation = parentNode->visibleLocation(parentNode->children.value(indexNode->fileName)->fileName);
878
879 d->addNode(parentNode, newName,indexNode->info->fileInfo());
880 parentNode->visibleChildren.removeAt(visibleLocation);
881 QFileSystemModelPrivate::QFileSystemNode * oldValue = parentNode->children.value(oldName);
882 parentNode->children[newName] = oldValue;
883 QFileInfo info(d->rootDir, newName);
884 oldValue->fileName = newName;
885 oldValue->parent = parentNode;
886 oldValue->populate(d->fileInfoGatherer.getInfo(info));
887 oldValue->isVisible = true;
888
889 parentNode->children.remove(oldName);
890 parentNode->visibleChildren.insert(visibleLocation, newName);
891
892 d->delayedSort();
893 emit fileRenamed(filePath(idx.parent()), oldName, newName);
894 }
895 return true;
896}
897
898/*!
899 \reimp
900*/
901QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
902{
903 switch (role) {
904 case Qt::DecorationRole:
905 if (section == 0) {
906 // ### TODO oh man this is ugly and doesn't even work all the way!
907 // it is still 2 pixels off
908 QImage pixmap(16, 1, QImage::Format_Mono);
909 pixmap.fill(0);
910 pixmap.setAlphaChannel(pixmap.createAlphaMask());
911 return pixmap;
912 }
913 break;
914 case Qt::TextAlignmentRole:
915 return Qt::AlignLeft;
916 }
917
918 if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
919 return QAbstractItemModel::headerData(section, orientation, role);
920
921 QString returnValue;
922 switch (section) {
923 case 0: returnValue = tr("Name");
924 break;
925 case 1: returnValue = tr("Size");
926 break;
927 case 2: returnValue =
928#ifdef Q_OS_MAC
929 tr("Kind", "Match OS X Finder");
930#else
931 tr("Type", "All other platforms");
932#endif
933 break;
934 // Windows - Type
935 // OS X - Kind
936 // Konqueror - File Type
937 // Nautilus - Type
938 case 3: returnValue = tr("Date Modified");
939 break;
940 default: return QVariant();
941 }
942 return returnValue;
943}
944
945/*!
946 \reimp
947*/
948Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const
949{
950 Q_D(const QFileSystemModel);
951 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
952 if (!index.isValid())
953 return flags;
954
955 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
956 if (d->nameFilterDisables && !d->passNameFilters(indexNode)) {
957 flags &= ~Qt::ItemIsEnabled;
958 // ### TODO you shouldn't be able to set this as the current item, task 119433
959 return flags;
960 }
961
962 flags |= Qt::ItemIsDragEnabled;
963 if (d->readOnly)
964 return flags;
965 if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) {
966 flags |= Qt::ItemIsEditable;
967 if (indexNode->isDir())
968 flags |= Qt::ItemIsDropEnabled;
969 }
970 return flags;
971}
972
973/*!
974 \internal
975*/
976void QFileSystemModelPrivate::_q_performDelayedSort()
977{
978 Q_Q(QFileSystemModel);
979 q->sort(sortColumn, sortOrder);
980}
981
982static inline QChar getNextChar(const QString &s, int location)
983{
984 return (location < s.length()) ? s.at(location) : QChar();
985}
986
987/*!
988 Natural number sort, skips spaces.
989
990 Examples:
991 1, 2, 10, 55, 100
992 01.jpg, 2.jpg, 10.jpg
993
994 Note on the algorithm:
995 Only as many characters as necessary are looked at and at most they all
996 are looked at once.
997
998 Slower then QString::compare() (of course)
999 */
1000int QFileSystemModelPrivate::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
1001{
1002 for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) {
1003 // skip spaces, tabs and 0's
1004 QChar c1 = getNextChar(s1, l1);
1005 while (c1.isSpace())
1006 c1 = getNextChar(s1, ++l1);
1007 QChar c2 = getNextChar(s2, l2);
1008 while (c2.isSpace())
1009 c2 = getNextChar(s2, ++l2);
1010
1011 if (c1.isDigit() && c2.isDigit()) {
1012 while (c1.digitValue() == 0)
1013 c1 = getNextChar(s1, ++l1);
1014 while (c2.digitValue() == 0)
1015 c2 = getNextChar(s2, ++l2);
1016
1017 int lookAheadLocation1 = l1;
1018 int lookAheadLocation2 = l2;
1019 int currentReturnValue = 0;
1020 // find the last digit, setting currentReturnValue as we go if it isn't equal
1021 for (
1022 QChar lookAhead1 = c1, lookAhead2 = c2;
1023 (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
1024 lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
1025 lookAhead2 = getNextChar(s2, ++lookAheadLocation2)
1026 ) {
1027 bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
1028 bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
1029 if (!is1ADigit && !is2ADigit)
1030 break;
1031 if (!is1ADigit)
1032 return -1;
1033 if (!is2ADigit)
1034 return 1;
1035 if (currentReturnValue == 0) {
1036 if (lookAhead1 < lookAhead2) {
1037 currentReturnValue = -1;
1038 } else if (lookAhead1 > lookAhead2) {
1039 currentReturnValue = 1;
1040 }
1041 }
1042 }
1043 if (currentReturnValue != 0)
1044 return currentReturnValue;
1045 }
1046
1047 if (cs == Qt::CaseInsensitive) {
1048 if (!c1.isLower()) c1 = c1.toLower();
1049 if (!c2.isLower()) c2 = c2.toLower();
1050 }
1051 int r = QString::localeAwareCompare(c1, c2);
1052 if (r < 0)
1053 return -1;
1054 if (r > 0)
1055 return 1;
1056 }
1057 // The two strings are the same (02 == 2) so fall back to the normal sort
1058 return QString::compare(s1, s2, cs);
1059}
1060
1061/*
1062 \internal
1063 Helper functor used by sort()
1064*/
1065class QFileSystemModelSorter
1066{
1067public:
1068 inline QFileSystemModelSorter(int column) : sortColumn(column) {}
1069
1070 bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l,
1071 const QFileSystemModelPrivate::QFileSystemNode *r) const
1072 {
1073 switch (sortColumn) {
1074 case 0: {
1075#ifndef Q_OS_MAC
1076 // place directories before files
1077 bool left = l->isDir();
1078 bool right = r->isDir();
1079 if (left ^ right)
1080 return left;
1081
1082 // place "." and ".." pseudo dirs on top
1083 if (left) {
1084 int lw = l->fileName == QLatin1String(".") ? 2 :
1085 l->fileName == QLatin1String("..") ? 1 : 0;
1086 int rw = r->fileName == QLatin1String(".") ? 2 :
1087 r->fileName == QLatin1String("..") ? 1 : 0;
1088 if (lw || rw)
1089 return lw > rw;
1090 }
1091#endif
1092 return QFileSystemModelPrivate::naturalCompare(l->fileName,
1093 r->fileName, Qt::CaseInsensitive) < 0;
1094 }
1095 case 1:
1096 // Directories go first
1097 if (l->isDir() && !r->isDir())
1098 return true;
1099 return l->size() < r->size();
1100 case 2:
1101 return l->type() < r->type();
1102 case 3:
1103 return l->lastModified() < r->lastModified();
1104 }
1105 Q_ASSERT(false);
1106 return false;
1107 }
1108
1109 bool operator()(const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &l,
1110 const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &r) const
1111 {
1112 return compareNodes(l.first, r.first);
1113 }
1114
1115
1116private:
1117 int sortColumn;
1118};
1119
1120/*
1121 \internal
1122
1123 Sort all of the children of parent
1124*/
1125void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent)
1126{
1127 Q_Q(QFileSystemModel);
1128 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent);
1129 if (indexNode->children.count() == 0)
1130 return;
1131
1132 QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > values;
1133 QHash<FileNameKey, QFileSystemNode *>::const_iterator iterator;
1134 int i = 0;
1135 for(iterator = indexNode->children.begin() ; iterator != indexNode->children.end() ; ++iterator) {
1136 if (filtersAcceptsNode(iterator.value())) {
1137 values.append(QPair<QFileSystemModelPrivate::QFileSystemNode*, int>((iterator.value()), i));
1138 } else {
1139 iterator.value()->isVisible = false;
1140 }
1141 i++;
1142 }
1143 QFileSystemModelSorter ms(column);
1144 qStableSort(values.begin(), values.end(), ms);
1145 // First update the new visible list
1146 indexNode->visibleChildren.clear();
1147 //No more dirty item we reset our internal dirty index
1148 indexNode->dirtyChildrenIndex = -1;
1149 for (int i = 0; i < values.count(); ++i) {
1150 indexNode->visibleChildren.append(values.at(i).first->fileName);
1151 values.at(i).first->isVisible = true;
1152 }
1153
1154 if (!disableRecursiveSort) {
1155 for (int i = 0; i < q->rowCount(parent); ++i) {
1156 const QModelIndex childIndex = q->index(i, 0, parent);
1157 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(childIndex);
1158 //Only do a recursive sort on visible nodes
1159 if (indexNode->isVisible)
1160 sortChildren(column, childIndex);
1161 }
1162 }
1163}
1164
1165/*!
1166 \reimp
1167*/
1168void QFileSystemModel::sort(int column, Qt::SortOrder order)
1169{
1170 Q_D(QFileSystemModel);
1171 if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
1172 return;
1173
1174 emit layoutAboutToBeChanged();
1175 QModelIndexList oldList = persistentIndexList();
1176 QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > oldNodes;
1177 for (int i = 0; i < oldList.count(); ++i) {
1178 QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(oldList.at(i)), oldList.at(i).column());
1179 oldNodes.append(pair);
1180 }
1181
1182 if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) {
1183 //we sort only from where we are, don't need to sort all the model
1184 d->sortChildren(column, index(rootPath()));
1185 d->sortColumn = column;
1186 d->forceSort = false;
1187 }
1188 d->sortOrder = order;
1189
1190 QModelIndexList newList;
1191 for (int i = 0; i < oldNodes.count(); ++i) {
1192 QModelIndex idx = d->index(oldNodes.at(i).first);
1193 idx = idx.sibling(idx.row(), oldNodes.at(i).second);
1194 newList.append(idx);
1195 }
1196 changePersistentIndexList(oldList, newList);
1197 emit layoutChanged();
1198}
1199
1200/*!
1201 Returns a list of MIME types that can be used to describe a list of items
1202 in the model.
1203*/
1204QStringList QFileSystemModel::mimeTypes() const
1205{
1206 return QStringList(QLatin1String("text/uri-list"));
1207}
1208
1209/*!
1210 Returns an object that contains a serialized description of the specified
1211 \a indexes. The format used to describe the items corresponding to the
1212 indexes is obtained from the mimeTypes() function.
1213
1214 If the list of indexes is empty, 0 is returned rather than a serialized
1215 empty list.
1216*/
1217QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const
1218{
1219 QList<QUrl> urls;
1220 QList<QModelIndex>::const_iterator it = indexes.begin();
1221 for (; it != indexes.end(); ++it)
1222 if ((*it).column() == 0)
1223 urls << QUrl::fromLocalFile(filePath(*it));
1224 QMimeData *data = new QMimeData();
1225 data->setUrls(urls);
1226 return data;
1227}
1228
1229/*!
1230 Handles the \a data supplied by a drag and drop operation that ended with
1231 the given \a action over the row in the model specified by the \a row and
1232 \a column and by the \a parent index.
1233
1234 \sa supportedDropActions()
1235*/
1236bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
1237 int row, int column, const QModelIndex &parent)
1238{
1239 Q_UNUSED(row);
1240 Q_UNUSED(column);
1241 if (!parent.isValid() || isReadOnly())
1242 return false;
1243
1244 bool success = true;
1245 QString to = filePath(parent) + QDir::separator();
1246
1247 QList<QUrl> urls = data->urls();
1248 QList<QUrl>::const_iterator it = urls.constBegin();
1249
1250 switch (action) {
1251 case Qt::CopyAction:
1252 for (; it != urls.constEnd(); ++it) {
1253 QString path = (*it).toLocalFile();
1254 success = QFile::copy(path, to + QFileInfo(path).fileName()) && success;
1255 }
1256 break;
1257 case Qt::LinkAction:
1258 for (; it != urls.constEnd(); ++it) {
1259 QString path = (*it).toLocalFile();
1260 success = QFile::link(path, to + QFileInfo(path).fileName()) && success;
1261 }
1262 break;
1263 case Qt::MoveAction:
1264 for (; it != urls.constEnd(); ++it) {
1265 QString path = (*it).toLocalFile();
1266 success = QFile::rename(path, to + QFileInfo(path).fileName()) && success;
1267 }
1268 break;
1269 default:
1270 return false;
1271 }
1272
1273 return success;
1274}
1275
1276/*!
1277 \reimp
1278*/
1279Qt::DropActions QFileSystemModel::supportedDropActions() const
1280{
1281 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
1282}
1283
1284/*!
1285 Returns the path of the item stored in the model under the
1286 \a index given.
1287*/
1288QString QFileSystemModel::filePath(const QModelIndex &index) const
1289{
1290 Q_D(const QFileSystemModel);
1291 QString fullPath = d->filePath(index);
1292 QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
1293 if (dirNode->isSymLink() && d->fileInfoGatherer.resolveSymlinks()
1294 && d->resolvedSymLinks.contains(fullPath)
1295 && dirNode->isDir()) {
1296 QFileInfo resolvedInfo(fullPath);
1297 resolvedInfo = resolvedInfo.canonicalFilePath();
1298 if (resolvedInfo.exists())
1299 return resolvedInfo.filePath();
1300 }
1301 return fullPath;
1302}
1303
1304QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const
1305{
1306 Q_Q(const QFileSystemModel);
1307 Q_UNUSED(q);
1308 if (!index.isValid())
1309 return QString();
1310 Q_ASSERT(index.model() == q);
1311
1312 QStringList path;
1313 QModelIndex idx = index;
1314 while (idx.isValid()) {
1315 QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx);
1316 if (dirNode)
1317 path.prepend(dirNode->fileName);
1318 idx = idx.parent();
1319 }
1320 QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator()));
1321#if !defined(Q_OS_OS2) && (!defined(Q_OS_WIN) || defined(Q_OS_WINCE))
1322 if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/'))
1323 fullPath = fullPath.mid(1);
1324#endif
1325 return fullPath;
1326}
1327
1328/*!
1329 Create a directory with the \a name in the \a parent model index.
1330*/
1331QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name)
1332{
1333 Q_D(QFileSystemModel);
1334 if (!parent.isValid())
1335 return parent;
1336
1337 QDir dir(filePath(parent));
1338 if (!dir.mkdir(name))
1339 return QModelIndex();
1340 QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
1341 d->addNode(parentNode, name, QFileInfo());
1342 Q_ASSERT(parentNode->children.contains(name));
1343 QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name];
1344 node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name)));
1345 d->addVisibleFiles(parentNode, QStringList(name));
1346 return d->index(node);
1347}
1348
1349/*!
1350 Returns the complete OR-ed together combination of QFile::Permission for the \a index.
1351 */
1352QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const
1353{
1354 Q_D(const QFileSystemModel);
1355 QFile::Permissions p = d->node(index)->permissions();
1356 if (d->readOnly) {
1357 p ^= (QFile::WriteOwner | QFile::WriteUser
1358 | QFile::WriteGroup | QFile::WriteOther);
1359 }
1360 return p;
1361}
1362
1363/*!
1364 Sets the directory that is being watched by the model to \a newPath by
1365 installing a \l{QFileSystemWatcher}{file system watcher} on it. Any
1366 changes to files and directories within this directory will be
1367 reflected in the model.
1368
1369 If the path is changed, the rootPathChanged() signal will be emitted.
1370
1371 \note This function does not change the structure of the model or
1372 modify the data available to views. In other words, the "root" of
1373 the model is \e not changed to include only files and directories
1374 within the directory specified by \a newPath in the file system.
1375 */
1376QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
1377{
1378 Q_D(QFileSystemModel);
1379#if defined(Q_OS_WIN32)
1380 QString longNewPath = qt_GetLongPathName(newPath);
1381#elif defined(Q_OS_WIN) || defined(Q_OS_OS2)
1382 QString longNewPath = QDir::fromNativeSeparators(newPath);
1383#else
1384 QString longNewPath = newPath;
1385#endif
1386 QDir newPathDir(longNewPath);
1387 //we remove .. and . from the given path if exist
1388 if (!newPath.isEmpty()) {
1389 longNewPath = QDir::cleanPath(longNewPath);
1390 newPathDir.setPath(longNewPath);
1391 }
1392
1393 d->setRootPath = true;
1394
1395 //user don't ask for the root path ("") but the conversion failed
1396 if (!newPath.isEmpty() && longNewPath.isEmpty())
1397 return d->index(rootPath());
1398
1399 if (d->rootDir.path() == longNewPath)
1400 return d->index(rootPath());
1401
1402 bool showDrives = (longNewPath.isEmpty() || longNewPath == d->myComputer());
1403 if (!showDrives && !newPathDir.exists())
1404 return d->index(rootPath());
1405
1406 //We remove the watcher on the previous path
1407 if (!rootPath().isEmpty() && rootPath() != QLatin1String(".")) {
1408 //This remove the watcher for the old rootPath
1409 d->fileInfoGatherer.removePath(rootPath());
1410 //This line "marks" the node as dirty, so the next fetchMore
1411 //call on the path will ask the gatherer to install a watcher again
1412 //But it doesn't re-fetch everything
1413 d->node(rootPath())->populatedChildren = false;
1414 }
1415
1416 // We have a new valid root path
1417 d->rootDir = newPathDir;
1418 QModelIndex newRootIndex;
1419 if (showDrives) {
1420 // otherwise dir will become '.'
1421 d->rootDir.setPath(QLatin1String(""));
1422 } else {
1423 newRootIndex = d->index(newPathDir.path());
1424 }
1425 fetchMore(newRootIndex);
1426 emit rootPathChanged(longNewPath);
1427 d->forceSort = true;
1428 d->delayedSort();
1429 return newRootIndex;
1430}
1431
1432/*!
1433 The currently set root path
1434
1435 \sa rootDirectory()
1436*/
1437QString QFileSystemModel::rootPath() const
1438{
1439 Q_D(const QFileSystemModel);
1440 return d->rootDir.path();
1441}
1442
1443/*!
1444 The currently set directory
1445
1446 \sa rootPath()
1447*/
1448QDir QFileSystemModel::rootDirectory() const
1449{
1450 Q_D(const QFileSystemModel);
1451 QDir dir(d->rootDir);
1452 dir.setNameFilters(nameFilters());
1453 dir.setFilter(filter());
1454 return dir;
1455}
1456
1457/*!
1458 Sets the \a provider of file icons for the directory model.
1459*/
1460void QFileSystemModel::setIconProvider(QFileIconProvider *provider)
1461{
1462 Q_D(QFileSystemModel);
1463 d->fileInfoGatherer.setIconProvider(provider);
1464 QApplication::processEvents();
1465 d->root.updateIcon(provider, QString());
1466}
1467
1468/*!
1469 Returns the file icon provider for this directory model.
1470*/
1471QFileIconProvider *QFileSystemModel::iconProvider() const
1472{
1473 Q_D(const QFileSystemModel);
1474 return d->fileInfoGatherer.iconProvider();
1475}
1476
1477/*!
1478 Sets the directory model's filter to that specified by \a filters.
1479
1480 Note that the filter you set should always include the QDir::AllDirs enum value,
1481 otherwise QFileSystemModel won't be able to read the directory structure.
1482
1483 \sa QDir::Filters
1484*/
1485void QFileSystemModel::setFilter(QDir::Filters filters)
1486{
1487 Q_D(QFileSystemModel);
1488 if (d->filters == filters)
1489 return;
1490 d->filters = filters;
1491 // CaseSensitivity might have changed
1492 setNameFilters(nameFilters());
1493 d->forceSort = true;
1494 d->delayedSort();
1495}
1496
1497/*!
1498 Returns the filter specified for the directory model.
1499
1500 If a filter has not been set, the default filter is QDir::AllEntries |
1501 QDir::NoDotAndDotDot | QDir::AllDirs.
1502
1503 \sa QDir::Filters
1504*/
1505QDir::Filters QFileSystemModel::filter() const
1506{
1507 Q_D(const QFileSystemModel);
1508 return d->filters;
1509}
1510
1511/*!
1512 \property QFileSystemModel::resolveSymlinks
1513 \brief Whether the directory model should resolve symbolic links
1514
1515 This is only relevant on operating systems that support symbolic links.
1516
1517 By default, this property is false.
1518*/
1519void QFileSystemModel::setResolveSymlinks(bool enable)
1520{
1521 Q_D(QFileSystemModel);
1522 d->fileInfoGatherer.setResolveSymlinks(enable);
1523}
1524
1525bool QFileSystemModel::resolveSymlinks() const
1526{
1527 Q_D(const QFileSystemModel);
1528 return d->fileInfoGatherer.resolveSymlinks();
1529}
1530
1531/*!
1532 \property QFileSystemModel::readOnly
1533 \brief Whether the directory model allows writing to the file system
1534
1535 If this property is set to false, the directory model will allow renaming, copying
1536 and deleting of files and directories.
1537
1538 This property is true by default
1539*/
1540void QFileSystemModel::setReadOnly(bool enable)
1541{
1542 Q_D(QFileSystemModel);
1543 d->readOnly = enable;
1544}
1545
1546bool QFileSystemModel::isReadOnly() const
1547{
1548 Q_D(const QFileSystemModel);
1549 return d->readOnly;
1550}
1551
1552/*!
1553 \property QFileSystemModel::nameFilterDisables
1554 \brief Whether files that don't pass the name filter are hidden or disabled
1555
1556 This property is true by default
1557*/
1558void QFileSystemModel::setNameFilterDisables(bool enable)
1559{
1560 Q_D(QFileSystemModel);
1561 if (d->nameFilterDisables == enable)
1562 return;
1563 d->nameFilterDisables = enable;
1564 d->forceSort = true;
1565 d->delayedSort();
1566}
1567
1568bool QFileSystemModel::nameFilterDisables() const
1569{
1570 Q_D(const QFileSystemModel);
1571 return d->nameFilterDisables;
1572}
1573
1574/*!
1575 Sets the name \a filters to apply against the existing files.
1576*/
1577void QFileSystemModel::setNameFilters(const QStringList &filters)
1578{
1579 // Prep the regexp's ahead of time
1580#ifndef QT_NO_REGEXP
1581 Q_D(QFileSystemModel);
1582
1583 if (!d->bypassFilters.isEmpty()) {
1584 // update the bypass filter to only bypass the stuff that must be kept around
1585 d->bypassFilters.clear();
1586 // We guarantee that rootPath will stick around
1587 QPersistentModelIndex root(index(rootPath()));
1588 QModelIndexList persistantList = persistentIndexList();
1589 for (int i = 0; i < persistantList.count(); ++i) {
1590 QFileSystemModelPrivate::QFileSystemNode *node;
1591 node = d->node(persistantList.at(i));
1592 while (node) {
1593 if (d->bypassFilters.contains(node))
1594 break;
1595 if (node->isDir())
1596 d->bypassFilters[node] = true;
1597 node = node->parent;
1598 }
1599 }
1600 }
1601
1602 d->nameFilters.clear();
1603 const Qt::CaseSensitivity caseSensitive =
1604 (filter() & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
1605 for (int i = 0; i < filters.size(); ++i) {
1606 d->nameFilters << QRegExp(filters.at(i), caseSensitive, QRegExp::Wildcard);
1607 }
1608 d->forceSort = true;
1609 d->delayedSort();
1610#endif
1611}
1612
1613/*!
1614 Returns a list of filters applied to the names in the model.
1615*/
1616QStringList QFileSystemModel::nameFilters() const
1617{
1618 Q_D(const QFileSystemModel);
1619 QStringList filters;
1620#ifndef QT_NO_REGEXP
1621 for (int i = 0; i < d->nameFilters.size(); ++i) {
1622 filters << d->nameFilters.at(i).pattern();
1623 }
1624#endif
1625 return filters;
1626}
1627
1628/*!
1629 \reimp
1630*/
1631bool QFileSystemModel::event(QEvent *event)
1632{
1633 Q_D(QFileSystemModel);
1634 if (event->type() == QEvent::LanguageChange) {
1635 d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString());
1636 return true;
1637 }
1638 return QAbstractItemModel::event(event);
1639}
1640
1641/*!
1642 \internal
1643
1644 Performed quick listing and see if any files have been added or removed,
1645 then fetch more information on visible files.
1646 */
1647void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files)
1648{
1649 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
1650 if (parentNode->children.count() == 0)
1651 return;
1652 QStringList toRemove;
1653#if defined(Q_OS_SYMBIAN)
1654 // Filename case must be exact in qBinaryFind below, so create a list of all lowercase names.
1655 QStringList newFiles;
1656 for(int i = 0; i < files.size(); i++) {
1657 newFiles << files.at(i).toLower();
1658 }
1659#else
1660 QStringList newFiles = files;
1661#endif
1662 qSort(newFiles.begin(), newFiles.end());
1663 QHash<FileNameKey, QFileSystemNode*>::const_iterator i = parentNode->children.constBegin();
1664 while (i != parentNode->children.constEnd()) {
1665 QStringList::iterator iterator;
1666 iterator = qBinaryFind(newFiles.begin(), newFiles.end(),
1667#if defined(Q_OS_SYMBIAN)
1668 i.value()->fileName.toLower());
1669#else
1670 i.value()->fileName);
1671#endif
1672 if (iterator == newFiles.end()) {
1673 toRemove.append(i.value()->fileName);
1674 }
1675 ++i;
1676 }
1677 for (int i = 0 ; i < toRemove.count() ; ++i )
1678 removeNode(parentNode, toRemove[i]);
1679}
1680
1681/*!
1682 \internal
1683
1684 Adds a new file to the children of parentNode
1685
1686 *WARNING* this will change the count of children
1687*/
1688QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1689{
1690 // In the common case, itemLocation == count() so check there first
1691 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1692#ifndef QT_NO_FILESYSTEMWATCHER
1693 node->populate(info);
1694#endif
1695#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
1696 //The parentNode is "" so we are listing the drives
1697 if (parentNode->fileName.isEmpty()) {
1698 wchar_t name[MAX_PATH + 1];
1699 //GetVolumeInformation requires to add trailing backslash
1700 const QString nodeName = fileName + QLatin1String("\\");
1701 BOOL success = ::GetVolumeInformation((wchar_t *)(nodeName.utf16()),
1702 name, MAX_PATH + 1, NULL, 0, NULL, NULL, 0);
1703 if (success && name[0])
1704 node->volumeName = QString::fromWCharArray(name);
1705 }
1706#endif
1707 parentNode->children.insert(fileName, node);
1708 return node;
1709}
1710
1711/*!
1712 \internal
1713
1714 File at parentNode->children(itemLocation) has been removed, remove from the lists
1715 and emit signals if necessary
1716
1717 *WARNING* this will change the count of children and could change visibleChildren
1718 */
1719void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1720{
1721 Q_Q(QFileSystemModel);
1722 QModelIndex parent = index(parentNode);
1723 bool indexHidden = isHiddenByFilter(parentNode, parent);
1724
1725 int vLocation = parentNode->visibleLocation(name);
1726 if (vLocation >= 0 && !indexHidden)
1727 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1728 translateVisibleLocation(parentNode, vLocation));
1729 QFileSystemNode * node = parentNode->children.take(name);
1730 delete node;
1731 // cleanup sort files after removing rather then re-sorting which is O(n)
1732 if (vLocation >= 0)
1733 parentNode->visibleChildren.removeAt(vLocation);
1734 if (vLocation >= 0 && !indexHidden)
1735 q->endRemoveRows();
1736}
1737
1738/*
1739 \internal
1740 Helper functor used by addVisibleFiles()
1741*/
1742class QFileSystemModelVisibleFinder
1743{
1744public:
1745 inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode *node, QFileSystemModelSorter *sorter) : parentNode(node), sorter(sorter) {}
1746
1747 bool operator()(const QString &, QString r) const
1748 {
1749 return sorter->compareNodes(parentNode->children.value(name), parentNode->children.value(r));
1750 }
1751
1752 QString name;
1753private:
1754 QFileSystemModelPrivate::QFileSystemNode *parentNode;
1755 QFileSystemModelSorter *sorter;
1756};
1757
1758/*!
1759 \internal
1760
1761 File at parentNode->children(itemLocation) was not visible before, but now should be
1762 and emit signals if necessary.
1763
1764 *WARNING* this will change the visible count
1765 */
1766void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1767{
1768 Q_Q(QFileSystemModel);
1769 QModelIndex parent = index(parentNode);
1770 bool indexHidden = isHiddenByFilter(parentNode, parent);
1771 if (!indexHidden) {
1772 q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1);
1773 }
1774
1775 if (parentNode->dirtyChildrenIndex == -1)
1776 parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count();
1777
1778 for (int i = 0; i < newFiles.count(); ++i) {
1779 parentNode->visibleChildren.append(newFiles.at(i));
1780 parentNode->children[newFiles.at(i)]->isVisible = true;
1781 }
1782 if (!indexHidden)
1783 q->endInsertRows();
1784}
1785
1786/*!
1787 \internal
1788
1789 File was visible before, but now should NOT be
1790
1791 *WARNING* this will change the visible count
1792 */
1793void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1794{
1795 Q_Q(QFileSystemModel);
1796 if (vLocation == -1)
1797 return;
1798 QModelIndex parent = index(parentNode);
1799 bool indexHidden = isHiddenByFilter(parentNode, parent);
1800 if (!indexHidden)
1801 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1802 translateVisibleLocation(parentNode, vLocation));
1803 parentNode->children[parentNode->visibleChildren.at(vLocation)]->isVisible = false;
1804 parentNode->visibleChildren.removeAt(vLocation);
1805 if (!indexHidden)
1806 q->endRemoveRows();
1807}
1808
1809/*!
1810 \internal
1811
1812 The thread has received new information about files,
1813 update and emit dataChanged if it has actually changed.
1814 */
1815void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &updates)
1816{
1817 Q_Q(QFileSystemModel);
1818 QVector<QString> rowsToUpdate;
1819 QStringList newFiles;
1820 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false);
1821 QModelIndex parentIndex = index(parentNode);
1822 for (int i = 0; i < updates.count(); ++i) {
1823 QString fileName = updates.at(i).first;
1824 Q_ASSERT(!fileName.isEmpty());
1825 QExtendedInformation info = fileInfoGatherer.getInfo(updates.at(i).second);
1826 bool previouslyHere = parentNode->children.contains(fileName);
1827 if (!previouslyHere) {
1828 addNode(parentNode, fileName, info.fileInfo());
1829 }
1830 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName);
1831 bool isCaseSensitive = parentNode->caseSensitive();
1832 if (isCaseSensitive) {
1833 if (node->fileName != fileName)
1834 continue;
1835 } else {
1836 if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0)
1837 continue;
1838 }
1839 if (isCaseSensitive) {
1840 Q_ASSERT(node->fileName == fileName);
1841 } else {
1842 node->fileName = fileName;
1843 }
1844#ifdef Q_OS_OS2
1845 // remove the invalid (non-existent) file unless it's a drive letter
1846 // (note that QFileInfoGatherer doesn't report invalid drive letters so
1847 // info.size() = -1 usually means that there is no media inserted etc.,
1848 // which is not a sufficient reason to hide the drive from the view)
1849 if (info.size() == -1 && !info.isSymLink() && parentNode != &root) {
1850#else
1851 if (info.size() == -1 && !info.isSymLink()) {
1852#endif
1853 removeNode(parentNode, fileName);
1854 continue;
1855 }
1856 if (*node != info ) {
1857 node->populate(info);
1858 bypassFilters.remove(node);
1859 // brand new information.
1860 if (filtersAcceptsNode(node)) {
1861 if (!node->isVisible) {
1862 newFiles.append(fileName);
1863 } else {
1864 rowsToUpdate.append(fileName);
1865 }
1866 } else {
1867 if (node->isVisible) {
1868 int visibleLocation = parentNode->visibleLocation(fileName);
1869 removeVisibleFile(parentNode, visibleLocation);
1870 } else {
1871 // The file is not visible, don't do anything
1872 }
1873 }
1874 }
1875 }
1876
1877 // bundle up all of the changed signals into as few as possible.
1878 qSort(rowsToUpdate.begin(), rowsToUpdate.end());
1879 QString min;
1880 QString max;
1881 for (int i = 0; i < rowsToUpdate.count(); ++i) {
1882 QString value = rowsToUpdate.at(i);
1883 //##TODO is there a way to bundle signals with QString as the content of the list?
1884 /*if (min.isEmpty()) {
1885 min = value;
1886 if (i != rowsToUpdate.count() - 1)
1887 continue;
1888 }
1889 if (i != rowsToUpdate.count() - 1) {
1890 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
1891 max = value;
1892 continue;
1893 }
1894 }*/
1895 max = value;
1896 min = value;
1897 int visibleMin = parentNode->visibleLocation(min);
1898 int visibleMax = parentNode->visibleLocation(max);
1899 if (visibleMin >= 0
1900 && visibleMin < parentNode->visibleChildren.count()
1901 && parentNode->visibleChildren.at(visibleMin) == min
1902 && visibleMax >= 0) {
1903 QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex);
1904 QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex);
1905 emit q->dataChanged(bottom, top);
1906 }
1907
1908 /*min = QString();
1909 max = QString();*/
1910 }
1911
1912 if (newFiles.count() > 0) {
1913 addVisibleFiles(parentNode, newFiles);
1914 }
1915
1916 if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) {
1917 forceSort = true;
1918 delayedSort();
1919 }
1920}
1921
1922/*!
1923 \internal
1924*/
1925void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
1926{
1927 resolvedSymLinks[fileName] = resolvedName;
1928}
1929
1930/*!
1931 \internal
1932*/
1933void QFileSystemModelPrivate::init()
1934{
1935 Q_Q(QFileSystemModel);
1936 qRegisterMetaType<QList<QPair<QString,QFileInfo> > >("QList<QPair<QString,QFileInfo> >");
1937 q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)),
1938 q, SLOT(_q_directoryChanged(QString,QStringList)));
1939 q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList<QPair<QString,QFileInfo> >)),
1940 q, SLOT(_q_fileSystemChanged(QString,QList<QPair<QString,QFileInfo> >)));
1941 q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)),
1942 q, SLOT(_q_resolvedName(QString,QString)));
1943 q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)),
1944 q, SIGNAL(directoryLoaded(QString)));
1945 q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
1946
1947 QHash<int, QByteArray> roles = q->roleNames();
1948 roles.insertMulti(QFileSystemModel::FileIconRole, "fileIcon"); // == Qt::decoration
1949 roles.insert(QFileSystemModel::FilePathRole, "filePath");
1950 roles.insert(QFileSystemModel::FileNameRole, "fileName");
1951 roles.insert(QFileSystemModel::FilePermissions, "filePermissions");
1952 q->setRoleNames(roles);
1953}
1954
1955/*!
1956 \internal
1957
1958 Returns false if node doesn't pass the filters otherwise true
1959
1960 QDir::Modified is not supported
1961 QDir::Drives is not supported
1962*/
1963bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
1964{
1965 // always accept drives
1966 if (node->parent == &root || bypassFilters.contains(node))
1967 return true;
1968
1969 // If we don't know anything yet don't accept it
1970 if (!node->hasInformation())
1971 return false;
1972
1973 const bool filterPermissions = ((filters & QDir::PermissionMask)
1974 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
1975 const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
1976 const bool hideFiles = !(filters & QDir::Files);
1977 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
1978 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
1979 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
1980 const bool hideHidden = !(filters & QDir::Hidden);
1981 const bool hideSystem = !(filters & QDir::System);
1982 const bool hideSymlinks = (filters & QDir::NoSymLinks);
1983 const bool hideDotAndDotDot = (filters & QDir::NoDotAndDotDot);
1984
1985 // Note that we match the behavior of entryList and not QFileInfo on this and this
1986 // incompatibility won't be fixed until Qt 5 at least
1987 bool isDotOrDot = ( (node->fileName == QLatin1String(".")
1988 || node->fileName == QLatin1String("..")));
1989 if ( (hideHidden && (!isDotOrDot && node->isHidden()))
1990 || (hideSystem && node->isSystem())
1991 || (hideDirs && node->isDir())
1992 || (hideFiles && node->isFile())
1993 || (hideSymlinks && node->isSymLink())
1994 || (hideReadable && node->isReadable())
1995 || (hideWritable && node->isWritable())
1996 || (hideExecutable && node->isExecutable())
1997 || (hideDotAndDotDot && isDotOrDot))
1998 return false;
1999
2000 return nameFilterDisables || passNameFilters(node);
2001}
2002
2003/*
2004 \internal
2005
2006 Returns true if node passes the name filters and should be visible.
2007 */
2008bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
2009{
2010#ifndef QT_NO_REGEXP
2011 if (nameFilters.isEmpty())
2012 return true;
2013
2014 // Check the name regularexpression filters
2015 if (!(node->isDir() && (filters & QDir::AllDirs))) {
2016 for (int i = 0; i < nameFilters.size(); ++i) {
2017 if (nameFilters.at(i).exactMatch(node->fileName))
2018 return true;
2019 }
2020 return false;
2021 }
2022#endif
2023 return true;
2024}
2025
2026QT_END_NAMESPACE
2027
2028#include "moc_qfilesystemmodel.cpp"
2029
2030#endif // QT_NO_FILESYSTEMMODEL
Note: See TracBrowser for help on using the repository browser.