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

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

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

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