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

Last change on this file since 858 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 64.6 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#endif
1082 return QFileSystemModelPrivate::naturalCompare(l->fileName,
1083 r->fileName, Qt::CaseInsensitive) < 0;
1084 }
1085 case 1:
1086 // Directories go first
1087 if (l->isDir() && !r->isDir())
1088 return true;
1089 return l->size() < r->size();
1090 case 2:
1091 return l->type() < r->type();
1092 case 3:
1093 return l->lastModified() < r->lastModified();
1094 }
1095 Q_ASSERT(false);
1096 return false;
1097 }
1098
1099 bool operator()(const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &l,
1100 const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &r) const
1101 {
1102 return compareNodes(l.first, r.first);
1103 }
1104
1105
1106private:
1107 int sortColumn;
1108};
1109
1110/*
1111 \internal
1112
1113 Sort all of the children of parent
1114*/
1115void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent)
1116{
1117 Q_Q(QFileSystemModel);
1118 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent);
1119 if (indexNode->children.count() == 0)
1120 return;
1121
1122 QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > values;
1123 QHash<FileNameKey, QFileSystemNode *>::const_iterator iterator;
1124 int i = 0;
1125 for(iterator = indexNode->children.begin() ; iterator != indexNode->children.end() ; ++iterator) {
1126 if (filtersAcceptsNode(iterator.value())) {
1127 values.append(QPair<QFileSystemModelPrivate::QFileSystemNode*, int>((iterator.value()), i));
1128 } else {
1129 iterator.value()->isVisible = false;
1130 }
1131 i++;
1132 }
1133 QFileSystemModelSorter ms(column);
1134 qStableSort(values.begin(), values.end(), ms);
1135 // First update the new visible list
1136 indexNode->visibleChildren.clear();
1137 //No more dirty item we reset our internal dirty index
1138 indexNode->dirtyChildrenIndex = -1;
1139 for (int i = 0; i < values.count(); ++i) {
1140 indexNode->visibleChildren.append(values.at(i).first->fileName);
1141 values.at(i).first->isVisible = true;
1142 }
1143
1144 if (!disableRecursiveSort) {
1145 for (int i = 0; i < q->rowCount(parent); ++i) {
1146 const QModelIndex childIndex = q->index(i, 0, parent);
1147 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(childIndex);
1148 //Only do a recursive sort on visible nodes
1149 if (indexNode->isVisible)
1150 sortChildren(column, childIndex);
1151 }
1152 }
1153}
1154
1155/*!
1156 \reimp
1157*/
1158void QFileSystemModel::sort(int column, Qt::SortOrder order)
1159{
1160 Q_D(QFileSystemModel);
1161 if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
1162 return;
1163
1164 emit layoutAboutToBeChanged();
1165 QModelIndexList oldList = persistentIndexList();
1166 QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > oldNodes;
1167 for (int i = 0; i < oldList.count(); ++i) {
1168 QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(oldList.at(i)), oldList.at(i).column());
1169 oldNodes.append(pair);
1170 }
1171
1172 if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) {
1173 //we sort only from where we are, don't need to sort all the model
1174 d->sortChildren(column, index(rootPath()));
1175 d->sortColumn = column;
1176 d->forceSort = false;
1177 }
1178 d->sortOrder = order;
1179
1180 QModelIndexList newList;
1181 for (int i = 0; i < oldNodes.count(); ++i) {
1182 QModelIndex idx = d->index(oldNodes.at(i).first);
1183 idx = idx.sibling(idx.row(), oldNodes.at(i).second);
1184 newList.append(idx);
1185 }
1186 changePersistentIndexList(oldList, newList);
1187 emit layoutChanged();
1188}
1189
1190/*!
1191 Returns a list of MIME types that can be used to describe a list of items
1192 in the model.
1193*/
1194QStringList QFileSystemModel::mimeTypes() const
1195{
1196 return QStringList(QLatin1String("text/uri-list"));
1197}
1198
1199/*!
1200 Returns an object that contains a serialized description of the specified
1201 \a indexes. The format used to describe the items corresponding to the
1202 indexes is obtained from the mimeTypes() function.
1203
1204 If the list of indexes is empty, 0 is returned rather than a serialized
1205 empty list.
1206*/
1207QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const
1208{
1209 QList<QUrl> urls;
1210 QList<QModelIndex>::const_iterator it = indexes.begin();
1211 for (; it != indexes.end(); ++it)
1212 if ((*it).column() == 0)
1213 urls << QUrl::fromLocalFile(filePath(*it));
1214 QMimeData *data = new QMimeData();
1215 data->setUrls(urls);
1216 return data;
1217}
1218
1219/*!
1220 Handles the \a data supplied by a drag and drop operation that ended with
1221 the given \a action over the row in the model specified by the \a row and
1222 \a column and by the \a parent index.
1223
1224 \sa supportedDropActions()
1225*/
1226bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
1227 int row, int column, const QModelIndex &parent)
1228{
1229 Q_UNUSED(row);
1230 Q_UNUSED(column);
1231 if (!parent.isValid() || isReadOnly())
1232 return false;
1233
1234 bool success = true;
1235 QString to = filePath(parent) + QDir::separator();
1236
1237 QList<QUrl> urls = data->urls();
1238 QList<QUrl>::const_iterator it = urls.constBegin();
1239
1240 switch (action) {
1241 case Qt::CopyAction:
1242 for (; it != urls.constEnd(); ++it) {
1243 QString path = (*it).toLocalFile();
1244 success = QFile::copy(path, to + QFileInfo(path).fileName()) && success;
1245 }
1246 break;
1247 case Qt::LinkAction:
1248 for (; it != urls.constEnd(); ++it) {
1249 QString path = (*it).toLocalFile();
1250 success = QFile::link(path, to + QFileInfo(path).fileName()) && success;
1251 }
1252 break;
1253 case Qt::MoveAction:
1254 for (; it != urls.constEnd(); ++it) {
1255 QString path = (*it).toLocalFile();
1256 success = QFile::rename(path, to + QFileInfo(path).fileName()) && success;
1257 }
1258 break;
1259 default:
1260 return false;
1261 }
1262
1263 return success;
1264}
1265
1266/*!
1267 \reimp
1268*/
1269Qt::DropActions QFileSystemModel::supportedDropActions() const
1270{
1271 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
1272}
1273
1274/*!
1275 Returns the path of the item stored in the model under the
1276 \a index given.
1277*/
1278QString QFileSystemModel::filePath(const QModelIndex &index) const
1279{
1280 Q_D(const QFileSystemModel);
1281 QString fullPath = d->filePath(index);
1282 QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
1283 if (dirNode->isSymLink() && d->fileInfoGatherer.resolveSymlinks()
1284 && d->resolvedSymLinks.contains(fullPath)
1285 && dirNode->isDir()) {
1286 QFileInfo resolvedInfo(fullPath);
1287 resolvedInfo = resolvedInfo.canonicalFilePath();
1288 if (resolvedInfo.exists())
1289 return resolvedInfo.filePath();
1290 }
1291 return fullPath;
1292}
1293
1294QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const
1295{
1296 Q_Q(const QFileSystemModel);
1297 Q_UNUSED(q);
1298 if (!index.isValid())
1299 return QString();
1300 Q_ASSERT(index.model() == q);
1301
1302 QStringList path;
1303 QModelIndex idx = index;
1304 while (idx.isValid()) {
1305 QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx);
1306 if (dirNode)
1307 path.prepend(dirNode->fileName);
1308 idx = idx.parent();
1309 }
1310 QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator()));
1311#if !defined(Q_OS_OS2) && (!defined(Q_OS_WIN) || defined(Q_OS_WINCE))
1312 if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/'))
1313 fullPath = fullPath.mid(1);
1314#endif
1315 return fullPath;
1316}
1317
1318/*!
1319 Create a directory with the \a name in the \a parent model index.
1320*/
1321QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name)
1322{
1323 Q_D(QFileSystemModel);
1324 if (!parent.isValid())
1325 return parent;
1326
1327 QDir dir(filePath(parent));
1328 if (!dir.mkdir(name))
1329 return QModelIndex();
1330 QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
1331 d->addNode(parentNode, name, QFileInfo());
1332 Q_ASSERT(parentNode->children.contains(name));
1333 QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name];
1334 node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name)));
1335 d->addVisibleFiles(parentNode, QStringList(name));
1336 return d->index(node);
1337}
1338
1339/*!
1340 Returns the complete OR-ed together combination of QFile::Permission for the \a index.
1341 */
1342QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const
1343{
1344 Q_D(const QFileSystemModel);
1345 QFile::Permissions p = d->node(index)->permissions();
1346 if (d->readOnly) {
1347 p ^= (QFile::WriteOwner | QFile::WriteUser
1348 | QFile::WriteGroup | QFile::WriteOther);
1349 }
1350 return p;
1351}
1352
1353/*!
1354 Sets the directory that is being watched by the model to \a newPath by
1355 installing a \l{QFileSystemWatcher}{file system watcher} on it. Any
1356 changes to files and directories within this directory will be
1357 reflected in the model.
1358
1359 If the path is changed, the rootPathChanged() signal will be emitted.
1360
1361 \note This function does not change the structure of the model or
1362 modify the data available to views. In other words, the "root" of
1363 the model is \e not changed to include only files and directories
1364 within the directory specified by \a newPath in the file system.
1365 */
1366QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
1367{
1368 Q_D(QFileSystemModel);
1369#ifdef Q_OS_WIN
1370#ifdef Q_OS_WIN32
1371 QString longNewPath = qt_GetLongPathName(newPath);
1372#else
1373 QString longNewPath = QDir::fromNativeSeparators(newPath);
1374#endif
1375#else
1376 QString longNewPath = newPath;
1377#endif
1378 QDir newPathDir(longNewPath);
1379 //we remove .. and . from the given path if exist
1380 if (!newPath.isEmpty()) {
1381 longNewPath = QDir::cleanPath(longNewPath);
1382 newPathDir.setPath(longNewPath);
1383 }
1384
1385 d->setRootPath = true;
1386
1387 //user don't ask for the root path ("") but the conversion failed
1388 if (!newPath.isEmpty() && longNewPath.isEmpty())
1389 return d->index(rootPath());
1390
1391 if (d->rootDir.path() == longNewPath)
1392 return d->index(rootPath());
1393
1394 bool showDrives = (longNewPath.isEmpty() || longNewPath == d->myComputer());
1395 if (!showDrives && !newPathDir.exists())
1396 return d->index(rootPath());
1397
1398 //We remove the watcher on the previous path
1399 if (!rootPath().isEmpty() && rootPath() != QLatin1String(".")) {
1400 //This remove the watcher for the old rootPath
1401 d->fileInfoGatherer.removePath(rootPath());
1402 //This line "marks" the node as dirty, so the next fetchMore
1403 //call on the path will ask the gatherer to install a watcher again
1404 //But it doesn't re-fetch everything
1405 d->node(rootPath())->populatedChildren = false;
1406 }
1407
1408 // We have a new valid root path
1409 d->rootDir = newPathDir;
1410 QModelIndex newRootIndex;
1411 if (showDrives) {
1412 // otherwise dir will become '.'
1413 d->rootDir.setPath(QLatin1String(""));
1414 } else {
1415 newRootIndex = d->index(newPathDir.path());
1416 }
1417 fetchMore(newRootIndex);
1418 emit rootPathChanged(longNewPath);
1419 d->forceSort = true;
1420 d->delayedSort();
1421 return newRootIndex;
1422}
1423
1424/*!
1425 The currently set root path
1426
1427 \sa rootDirectory()
1428*/
1429QString QFileSystemModel::rootPath() const
1430{
1431 Q_D(const QFileSystemModel);
1432 return d->rootDir.path();
1433}
1434
1435/*!
1436 The currently set directory
1437
1438 \sa rootPath()
1439*/
1440QDir QFileSystemModel::rootDirectory() const
1441{
1442 Q_D(const QFileSystemModel);
1443 QDir dir(d->rootDir);
1444 dir.setNameFilters(nameFilters());
1445 dir.setFilter(filter());
1446 return dir;
1447}
1448
1449/*!
1450 Sets the \a provider of file icons for the directory model.
1451*/
1452void QFileSystemModel::setIconProvider(QFileIconProvider *provider)
1453{
1454 Q_D(QFileSystemModel);
1455 d->fileInfoGatherer.setIconProvider(provider);
1456 QApplication::processEvents();
1457 d->root.updateIcon(provider, QString());
1458}
1459
1460/*!
1461 Returns the file icon provider for this directory model.
1462*/
1463QFileIconProvider *QFileSystemModel::iconProvider() const
1464{
1465 Q_D(const QFileSystemModel);
1466 return d->fileInfoGatherer.iconProvider();
1467}
1468
1469/*!
1470 Sets the directory model's filter to that specified by \a filters.
1471
1472 Note that the filter you set should always include the QDir::AllDirs enum value,
1473 otherwise QFileSystemModel won't be able to read the directory structure.
1474
1475 \sa QDir::Filters
1476*/
1477void QFileSystemModel::setFilter(QDir::Filters filters)
1478{
1479 Q_D(QFileSystemModel);
1480 if (d->filters == filters)
1481 return;
1482 d->filters = filters;
1483 // CaseSensitivity might have changed
1484 setNameFilters(nameFilters());
1485 d->forceSort = true;
1486 d->delayedSort();
1487}
1488
1489/*!
1490 Returns the filter specified for the directory model.
1491
1492 If a filter has not been set, the default filter is QDir::AllEntries |
1493 QDir::NoDotAndDotDot | QDir::AllDirs.
1494
1495 \sa QDir::Filters
1496*/
1497QDir::Filters QFileSystemModel::filter() const
1498{
1499 Q_D(const QFileSystemModel);
1500 return d->filters;
1501}
1502
1503/*!
1504 \property QFileSystemModel::resolveSymlinks
1505 \brief Whether the directory model should resolve symbolic links
1506
1507 This is only relevant on operating systems that support symbolic links.
1508
1509 By default, this property is false.
1510*/
1511void QFileSystemModel::setResolveSymlinks(bool enable)
1512{
1513 Q_D(QFileSystemModel);
1514 d->fileInfoGatherer.setResolveSymlinks(enable);
1515}
1516
1517bool QFileSystemModel::resolveSymlinks() const
1518{
1519 Q_D(const QFileSystemModel);
1520 return d->fileInfoGatherer.resolveSymlinks();
1521}
1522
1523/*!
1524 \property QFileSystemModel::readOnly
1525 \brief Whether the directory model allows writing to the file system
1526
1527 If this property is set to false, the directory model will allow renaming, copying
1528 and deleting of files and directories.
1529
1530 This property is true by default
1531*/
1532void QFileSystemModel::setReadOnly(bool enable)
1533{
1534 Q_D(QFileSystemModel);
1535 d->readOnly = enable;
1536}
1537
1538bool QFileSystemModel::isReadOnly() const
1539{
1540 Q_D(const QFileSystemModel);
1541 return d->readOnly;
1542}
1543
1544/*!
1545 \property QFileSystemModel::nameFilterDisables
1546 \brief Whether files that don't pass the name filter are hidden or disabled
1547
1548 This property is true by default
1549*/
1550void QFileSystemModel::setNameFilterDisables(bool enable)
1551{
1552 Q_D(QFileSystemModel);
1553 if (d->nameFilterDisables == enable)
1554 return;
1555 d->nameFilterDisables = enable;
1556 d->forceSort = true;
1557 d->delayedSort();
1558}
1559
1560bool QFileSystemModel::nameFilterDisables() const
1561{
1562 Q_D(const QFileSystemModel);
1563 return d->nameFilterDisables;
1564}
1565
1566/*!
1567 Sets the name \a filters to apply against the existing files.
1568*/
1569void QFileSystemModel::setNameFilters(const QStringList &filters)
1570{
1571 // Prep the regexp's ahead of time
1572#ifndef QT_NO_REGEXP
1573 Q_D(QFileSystemModel);
1574
1575 if (!d->bypassFilters.isEmpty()) {
1576 // update the bypass filter to only bypass the stuff that must be kept around
1577 d->bypassFilters.clear();
1578 // We guarantee that rootPath will stick around
1579 QPersistentModelIndex root(index(rootPath()));
1580 QModelIndexList persistantList = persistentIndexList();
1581 for (int i = 0; i < persistantList.count(); ++i) {
1582 QFileSystemModelPrivate::QFileSystemNode *node;
1583 node = d->node(persistantList.at(i));
1584 while (node) {
1585 if (d->bypassFilters.contains(node))
1586 break;
1587 if (node->isDir())
1588 d->bypassFilters[node] = true;
1589 node = node->parent;
1590 }
1591 }
1592 }
1593
1594 d->nameFilters.clear();
1595 const Qt::CaseSensitivity caseSensitive =
1596 (filter() & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
1597 for (int i = 0; i < filters.size(); ++i) {
1598 d->nameFilters << QRegExp(filters.at(i), caseSensitive, QRegExp::Wildcard);
1599 }
1600 d->forceSort = true;
1601 d->delayedSort();
1602#endif
1603}
1604
1605/*!
1606 Returns a list of filters applied to the names in the model.
1607*/
1608QStringList QFileSystemModel::nameFilters() const
1609{
1610 Q_D(const QFileSystemModel);
1611 QStringList filters;
1612#ifndef QT_NO_REGEXP
1613 for (int i = 0; i < d->nameFilters.size(); ++i) {
1614 filters << d->nameFilters.at(i).pattern();
1615 }
1616#endif
1617 return filters;
1618}
1619
1620/*!
1621 \reimp
1622*/
1623bool QFileSystemModel::event(QEvent *event)
1624{
1625 Q_D(QFileSystemModel);
1626 if (event->type() == QEvent::LanguageChange) {
1627 d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString());
1628 return true;
1629 }
1630 return QAbstractItemModel::event(event);
1631}
1632
1633/*!
1634 \internal
1635
1636 Performed quick listing and see if any files have been added or removed,
1637 then fetch more information on visible files.
1638 */
1639void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files)
1640{
1641 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
1642 if (parentNode->children.count() == 0)
1643 return;
1644 QStringList toRemove;
1645#if defined(Q_OS_SYMBIAN)
1646 // Filename case must be exact in qBinaryFind below, so create a list of all lowercase names.
1647 QStringList newFiles;
1648 for(int i = 0; i < files.size(); i++) {
1649 newFiles << files.at(i).toLower();
1650 }
1651#else
1652 QStringList newFiles = files;
1653#endif
1654 qSort(newFiles.begin(), newFiles.end());
1655 QHash<FileNameKey, QFileSystemNode*>::const_iterator i = parentNode->children.constBegin();
1656 while (i != parentNode->children.constEnd()) {
1657 QStringList::iterator iterator;
1658 iterator = qBinaryFind(newFiles.begin(), newFiles.end(),
1659#if defined(Q_OS_SYMBIAN)
1660 i.value()->fileName.toLower());
1661#else
1662 i.value()->fileName);
1663#endif
1664 if (iterator == newFiles.end()) {
1665 toRemove.append(i.value()->fileName);
1666 }
1667 ++i;
1668 }
1669 for (int i = 0 ; i < toRemove.count() ; ++i )
1670 removeNode(parentNode, toRemove[i]);
1671}
1672
1673/*!
1674 \internal
1675
1676 Adds a new file to the children of parentNode
1677
1678 *WARNING* this will change the count of children
1679*/
1680QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1681{
1682 // In the common case, itemLocation == count() so check there first
1683 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1684#ifndef QT_NO_FILESYSTEMWATCHER
1685 node->populate(info);
1686#endif
1687#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE)
1688 //The parentNode is "" so we are listing the drives
1689 if (parentNode->fileName.isEmpty()) {
1690 wchar_t name[MAX_PATH + 1];
1691 //GetVolumeInformation requires to add trailing backslash
1692 const QString nodeName = fileName + QLatin1String("\\");
1693 BOOL success = ::GetVolumeInformation((wchar_t *)(nodeName.utf16()),
1694 name, MAX_PATH + 1, NULL, 0, NULL, NULL, 0);
1695 if (success && name[0])
1696 node->volumeName = QString::fromWCharArray(name);
1697 }
1698#endif
1699 parentNode->children.insert(fileName, node);
1700 return node;
1701}
1702
1703/*!
1704 \internal
1705
1706 File at parentNode->children(itemLocation) has been removed, remove from the lists
1707 and emit signals if necessary
1708
1709 *WARNING* this will change the count of children and could change visibleChildren
1710 */
1711void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1712{
1713 Q_Q(QFileSystemModel);
1714 QModelIndex parent = index(parentNode);
1715 bool indexHidden = isHiddenByFilter(parentNode, parent);
1716
1717 int vLocation = parentNode->visibleLocation(name);
1718 if (vLocation >= 0 && !indexHidden)
1719 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1720 translateVisibleLocation(parentNode, vLocation));
1721 QFileSystemNode * node = parentNode->children.take(name);
1722 delete node;
1723 // cleanup sort files after removing rather then re-sorting which is O(n)
1724 if (vLocation >= 0)
1725 parentNode->visibleChildren.removeAt(vLocation);
1726 if (vLocation >= 0 && !indexHidden)
1727 q->endRemoveRows();
1728}
1729
1730/*
1731 \internal
1732 Helper functor used by addVisibleFiles()
1733*/
1734class QFileSystemModelVisibleFinder
1735{
1736public:
1737 inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode *node, QFileSystemModelSorter *sorter) : parentNode(node), sorter(sorter) {}
1738
1739 bool operator()(const QString &, QString r) const
1740 {
1741 return sorter->compareNodes(parentNode->children.value(name), parentNode->children.value(r));
1742 }
1743
1744 QString name;
1745private:
1746 QFileSystemModelPrivate::QFileSystemNode *parentNode;
1747 QFileSystemModelSorter *sorter;
1748};
1749
1750/*!
1751 \internal
1752
1753 File at parentNode->children(itemLocation) was not visible before, but now should be
1754 and emit signals if necessary.
1755
1756 *WARNING* this will change the visible count
1757 */
1758void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1759{
1760 Q_Q(QFileSystemModel);
1761 QModelIndex parent = index(parentNode);
1762 bool indexHidden = isHiddenByFilter(parentNode, parent);
1763 if (!indexHidden) {
1764 q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1);
1765 }
1766
1767 if (parentNode->dirtyChildrenIndex == -1)
1768 parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count();
1769
1770 for (int i = 0; i < newFiles.count(); ++i) {
1771 parentNode->visibleChildren.append(newFiles.at(i));
1772 parentNode->children[newFiles.at(i)]->isVisible = true;
1773 }
1774 if (!indexHidden)
1775 q->endInsertRows();
1776}
1777
1778/*!
1779 \internal
1780
1781 File was visible before, but now should NOT be
1782
1783 *WARNING* this will change the visible count
1784 */
1785void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1786{
1787 Q_Q(QFileSystemModel);
1788 if (vLocation == -1)
1789 return;
1790 QModelIndex parent = index(parentNode);
1791 bool indexHidden = isHiddenByFilter(parentNode, parent);
1792 if (!indexHidden)
1793 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1794 translateVisibleLocation(parentNode, vLocation));
1795 parentNode->children[parentNode->visibleChildren.at(vLocation)]->isVisible = false;
1796 parentNode->visibleChildren.removeAt(vLocation);
1797 if (!indexHidden)
1798 q->endRemoveRows();
1799}
1800
1801/*!
1802 \internal
1803
1804 The thread has received new information about files,
1805 update and emit dataChanged if it has actually changed.
1806 */
1807void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &updates)
1808{
1809 Q_Q(QFileSystemModel);
1810 QVector<QString> rowsToUpdate;
1811 QStringList newFiles;
1812 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false);
1813 QModelIndex parentIndex = index(parentNode);
1814 for (int i = 0; i < updates.count(); ++i) {
1815 QString fileName = updates.at(i).first;
1816 Q_ASSERT(!fileName.isEmpty());
1817 QExtendedInformation info = fileInfoGatherer.getInfo(updates.at(i).second);
1818 bool previouslyHere = parentNode->children.contains(fileName);
1819 if (!previouslyHere) {
1820 addNode(parentNode, fileName, info.fileInfo());
1821 }
1822 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName);
1823 bool isCaseSensitive = parentNode->caseSensitive();
1824 if (isCaseSensitive) {
1825 if (node->fileName != fileName)
1826 continue;
1827 } else {
1828 if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0)
1829 continue;
1830 }
1831 if (isCaseSensitive) {
1832 Q_ASSERT(node->fileName == fileName);
1833 } else {
1834 node->fileName = fileName;
1835 }
1836#ifdef Q_OS_OS2
1837 // remove the invalid (non-existent) file unless it's a drive letter
1838 // (note that QFileInfoGatherer doesn't report invalid drive letters so
1839 // info.size() = -1 usually means that there is no media inserted etc.,
1840 // which is not a sufficient reason to hide the drive from the view)
1841 if (info.size() == -1 && parentNode != &root) {
1842#else
1843 if (info.size() == -1 && !info.isSymLink()) {
1844#endif
1845 removeNode(parentNode, fileName);
1846 continue;
1847 }
1848 if (*node != info ) {
1849 node->populate(info);
1850 bypassFilters.remove(node);
1851 // brand new information.
1852 if (filtersAcceptsNode(node)) {
1853 if (!node->isVisible) {
1854 newFiles.append(fileName);
1855 } else {
1856 rowsToUpdate.append(fileName);
1857 }
1858 } else {
1859 if (node->isVisible) {
1860 int visibleLocation = parentNode->visibleLocation(fileName);
1861 removeVisibleFile(parentNode, visibleLocation);
1862 } else {
1863 // The file is not visible, don't do anything
1864 }
1865 }
1866 }
1867 }
1868
1869 // bundle up all of the changed signals into as few as possible.
1870 qSort(rowsToUpdate.begin(), rowsToUpdate.end());
1871 QString min;
1872 QString max;
1873 for (int i = 0; i < rowsToUpdate.count(); ++i) {
1874 QString value = rowsToUpdate.at(i);
1875 //##TODO is there a way to bundle signals with QString as the content of the list?
1876 /*if (min.isEmpty()) {
1877 min = value;
1878 if (i != rowsToUpdate.count() - 1)
1879 continue;
1880 }
1881 if (i != rowsToUpdate.count() - 1) {
1882 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
1883 max = value;
1884 continue;
1885 }
1886 }*/
1887 max = value;
1888 min = value;
1889 int visibleMin = parentNode->visibleLocation(min);
1890 int visibleMax = parentNode->visibleLocation(max);
1891 if (visibleMin >= 0
1892 && visibleMin < parentNode->visibleChildren.count()
1893 && parentNode->visibleChildren.at(visibleMin) == min
1894 && visibleMax >= 0) {
1895 QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex);
1896 QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex);
1897 emit q->dataChanged(bottom, top);
1898 }
1899
1900 /*min = QString();
1901 max = QString();*/
1902 }
1903
1904 if (newFiles.count() > 0) {
1905 addVisibleFiles(parentNode, newFiles);
1906 }
1907
1908 if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) {
1909 forceSort = true;
1910 delayedSort();
1911 }
1912}
1913
1914/*!
1915 \internal
1916*/
1917void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
1918{
1919 resolvedSymLinks[fileName] = resolvedName;
1920}
1921
1922/*!
1923 \internal
1924*/
1925void QFileSystemModelPrivate::init()
1926{
1927 Q_Q(QFileSystemModel);
1928 qRegisterMetaType<QList<QPair<QString,QFileInfo> > >("QList<QPair<QString,QFileInfo> >");
1929 q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)),
1930 q, SLOT(_q_directoryChanged(QString,QStringList)));
1931 q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList<QPair<QString,QFileInfo> >)),
1932 q, SLOT(_q_fileSystemChanged(QString,QList<QPair<QString,QFileInfo> >)));
1933 q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)),
1934 q, SLOT(_q_resolvedName(QString,QString)));
1935 q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)),
1936 q, SIGNAL(directoryLoaded(QString)));
1937 q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
1938
1939 QHash<int, QByteArray> roles = q->roleNames();
1940 roles.insertMulti(QFileSystemModel::FileIconRole, "fileIcon"); // == Qt::decoration
1941 roles.insert(QFileSystemModel::FilePathRole, "filePath");
1942 roles.insert(QFileSystemModel::FileNameRole, "fileName");
1943 roles.insert(QFileSystemModel::FilePermissions, "filePermissions");
1944 q->setRoleNames(roles);
1945}
1946
1947/*!
1948 \internal
1949
1950 Returns false if node doesn't pass the filters otherwise true
1951
1952 QDir::Modified is not supported
1953 QDir::Drives is not supported
1954*/
1955bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
1956{
1957 // always accept drives
1958 if (node->parent == &root || bypassFilters.contains(node))
1959 return true;
1960
1961 // If we don't know anything yet don't accept it
1962 if (!node->hasInformation())
1963 return false;
1964
1965 const bool filterPermissions = ((filters & QDir::PermissionMask)
1966 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
1967 const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
1968 const bool hideFiles = !(filters & QDir::Files);
1969 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
1970 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
1971 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
1972 const bool hideHidden = !(filters & QDir::Hidden);
1973 const bool hideSystem = !(filters & QDir::System);
1974 const bool hideSymlinks = (filters & QDir::NoSymLinks);
1975 const bool hideDotAndDotDot = (filters & QDir::NoDotAndDotDot);
1976
1977 // Note that we match the behavior of entryList and not QFileInfo on this and this
1978 // incompatibility won't be fixed until Qt 5 at least
1979 bool isDotOrDot = ( (node->fileName == QLatin1String(".")
1980 || node->fileName == QLatin1String("..")));
1981 if ( (hideHidden && (!isDotOrDot && node->isHidden()))
1982 || (hideSystem && node->isSystem())
1983 || (hideDirs && node->isDir())
1984 || (hideFiles && node->isFile())
1985 || (hideSymlinks && node->isSymLink())
1986 || (hideReadable && node->isReadable())
1987 || (hideWritable && node->isWritable())
1988 || (hideExecutable && node->isExecutable())
1989 || (hideDotAndDotDot && isDotOrDot))
1990 return false;
1991
1992 return nameFilterDisables || passNameFilters(node);
1993}
1994
1995/*
1996 \internal
1997
1998 Returns true if node passes the name filters and should be visible.
1999 */
2000bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
2001{
2002#ifndef QT_NO_REGEXP
2003 if (nameFilters.isEmpty())
2004 return true;
2005
2006 // Check the name regularexpression filters
2007 if (!(node->isDir() && (filters & QDir::AllDirs))) {
2008 for (int i = 0; i < nameFilters.size(); ++i) {
2009 if (nameFilters.at(i).exactMatch(node->fileName))
2010 return true;
2011 }
2012 return false;
2013 }
2014#endif
2015 return true;
2016}
2017
2018QT_END_NAMESPACE
2019
2020#include "moc_qfilesystemmodel.cpp"
2021
2022#endif // QT_NO_FILESYSTEMMODEL
Note: See TracBrowser for help on using the repository browser.