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

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

trunk: Merged in qt 4.6.2 sources.

File size: 62.2 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 have a new valid root path
1364 d->rootDir = newPathDir;
1365 QModelIndex newRootIndex;
1366 if (showDrives) {
1367 // otherwise dir will become '.'
1368 d->rootDir.setPath(QLatin1String(""));
1369 } else {
1370 newRootIndex = d->index(newPathDir.path());
1371 }
1372 fetchMore(newRootIndex);
1373 emit rootPathChanged(longNewPath);
1374 d->forceSort = true;
1375 d->delayedSort();
1376 return newRootIndex;
1377}
1378
1379/*!
1380 The currently set root path
1381
1382 \sa rootDirectory()
1383*/
1384QString QFileSystemModel::rootPath() const
1385{
1386 Q_D(const QFileSystemModel);
1387 return d->rootDir.path();
1388}
1389
1390/*!
1391 The currently set directory
1392
1393 \sa rootPath()
1394*/
1395QDir QFileSystemModel::rootDirectory() const
1396{
1397 Q_D(const QFileSystemModel);
1398 QDir dir(d->rootDir);
1399 dir.setNameFilters(nameFilters());
1400 dir.setFilter(filter());
1401 return dir;
1402}
1403
1404/*!
1405 Sets the \a provider of file icons for the directory model.
1406*/
1407void QFileSystemModel::setIconProvider(QFileIconProvider *provider)
1408{
1409 Q_D(QFileSystemModel);
1410 d->fileInfoGatherer.setIconProvider(provider);
1411 QApplication::processEvents();
1412 d->root.updateIcon(provider, QString());
1413}
1414
1415/*!
1416 Returns the file icon provider for this directory model.
1417*/
1418QFileIconProvider *QFileSystemModel::iconProvider() const
1419{
1420 Q_D(const QFileSystemModel);
1421 return d->fileInfoGatherer.iconProvider();
1422}
1423
1424/*!
1425 Sets the directory model's filter to that specified by \a filters.
1426
1427 Note that the filter you set should always include the QDir::AllDirs enum value,
1428 otherwise QFileSystemModel won't be able to read the directory structure.
1429
1430 \sa QDir::Filters
1431*/
1432void QFileSystemModel::setFilter(QDir::Filters filters)
1433{
1434 Q_D(QFileSystemModel);
1435 if (d->filters == filters)
1436 return;
1437 d->filters = filters;
1438 // CaseSensitivity might have changed
1439 setNameFilters(nameFilters());
1440 d->forceSort = true;
1441 d->delayedSort();
1442}
1443
1444/*!
1445 Returns the filter specified for the directory model.
1446
1447 If a filter has not been set, the default filter is QDir::AllEntries |
1448 QDir::NoDotAndDotDot | QDir::AllDirs.
1449
1450 \sa QDir::Filters
1451*/
1452QDir::Filters QFileSystemModel::filter() const
1453{
1454 Q_D(const QFileSystemModel);
1455 return d->filters;
1456}
1457
1458/*!
1459 \property QFileSystemModel::resolveSymlinks
1460 \brief Whether the directory model should resolve symbolic links
1461
1462 This is only relevant on operating systems that support symbolic links.
1463
1464 By default, this property is false.
1465*/
1466void QFileSystemModel::setResolveSymlinks(bool enable)
1467{
1468 Q_D(QFileSystemModel);
1469 d->fileInfoGatherer.setResolveSymlinks(enable);
1470}
1471
1472bool QFileSystemModel::resolveSymlinks() const
1473{
1474 Q_D(const QFileSystemModel);
1475 return d->fileInfoGatherer.resolveSymlinks();
1476}
1477
1478/*!
1479 \property QFileSystemModel::readOnly
1480 \brief Whether the directory model allows writing to the file system
1481
1482 If this property is set to false, the directory model will allow renaming, copying
1483 and deleting of files and directories.
1484
1485 This property is true by default
1486*/
1487void QFileSystemModel::setReadOnly(bool enable)
1488{
1489 Q_D(QFileSystemModel);
1490 d->readOnly = enable;
1491}
1492
1493bool QFileSystemModel::isReadOnly() const
1494{
1495 Q_D(const QFileSystemModel);
1496 return d->readOnly;
1497}
1498
1499/*!
1500 \property QFileSystemModel::nameFilterDisables
1501 \brief Whether files that don't pass the name filter are hidden or disabled
1502
1503 This property is true by default
1504*/
1505void QFileSystemModel::setNameFilterDisables(bool enable)
1506{
1507 Q_D(QFileSystemModel);
1508 if (d->nameFilterDisables == enable)
1509 return;
1510 d->nameFilterDisables = enable;
1511 d->forceSort = true;
1512 d->delayedSort();
1513}
1514
1515bool QFileSystemModel::nameFilterDisables() const
1516{
1517 Q_D(const QFileSystemModel);
1518 return d->nameFilterDisables;
1519}
1520
1521/*!
1522 Sets the name \a filters to apply against the existing files.
1523*/
1524void QFileSystemModel::setNameFilters(const QStringList &filters)
1525{
1526 // Prep the regexp's ahead of time
1527#ifndef QT_NO_REGEXP
1528 Q_D(QFileSystemModel);
1529
1530 if (!d->bypassFilters.isEmpty()) {
1531 // update the bypass filter to only bypass the stuff that must be kept around
1532 d->bypassFilters.clear();
1533 // We guarantee that rootPath will stick around
1534 QPersistentModelIndex root(index(rootPath()));
1535 QModelIndexList persistantList = persistentIndexList();
1536 for (int i = 0; i < persistantList.count(); ++i) {
1537 QFileSystemModelPrivate::QFileSystemNode *node;
1538 node = d->node(persistantList.at(i));
1539 while (node) {
1540 if (d->bypassFilters.contains(node))
1541 break;
1542 if (node->isDir())
1543 d->bypassFilters[node] = true;
1544 node = node->parent;
1545 }
1546 }
1547 }
1548
1549 d->nameFilters.clear();
1550 const Qt::CaseSensitivity caseSensitive =
1551 (filter() & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
1552 for (int i = 0; i < filters.size(); ++i) {
1553 d->nameFilters << QRegExp(filters.at(i), caseSensitive, QRegExp::Wildcard);
1554 }
1555 d->forceSort = true;
1556 d->delayedSort();
1557#endif
1558}
1559
1560/*!
1561 Returns a list of filters applied to the names in the model.
1562*/
1563QStringList QFileSystemModel::nameFilters() const
1564{
1565 Q_D(const QFileSystemModel);
1566 QStringList filters;
1567#ifndef QT_NO_REGEXP
1568 for (int i = 0; i < d->nameFilters.size(); ++i) {
1569 filters << d->nameFilters.at(i).pattern();
1570 }
1571#endif
1572 return filters;
1573}
1574
1575/*!
1576 \reimp
1577*/
1578bool QFileSystemModel::event(QEvent *event)
1579{
1580 Q_D(QFileSystemModel);
1581 if (event->type() == QEvent::LanguageChange) {
1582 d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString());
1583 return true;
1584 }
1585 return QAbstractItemModel::event(event);
1586}
1587
1588/*!
1589 \internal
1590
1591 Performed quick listing and see if any files have been added or removed,
1592 then fetch more information on visible files.
1593 */
1594void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files)
1595{
1596 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
1597 if (parentNode->children.count() == 0)
1598 return;
1599 QStringList toRemove;
1600#if defined(Q_OS_SYMBIAN)
1601 // Filename case must be exact in qBinaryFind below, so create a list of all lowercase names.
1602 QStringList newFiles;
1603 for(int i = 0; i < files.size(); i++) {
1604 newFiles << files.at(i).toLower();
1605 }
1606#else
1607 QStringList newFiles = files;
1608#endif
1609 qSort(newFiles.begin(), newFiles.end());
1610 QHash<FileNameKey, QFileSystemNode*>::const_iterator i = parentNode->children.constBegin();
1611 while (i != parentNode->children.constEnd()) {
1612 QStringList::iterator iterator;
1613 iterator = qBinaryFind(newFiles.begin(), newFiles.end(),
1614#if defined(Q_OS_SYMBIAN)
1615 i.value()->fileName.toLower());
1616#else
1617 i.value()->fileName);
1618#endif
1619 if (iterator == newFiles.end()) {
1620 toRemove.append(i.value()->fileName);
1621 }
1622 ++i;
1623 }
1624 for (int i = 0 ; i < toRemove.count() ; ++i )
1625 removeNode(parentNode, toRemove[i]);
1626}
1627
1628/*!
1629 \internal
1630
1631 Adds a new file to the children of parentNode
1632
1633 *WARNING* this will change the count of children
1634*/
1635QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1636{
1637 // In the common case, itemLocation == count() so check there first
1638 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1639#ifndef QT_NO_FILESYSTEMWATCHER
1640 node->populate(info);
1641#endif
1642 parentNode->children.insert(fileName, node);
1643 return node;
1644}
1645
1646/*!
1647 \internal
1648
1649 File at parentNode->children(itemLocation) has been removed, remove from the lists
1650 and emit signals if necessary
1651
1652 *WARNING* this will change the count of children and could change visibleChildren
1653 */
1654void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1655{
1656 Q_Q(QFileSystemModel);
1657 QModelIndex parent = index(parentNode);
1658 bool indexHidden = isHiddenByFilter(parentNode, parent);
1659
1660 int vLocation = parentNode->visibleLocation(name);
1661 if (vLocation >= 0 && !indexHidden)
1662 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1663 translateVisibleLocation(parentNode, vLocation));
1664 QFileSystemNode * node = parentNode->children.take(name);
1665 delete node;
1666 // cleanup sort files after removing rather then re-sorting which is O(n)
1667 if (vLocation >= 0)
1668 parentNode->visibleChildren.removeAt(vLocation);
1669 if (vLocation >= 0 && !indexHidden)
1670 q->endRemoveRows();
1671}
1672
1673/*
1674 \internal
1675 Helper functor used by addVisibleFiles()
1676*/
1677class QFileSystemModelVisibleFinder
1678{
1679public:
1680 inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode *node, QFileSystemModelSorter *sorter) : parentNode(node), sorter(sorter) {}
1681
1682 bool operator()(const QString &, QString r) const
1683 {
1684 return sorter->compareNodes(parentNode->children.value(name), parentNode->children.value(r));
1685 }
1686
1687 QString name;
1688private:
1689 QFileSystemModelPrivate::QFileSystemNode *parentNode;
1690 QFileSystemModelSorter *sorter;
1691};
1692
1693/*!
1694 \internal
1695
1696 File at parentNode->children(itemLocation) was not visible before, but now should be
1697 and emit signals if necessary.
1698
1699 *WARNING* this will change the visible count
1700 */
1701void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1702{
1703 Q_Q(QFileSystemModel);
1704 QModelIndex parent = index(parentNode);
1705 bool indexHidden = isHiddenByFilter(parentNode, parent);
1706 if (!indexHidden) {
1707 q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1);
1708 }
1709
1710 if (parentNode->dirtyChildrenIndex == -1)
1711 parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count();
1712
1713 for (int i = 0; i < newFiles.count(); ++i) {
1714 parentNode->visibleChildren.append(newFiles.at(i));
1715 parentNode->children[newFiles.at(i)]->isVisible = true;
1716 }
1717 if (!indexHidden)
1718 q->endInsertRows();
1719}
1720
1721/*!
1722 \internal
1723
1724 File was visible before, but now should NOT be
1725
1726 *WARNING* this will change the visible count
1727 */
1728void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1729{
1730 Q_Q(QFileSystemModel);
1731 if (vLocation == -1)
1732 return;
1733 QModelIndex parent = index(parentNode);
1734 bool indexHidden = isHiddenByFilter(parentNode, parent);
1735 if (!indexHidden)
1736 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1737 translateVisibleLocation(parentNode, vLocation));
1738 parentNode->children[parentNode->visibleChildren.at(vLocation)]->isVisible = false;
1739 parentNode->visibleChildren.removeAt(vLocation);
1740 if (!indexHidden)
1741 q->endRemoveRows();
1742}
1743
1744/*!
1745 \internal
1746
1747 The thread has received new information about files,
1748 update and emit dataChanged if it has actually changed.
1749 */
1750void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &updates)
1751{
1752 Q_Q(QFileSystemModel);
1753 QVector<QString> rowsToUpdate;
1754 QStringList newFiles;
1755 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false);
1756 QModelIndex parentIndex = index(parentNode);
1757 for (int i = 0; i < updates.count(); ++i) {
1758 QString fileName = updates.at(i).first;
1759 Q_ASSERT(!fileName.isEmpty());
1760 QExtendedInformation info = fileInfoGatherer.getInfo(updates.at(i).second);
1761 bool previouslyHere = parentNode->children.contains(fileName);
1762 if (!previouslyHere) {
1763 addNode(parentNode, fileName, info.fileInfo());
1764 }
1765 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName);
1766 bool isCaseSensitive = parentNode->caseSensitive();
1767 if (isCaseSensitive) {
1768 if (node->fileName != fileName)
1769 continue;
1770 } else {
1771 if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0)
1772 continue;
1773 }
1774 if (isCaseSensitive) {
1775 Q_ASSERT(node->fileName == fileName);
1776 } else {
1777 node->fileName = fileName;
1778 }
1779#ifdef Q_OS_OS2
1780 // remove the invalid (non-existent) file unless it's a drive letter
1781 // (note that QFileInfoGatherer doesn't report invalid drive letters so
1782 // info.size() = -1 usually means that there is no media inserted etc.,
1783 // which is not a sufficient reason to hide the drive from the view)
1784 if (info.size() == -1 && parentNode != &root) {
1785#else
1786 if (info.size() == -1 && !info.isSymLink()) {
1787#endif
1788 removeNode(parentNode, fileName);
1789 continue;
1790 }
1791 if (*node != info ) {
1792 node->populate(info);
1793 bypassFilters.remove(node);
1794 // brand new information.
1795 if (filtersAcceptsNode(node)) {
1796 if (!node->isVisible) {
1797 newFiles.append(fileName);
1798 } else {
1799 rowsToUpdate.append(fileName);
1800 }
1801 } else {
1802 if (node->isVisible) {
1803 int visibleLocation = parentNode->visibleLocation(fileName);
1804 removeVisibleFile(parentNode, visibleLocation);
1805 } else {
1806 // The file is not visible, don't do anything
1807 }
1808 }
1809 }
1810 }
1811
1812 // bundle up all of the changed signals into as few as possible.
1813 qSort(rowsToUpdate.begin(), rowsToUpdate.end());
1814 QString min;
1815 QString max;
1816 for (int i = 0; i < rowsToUpdate.count(); ++i) {
1817 QString value = rowsToUpdate.at(i);
1818 //##TODO is there a way to bundle signals with QString as the content of the list?
1819 /*if (min.isEmpty()) {
1820 min = value;
1821 if (i != rowsToUpdate.count() - 1)
1822 continue;
1823 }
1824 if (i != rowsToUpdate.count() - 1) {
1825 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
1826 max = value;
1827 continue;
1828 }
1829 }*/
1830 max = value;
1831 min = value;
1832 int visibleMin = parentNode->visibleLocation(min);
1833 int visibleMax = parentNode->visibleLocation(max);
1834 if (visibleMin >= 0
1835 && visibleMin < parentNode->visibleChildren.count()
1836 && parentNode->visibleChildren.at(visibleMin) == min
1837 && visibleMax >= 0) {
1838 QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex);
1839 QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex);
1840 emit q->dataChanged(bottom, top);
1841 }
1842
1843 /*min = QString();
1844 max = QString();*/
1845 }
1846
1847 if (newFiles.count() > 0) {
1848 addVisibleFiles(parentNode, newFiles);
1849 }
1850
1851 if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) {
1852 forceSort = true;
1853 delayedSort();
1854 }
1855}
1856
1857/*!
1858 \internal
1859*/
1860void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
1861{
1862 resolvedSymLinks[fileName] = resolvedName;
1863}
1864
1865/*!
1866 \internal
1867*/
1868void QFileSystemModelPrivate::init()
1869{
1870 Q_Q(QFileSystemModel);
1871 qRegisterMetaType<QList<QPair<QString,QFileInfo> > >("QList<QPair<QString,QFileInfo> >");
1872 q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)),
1873 q, SLOT(_q_directoryChanged(QString,QStringList)));
1874 q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList<QPair<QString,QFileInfo> >)),
1875 q, SLOT(_q_fileSystemChanged(QString,QList<QPair<QString,QFileInfo> >)));
1876 q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)),
1877 q, SLOT(_q_resolvedName(QString,QString)));
1878 q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
1879}
1880
1881/*!
1882 \internal
1883
1884 Returns false if node doesn't pass the filters otherwise true
1885
1886 QDir::Modified is not supported
1887 QDir::Drives is not supported
1888*/
1889bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
1890{
1891 // always accept drives
1892 if (node->parent == &root || bypassFilters.contains(node))
1893 return true;
1894
1895 // If we don't know anything yet don't accept it
1896 if (!node->hasInformation())
1897 return false;
1898
1899 const bool filterPermissions = ((filters & QDir::PermissionMask)
1900 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
1901 const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
1902 const bool hideFiles = !(filters & QDir::Files);
1903 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
1904 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
1905 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
1906 const bool hideHidden = !(filters & QDir::Hidden);
1907 const bool hideSystem = !(filters & QDir::System);
1908 const bool hideSymlinks = (filters & QDir::NoSymLinks);
1909 const bool hideDotAndDotDot = (filters & QDir::NoDotAndDotDot);
1910
1911 // Note that we match the behavior of entryList and not QFileInfo on this and this
1912 // incompatibility won't be fixed until Qt 5 at least
1913 bool isDotOrDot = ( (node->fileName == QLatin1String(".")
1914 || node->fileName == QLatin1String("..")));
1915 if ( (hideHidden && (!isDotOrDot && node->isHidden()))
1916 || (hideSystem && node->isSystem())
1917 || (hideDirs && node->isDir())
1918 || (hideFiles && node->isFile())
1919 || (hideSymlinks && node->isSymLink())
1920 || (hideReadable && node->isReadable())
1921 || (hideWritable && node->isWritable())
1922 || (hideExecutable && node->isExecutable())
1923 || (hideDotAndDotDot && isDotOrDot))
1924 return false;
1925
1926 return nameFilterDisables || passNameFilters(node);
1927}
1928
1929/*
1930 \internal
1931
1932 Returns true if node passes the name filters and should be visible.
1933 */
1934bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
1935{
1936#ifndef QT_NO_REGEXP
1937 if (nameFilters.isEmpty())
1938 return true;
1939
1940 // Check the name regularexpression filters
1941 if (!(node->isDir() && (filters & QDir::AllDirs))) {
1942 for (int i = 0; i < nameFilters.size(); ++i) {
1943 if (nameFilters.at(i).exactMatch(node->fileName))
1944 return true;
1945 }
1946 return false;
1947 }
1948#endif
1949 return true;
1950}
1951
1952QT_END_NAMESPACE
1953
1954#include "moc_qfilesystemmodel.cpp"
1955
1956#endif // QT_NO_FILESYSTEMMODEL
Note: See TracBrowser for help on using the repository browser.