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

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

gui/dialogs: Fixed: Duplicate file names were added to the file list in QFileDialog when an existing directory was typed directly in the filename entry field with a different case of letters than in the original (on platforms with case insensitive file systems), #65.

File size: 60.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information (qt-info@nokia.com)
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** 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 are unsure which license is appropriate for your use, please
37** contact the sales department at qt-sales@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 <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 QT_WA({
300 WIN32_FIND_DATAW findData;
301 h = ::FindFirstFileW((wchar_t *)longSection.utf16(), &findData);
302 if (h != INVALID_HANDLE_VALUE)
303 longPath.append(QString::fromUtf16((ushort*)findData.cFileName));
304 } , {
305 WIN32_FIND_DATAA findData;
306 h = ::FindFirstFileA(section.toLocal8Bit(), &findData);
307 if (h != INVALID_HANDLE_VALUE)
308 longPath.append(QString::fromLocal8Bit(findData.cFileName));
309 });
310 if (h == INVALID_HANDLE_VALUE) {
311 longPath.append(section);
312 break;
313 } else {
314 ::FindClose(h);
315 }
316 }
317 if (it != constEnd)
318 longPath.append(*it);
319 else
320 break;
321 }
322 ++it;
323 if (isSep && it == constEnd) // break out if the last character is a separator
324 break;
325 ++i;
326 } while (true);
327 return longPath;
328}
329#endif
330
331/*!
332 \internal
333
334 Given a path return the matching QFileSystemNode or &root if invalid
335*/
336QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const
337{
338 Q_Q(const QFileSystemModel);
339 Q_UNUSED(q);
340 if (path.isEmpty() || path == myComputer() || path.startsWith(QLatin1String(":")))
341 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
342
343 // Construct the nodes up to the new root path if they need to be built
344 QString absolutePath;
345#ifdef Q_OS_WIN
346 QString longPath = qt_GetLongPathName(path);
347#else
348 QString longPath = path;
349#endif
350 if (longPath == rootDir.path())
351 absolutePath = rootDir.absolutePath();
352 else
353 absolutePath = QDir(longPath).absolutePath();
354
355 QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts);
356 if ((pathElements.isEmpty())
357#if !defined(Q_OS_OS2) && (!defined(Q_OS_WIN) || defined(Q_OS_WINCE))
358 && QDir::fromNativeSeparators(longPath) != QLatin1String("/")
359#endif
360 )
361 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
362 QModelIndex index = QModelIndex(); // start with "My Computer"
363#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_OS2)
364 if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path
365 QString host = QLatin1String("\\\\") + pathElements.first();
366 if (absolutePath == QDir::fromNativeSeparators(host))
367 absolutePath.append(QLatin1Char('/'));
368 if (longPath.endsWith(QLatin1Char('/')) && !absolutePath.endsWith(QLatin1Char('/')))
369 absolutePath.append(QLatin1Char('/'));
370 int r = 0;
371 QFileSystemModelPrivate::QFileSystemNode *rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
372 if (!root.children.contains(host.toLower())) {
373 if (pathElements.count() == 1 && !absolutePath.endsWith(QLatin1Char('/')))
374 return rootNode;
375 QFileInfo info(host);
376 if (!info.exists())
377 return rootNode;
378 QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
379 p->addNode(rootNode, host,info);
380 p->addVisibleFiles(rootNode, QStringList(host));
381 }
382 r = rootNode->visibleLocation(host);
383 r = translateVisibleLocation(rootNode, r);
384 index = q->index(r, 0, QModelIndex());
385 pathElements.pop_front();
386 } else {
387 if (!pathElements.at(0).contains(QLatin1String(":")))
388 pathElements.prepend(QDir(longPath).rootPath());
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 case Qt::TextAlignmentRole:
883 return Qt::AlignLeft;
884 }
885
886 if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
887 return QAbstractItemModel::headerData(section, orientation, role);
888
889 QString returnValue;
890 switch (section) {
891 case 0: returnValue = tr("Name");
892 break;
893 case 1: returnValue = tr("Size");
894 break;
895 case 2: returnValue =
896#ifdef Q_OS_MAC
897 tr("Kind", "Match OS X Finder");
898#else
899 tr("Type", "All other platforms");
900#endif
901 break;
902 // Windows - Type
903 // OS X - Kind
904 // Konqueror - File Type
905 // Nautilus - Type
906 case 3: returnValue = tr("Date Modified");
907 break;
908 default: return QVariant();
909 }
910 return returnValue;
911}
912
913/*!
914 \reimp
915*/
916Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const
917{
918 Q_D(const QFileSystemModel);
919 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
920 if (!index.isValid())
921 return flags;
922
923 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
924 if (d->nameFilterDisables && !d->passNameFilters(indexNode)) {
925 flags &= ~Qt::ItemIsEnabled;
926 // ### TODO you shouldn't be able to set this as the current item, task 119433
927 return flags;
928 }
929
930 flags |= Qt::ItemIsDragEnabled;
931 if (d->readOnly)
932 return flags;
933 if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) {
934 flags |= Qt::ItemIsEditable;
935 if (indexNode->isDir())
936 flags |= Qt::ItemIsDropEnabled;
937 }
938 return flags;
939}
940
941/*!
942 \internal
943*/
944void QFileSystemModelPrivate::_q_performDelayedSort()
945{
946 Q_Q(QFileSystemModel);
947 q->sort(sortColumn, sortOrder);
948}
949
950static inline QChar getNextChar(const QString &s, int location)
951{
952 return (location < s.length()) ? s.at(location) : QChar();
953}
954
955/*!
956 Natural number sort, skips spaces.
957
958 Examples:
959 1, 2, 10, 55, 100
960 01.jpg, 2.jpg, 10.jpg
961
962 Note on the algorithm:
963 Only as many characters as necessary are looked at and at most they all
964 are looked at once.
965
966 Slower then QString::compare() (of course)
967 */
968int QFileSystemModelPrivate::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
969{
970 for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) {
971 // skip spaces, tabs and 0's
972 QChar c1 = getNextChar(s1, l1);
973 while (c1.isSpace())
974 c1 = getNextChar(s1, ++l1);
975 QChar c2 = getNextChar(s2, l2);
976 while (c2.isSpace())
977 c2 = getNextChar(s2, ++l2);
978
979 if (c1.isDigit() && c2.isDigit()) {
980 while (c1.digitValue() == 0)
981 c1 = getNextChar(s1, ++l1);
982 while (c2.digitValue() == 0)
983 c2 = getNextChar(s2, ++l2);
984
985 int lookAheadLocation1 = l1;
986 int lookAheadLocation2 = l2;
987 int currentReturnValue = 0;
988 // find the last digit, setting currentReturnValue as we go if it isn't equal
989 for (
990 QChar lookAhead1 = c1, lookAhead2 = c2;
991 (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
992 lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
993 lookAhead2 = getNextChar(s2, ++lookAheadLocation2)
994 ) {
995 bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
996 bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
997 if (!is1ADigit && !is2ADigit)
998 break;
999 if (!is1ADigit)
1000 return -1;
1001 if (!is2ADigit)
1002 return 1;
1003 if (currentReturnValue == 0) {
1004 if (lookAhead1 < lookAhead2) {
1005 currentReturnValue = -1;
1006 } else if (lookAhead1 > lookAhead2) {
1007 currentReturnValue = 1;
1008 }
1009 }
1010 }
1011 if (currentReturnValue != 0)
1012 return currentReturnValue;
1013 }
1014
1015 if (cs == Qt::CaseInsensitive) {
1016 if (!c1.isLower()) c1 = c1.toLower();
1017 if (!c2.isLower()) c2 = c2.toLower();
1018 }
1019 int r = QString::localeAwareCompare(c1, c2);
1020 if (r < 0)
1021 return -1;
1022 if (r > 0)
1023 return 1;
1024 }
1025 // The two strings are the same (02 == 2) so fall back to the normal sort
1026 return QString::compare(s1, s2, cs);
1027}
1028
1029/*
1030 \internal
1031 Helper functor used by sort()
1032*/
1033class QFileSystemModelSorter
1034{
1035public:
1036 inline QFileSystemModelSorter(int column) : sortColumn(column) {}
1037
1038 bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l,
1039 const QFileSystemModelPrivate::QFileSystemNode *r) const
1040 {
1041 switch (sortColumn) {
1042 case 0: {
1043#ifndef Q_OS_MAC
1044 // place directories before files
1045 bool left = l->isDir();
1046 bool right = r->isDir();
1047 if (left ^ right)
1048 return left;
1049#endif
1050 return QFileSystemModelPrivate::naturalCompare(l->fileName,
1051 r->fileName, Qt::CaseInsensitive) < 0;
1052 }
1053 case 1:
1054 // Directories go first
1055 if (l->isDir() && !r->isDir())
1056 return true;
1057 return l->size() < r->size();
1058 case 2:
1059 return l->type() < r->type();
1060 case 3:
1061 return l->lastModified() < r->lastModified();
1062 }
1063 Q_ASSERT(false);
1064 return false;
1065 }
1066
1067 bool operator()(const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &l,
1068 const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &r) const
1069 {
1070 return compareNodes(l.first, r.first);
1071 }
1072
1073
1074private:
1075 int sortColumn;
1076};
1077
1078/*
1079 \internal
1080
1081 Sort all of the children of parent
1082*/
1083void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent)
1084{
1085 QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent);
1086 if (indexNode->children.count() == 0)
1087 return;
1088
1089 QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > values;
1090 QHash<FileNameKey, QFileSystemNode *>::const_iterator iterator;
1091 int i = 0;
1092 for(iterator = indexNode->children.begin() ; iterator != indexNode->children.end() ; ++iterator) {
1093 if (filtersAcceptsNode(iterator.value())) {
1094 values.append(QPair<QFileSystemModelPrivate::QFileSystemNode*, int>((iterator.value()), i));
1095 } else {
1096 iterator.value()->isVisible = false;
1097 }
1098 i++;
1099 }
1100 QFileSystemModelSorter ms(column);
1101 qStableSort(values.begin(), values.end(), ms);
1102 // First update the new visible list
1103 indexNode->visibleChildren.clear();
1104 for (int i = 0; i < values.count(); ++i) {
1105 indexNode->visibleChildren.append(values.at(i).first->fileName);
1106 values.at(i).first->isVisible = true;
1107 }
1108}
1109
1110/*!
1111 \reimp
1112*/
1113void QFileSystemModel::sort(int column, Qt::SortOrder order)
1114{
1115 Q_D(QFileSystemModel);
1116 if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
1117 return;
1118
1119 emit layoutAboutToBeChanged();
1120 QModelIndexList oldList = persistentIndexList();
1121 QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > oldNodes;
1122 for (int i = 0; i < oldList.count(); ++i) {
1123 QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(oldList.at(i)), oldList.at(i).column());
1124 oldNodes.append(pair);
1125 }
1126
1127 if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) {
1128 //we sort only from where we are, don't need to sort all the model
1129 d->sortChildren(column, index(rootPath()));
1130 d->sortColumn = column;
1131 d->forceSort = false;
1132 }
1133 d->sortOrder = order;
1134
1135 QModelIndexList newList;
1136 for (int i = 0; i < oldNodes.count(); ++i) {
1137 QModelIndex idx = d->index(oldNodes.at(i).first);
1138 idx = idx.sibling(idx.row(), oldNodes.at(i).second);
1139 newList.append(idx);
1140 }
1141 changePersistentIndexList(oldList, newList);
1142 emit layoutChanged();
1143}
1144
1145/*!
1146 Returns a list of MIME types that can be used to describe a list of items
1147 in the model.
1148*/
1149QStringList QFileSystemModel::mimeTypes() const
1150{
1151 return QStringList(QLatin1String("text/uri-list"));
1152}
1153
1154/*!
1155 Returns an object that contains a serialized description of the specified
1156 \a indexes. The format used to describe the items corresponding to the
1157 indexes is obtained from the mimeTypes() function.
1158
1159 If the list of indexes is empty, 0 is returned rather than a serialized
1160 empty list.
1161*/
1162QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const
1163{
1164 QList<QUrl> urls;
1165 QList<QModelIndex>::const_iterator it = indexes.begin();
1166 for (; it != indexes.end(); ++it)
1167 if ((*it).column() == 0)
1168 urls << QUrl::fromLocalFile(filePath(*it));
1169 QMimeData *data = new QMimeData();
1170 data->setUrls(urls);
1171 return data;
1172}
1173
1174/*!
1175 Handles the \a data supplied by a drag and drop operation that ended with
1176 the given \a action over the row in the model specified by the \a row and
1177 \a column and by the \a parent index.
1178
1179 \sa supportedDropActions()
1180*/
1181bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
1182 int row, int column, const QModelIndex &parent)
1183{
1184 Q_UNUSED(row);
1185 Q_UNUSED(column);
1186 if (!parent.isValid() || isReadOnly())
1187 return false;
1188
1189 bool success = true;
1190 QString to = filePath(parent) + QDir::separator();
1191
1192 QList<QUrl> urls = data->urls();
1193 QList<QUrl>::const_iterator it = urls.constBegin();
1194
1195 switch (action) {
1196 case Qt::CopyAction:
1197 for (; it != urls.constEnd(); ++it) {
1198 QString path = (*it).toLocalFile();
1199 success = QFile::copy(path, to + QFileInfo(path).fileName()) && success;
1200 }
1201 break;
1202 case Qt::LinkAction:
1203 for (; it != urls.constEnd(); ++it) {
1204 QString path = (*it).toLocalFile();
1205 success = QFile::link(path, to + QFileInfo(path).fileName()) && success;
1206 }
1207 break;
1208 case Qt::MoveAction:
1209 for (; it != urls.constEnd(); ++it) {
1210 QString path = (*it).toLocalFile();
1211 success = QFile::copy(path, to + QFileInfo(path).fileName())
1212 && QFile::remove(path) && success;
1213 }
1214 break;
1215 default:
1216 return false;
1217 }
1218
1219 return success;
1220}
1221
1222/*!
1223 \reimp
1224*/
1225Qt::DropActions QFileSystemModel::supportedDropActions() const
1226{
1227 return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
1228}
1229
1230/*!
1231 Returns the path of the item stored in the model under the
1232 \a index given.
1233*/
1234QString QFileSystemModel::filePath(const QModelIndex &index) const
1235{
1236 Q_D(const QFileSystemModel);
1237 QString fullPath = d->filePath(index);
1238 QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
1239 if (dirNode->isSymLink() && d->fileInfoGatherer.resolveSymlinks()
1240 && d->resolvedSymLinks.contains(fullPath)
1241 && dirNode->isDir()) {
1242 QFileInfo resolvedInfo(fullPath);
1243 resolvedInfo = resolvedInfo.canonicalFilePath();
1244 if (resolvedInfo.exists())
1245 return resolvedInfo.filePath();
1246 }
1247 return fullPath;
1248}
1249
1250QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const
1251{
1252 Q_Q(const QFileSystemModel);
1253 Q_UNUSED(q);
1254 if (!index.isValid())
1255 return QString();
1256 Q_ASSERT(index.model() == q);
1257
1258 QStringList path;
1259 QModelIndex idx = index;
1260 while (idx.isValid()) {
1261 QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx);
1262 if (dirNode)
1263 path.prepend(dirNode->fileName);
1264 idx = idx.parent();
1265 }
1266 QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator()));
1267#if !defined(Q_OS_OS2) && (!defined(Q_OS_WIN) || defined(Q_OS_WINCE))
1268 if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/'))
1269 fullPath = fullPath.mid(1);
1270#endif
1271 return fullPath;
1272}
1273
1274/*!
1275 Create a directory with the \a name in the \a parent model index.
1276*/
1277QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name)
1278{
1279 Q_D(QFileSystemModel);
1280 if (!parent.isValid())
1281 return parent;
1282
1283 QDir dir(filePath(parent));
1284 if (!dir.mkdir(name))
1285 return QModelIndex();
1286 QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
1287 d->addNode(parentNode, name, QFileInfo());
1288 Q_ASSERT(parentNode->children.contains(name));
1289 QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name];
1290 node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name)));
1291 d->addVisibleFiles(parentNode, QStringList(name));
1292 return d->index(node);
1293}
1294
1295/*!
1296 Returns the complete OR-ed together combination of QFile::Permission for the \a index.
1297 */
1298QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const
1299{
1300 Q_D(const QFileSystemModel);
1301 QFile::Permissions p = d->node(index)->permissions();
1302 if (d->readOnly) {
1303 p ^= (QFile::WriteOwner | QFile::WriteUser
1304 | QFile::WriteGroup | QFile::WriteOther);
1305 }
1306 return p;
1307}
1308
1309/*!
1310 Sets the directory that is being watched by the model to \a newPath by
1311 installing a \l{QFileSystemWatcher}{file system watcher} on it. Any
1312 changes to files and directories within this directory will be
1313 reflected in the model.
1314
1315 If the path is changed, the rootPathChanged() signal will be emitted.
1316
1317 \note This function does not change the structure of the model or
1318 modify the data available to views. In other words, the "root" of
1319 the model is \e not changed to include only files and directories
1320 within the directory specified by \a newPath in the file system.
1321 */
1322QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
1323{
1324 Q_D(QFileSystemModel);
1325#ifdef Q_OS_WIN
1326 QString longNewPath = QDir::fromNativeSeparators(qt_GetLongPathName(newPath));
1327#else
1328 QString longNewPath = newPath;
1329#endif
1330 QDir newPathDir(longNewPath);
1331 //we remove .. and . from the given path if exist
1332 if (!newPath.isEmpty()) {
1333 longNewPath = QDir::cleanPath(longNewPath);
1334 newPathDir.setPath(longNewPath);
1335 }
1336
1337 d->setRootPath = true;
1338
1339 //user don't ask for the root path ("") but the conversion failed
1340 if (!newPath.isEmpty() && longNewPath.isEmpty())
1341 return d->index(rootPath());
1342
1343 if (d->rootDir.path() == longNewPath)
1344 return d->index(rootPath());
1345
1346 bool showDrives = (longNewPath.isEmpty() || longNewPath == d->myComputer());
1347 if (!showDrives && !newPathDir.exists())
1348 return d->index(rootPath());
1349
1350 // We have a new valid root path
1351 d->rootDir = newPathDir;
1352 QModelIndex newRootIndex;
1353 if (showDrives) {
1354 // otherwise dir will become '.'
1355 d->rootDir.setPath(QLatin1String(""));
1356 } else {
1357 newRootIndex = d->index(newPathDir.path());
1358 }
1359 fetchMore(newRootIndex);
1360 emit rootPathChanged(longNewPath);
1361 d->forceSort = true;
1362 d->delayedSort();
1363 return newRootIndex;
1364}
1365
1366/*!
1367 The currently set root path
1368
1369 \sa rootDirectory()
1370*/
1371QString QFileSystemModel::rootPath() const
1372{
1373 Q_D(const QFileSystemModel);
1374 return d->rootDir.path();
1375}
1376
1377/*!
1378 The currently set directory
1379
1380 \sa rootPath()
1381*/
1382QDir QFileSystemModel::rootDirectory() const
1383{
1384 Q_D(const QFileSystemModel);
1385 QDir dir(d->rootDir);
1386 dir.setNameFilters(nameFilters());
1387 dir.setFilter(filter());
1388 return dir;
1389}
1390
1391/*!
1392 Sets the \a provider of file icons for the directory model.
1393*/
1394void QFileSystemModel::setIconProvider(QFileIconProvider *provider)
1395{
1396 Q_D(QFileSystemModel);
1397 d->fileInfoGatherer.setIconProvider(provider);
1398 qApp->processEvents();
1399 d->root.updateIcon(provider, QString());
1400}
1401
1402/*!
1403 Returns the file icon provider for this directory model.
1404*/
1405QFileIconProvider *QFileSystemModel::iconProvider() const
1406{
1407 Q_D(const QFileSystemModel);
1408 return d->fileInfoGatherer.iconProvider();
1409}
1410
1411/*!
1412 Sets the directory model's filter to that specified by \a filters.
1413
1414 Note that the filter you set should always include the QDir::AllDirs enum value,
1415 otherwise QFileSystemModel won't be able to read the directory structure.
1416
1417 \sa QDir::Filters
1418*/
1419void QFileSystemModel::setFilter(QDir::Filters filters)
1420{
1421 Q_D(QFileSystemModel);
1422 if (d->filters == filters)
1423 return;
1424 d->filters = filters;
1425 // CaseSensitivity might have changed
1426 setNameFilters(nameFilters());
1427 d->forceSort = true;
1428 d->delayedSort();
1429}
1430
1431/*!
1432 Returns the filter specified for the directory model.
1433
1434 If a filter has not been set, the default filter is QDir::AllEntries |
1435 QDir::NoDotAndDotDot | QDir::AllDirs.
1436
1437 \sa QDir::Filters
1438*/
1439QDir::Filters QFileSystemModel::filter() const
1440{
1441 Q_D(const QFileSystemModel);
1442 return d->filters;
1443}
1444
1445/*!
1446 \property QFileSystemModel::resolveSymlinks
1447 \brief Whether the directory model should resolve symbolic links
1448
1449 This is only relevant on operating systems that support symbolic links.
1450
1451 By default, this property is false.
1452*/
1453void QFileSystemModel::setResolveSymlinks(bool enable)
1454{
1455 Q_D(QFileSystemModel);
1456 d->fileInfoGatherer.setResolveSymlinks(enable);
1457}
1458
1459bool QFileSystemModel::resolveSymlinks() const
1460{
1461 Q_D(const QFileSystemModel);
1462 return d->fileInfoGatherer.resolveSymlinks();
1463}
1464
1465/*!
1466 \property QFileSystemModel::readOnly
1467 \brief Whether the directory model allows writing to the file system
1468
1469 If this property is set to false, the directory model will allow renaming, copying
1470 and deleting of files and directories.
1471
1472 This property is true by default
1473*/
1474void QFileSystemModel::setReadOnly(bool enable)
1475{
1476 Q_D(QFileSystemModel);
1477 d->readOnly = enable;
1478}
1479
1480bool QFileSystemModel::isReadOnly() const
1481{
1482 Q_D(const QFileSystemModel);
1483 return d->readOnly;
1484}
1485
1486/*!
1487 \property QFileSystemModel::nameFilterDisables
1488 \brief Whether files that don't pass the name filter are hidden or disabled
1489
1490 This property is true by default
1491*/
1492void QFileSystemModel::setNameFilterDisables(bool enable)
1493{
1494 Q_D(QFileSystemModel);
1495 if (d->nameFilterDisables == enable)
1496 return;
1497 d->nameFilterDisables = enable;
1498 d->forceSort = true;
1499 d->delayedSort();
1500}
1501
1502bool QFileSystemModel::nameFilterDisables() const
1503{
1504 Q_D(const QFileSystemModel);
1505 return d->nameFilterDisables;
1506}
1507
1508/*!
1509 Sets the name \a filters to apply against the existing files.
1510*/
1511void QFileSystemModel::setNameFilters(const QStringList &filters)
1512{
1513 // Prep the regexp's ahead of time
1514#ifndef QT_NO_REGEXP
1515 Q_D(QFileSystemModel);
1516
1517 if (!d->bypassFilters.isEmpty()) {
1518 // update the bypass filter to only bypass the stuff that must be kept around
1519 d->bypassFilters.clear();
1520 // We guarantee that rootPath will stick around
1521 QPersistentModelIndex root(index(rootPath()));
1522 QModelIndexList persistantList = persistentIndexList();
1523 for (int i = 0; i < persistantList.count(); ++i) {
1524 QFileSystemModelPrivate::QFileSystemNode *node;
1525 node = d->node(persistantList.at(i));
1526 while (node) {
1527 if (d->bypassFilters.contains(node))
1528 break;
1529 if (node->isDir())
1530 d->bypassFilters[node] = true;
1531 node = node->parent;
1532 }
1533 }
1534 }
1535
1536 d->nameFilters.clear();
1537 const Qt::CaseSensitivity caseSensitive =
1538 (filter() & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
1539 for (int i = 0; i < filters.size(); ++i) {
1540 d->nameFilters << QRegExp(filters.at(i), caseSensitive, QRegExp::Wildcard);
1541 }
1542 d->forceSort = true;
1543 d->delayedSort();
1544#endif
1545}
1546
1547/*!
1548 Returns a list of filters applied to the names in the model.
1549*/
1550QStringList QFileSystemModel::nameFilters() const
1551{
1552 Q_D(const QFileSystemModel);
1553 QStringList filters;
1554#ifndef QT_NO_REGEXP
1555 for (int i = 0; i < d->nameFilters.size(); ++i) {
1556 filters << d->nameFilters.at(i).pattern();
1557 }
1558#endif
1559 return filters;
1560}
1561
1562/*!
1563 \reimp
1564*/
1565bool QFileSystemModel::event(QEvent *event)
1566{
1567 Q_D(QFileSystemModel);
1568 if (event->type() == QEvent::LanguageChange) {
1569 d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString());
1570 return true;
1571 }
1572 return QAbstractItemModel::event(event);
1573}
1574
1575/*!
1576 \internal
1577
1578 Performed quick listing and see if any files have been added or removed,
1579 then fetch more information on visible files.
1580 */
1581void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files)
1582{
1583 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
1584 if (parentNode->children.count() == 0)
1585 return;
1586 QStringList toRemove;
1587 QStringList newFiles = files;
1588 qSort(newFiles.begin(), newFiles.end());
1589 QHash<FileNameKey, QFileSystemNode*>::const_iterator i = parentNode->children.constBegin();
1590 while (i != parentNode->children.constEnd()) {
1591 QStringList::iterator iterator;
1592 iterator = qBinaryFind(newFiles.begin(), newFiles.end(), i.value()->fileName);
1593 if (iterator == newFiles.end()) {
1594 toRemove.append(i.value()->fileName);
1595 }
1596 ++i;
1597 }
1598 for (int i = 0 ; i < toRemove.count() ; ++i )
1599 removeNode(parentNode, toRemove[i]);
1600}
1601
1602/*!
1603 \internal
1604
1605 Adds a new file to the children of parentNode
1606
1607 *WARNING* this will change the count of children
1608*/
1609QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
1610{
1611 // In the common case, itemLocation == count() so check there first
1612 QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
1613#ifndef QT_NO_FILESYSTEMWATCHER
1614 node->populate(info);
1615#endif
1616 parentNode->children.insert(fileName, node);
1617 return node;
1618}
1619
1620/*!
1621 \internal
1622
1623 File at parentNode->children(itemLocation) has been removed, remove from the lists
1624 and emit signals if necessary
1625
1626 *WARNING* this will change the count of children and could change visibleChildren
1627 */
1628void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
1629{
1630 Q_Q(QFileSystemModel);
1631 QModelIndex parent = index(parentNode);
1632 bool indexHidden = isHiddenByFilter(parentNode, parent);
1633
1634 int vLocation = parentNode->visibleLocation(name);
1635 if (vLocation >= 0 && !indexHidden)
1636 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1637 translateVisibleLocation(parentNode, vLocation));
1638 QFileSystemNode * node = parentNode->children.take(name);
1639 delete node;
1640 // cleanup sort files after removing rather then re-sorting which is O(n)
1641 if (vLocation >= 0)
1642 parentNode->visibleChildren.removeAt(vLocation);
1643 if (vLocation >= 0 && !indexHidden)
1644 q->endRemoveRows();
1645}
1646
1647/*
1648 \internal
1649 Helper functor used by addVisibleFiles()
1650*/
1651class QFileSystemModelVisibleFinder
1652{
1653public:
1654 inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode *node, QFileSystemModelSorter *sorter) : parentNode(node), sorter(sorter) {}
1655
1656 bool operator()(const QString &, QString r) const
1657 {
1658 return sorter->compareNodes(parentNode->children.value(name), parentNode->children.value(r));
1659 }
1660
1661 QString name;
1662private:
1663 QFileSystemModelPrivate::QFileSystemNode *parentNode;
1664 QFileSystemModelSorter *sorter;
1665};
1666
1667/*!
1668 \internal
1669
1670 File at parentNode->children(itemLocation) was not visible before, but now should be
1671 and emit signals if necessary.
1672
1673 *WARNING* this will change the visible count
1674 */
1675void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
1676{
1677 Q_Q(QFileSystemModel);
1678 QModelIndex parent = index(parentNode);
1679 bool indexHidden = isHiddenByFilter(parentNode, parent);
1680 if (!indexHidden) {
1681 q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1);
1682 }
1683 for (int i = 0; i < newFiles.count(); ++i) {
1684 parentNode->visibleChildren.append(newFiles.at(i));
1685 parentNode->children[newFiles.at(i)]->isVisible = true;
1686 }
1687 if (!indexHidden)
1688 q->endInsertRows();
1689}
1690
1691/*!
1692 \internal
1693
1694 File was visible before, but now should NOT be
1695
1696 *WARNING* this will change the visible count
1697 */
1698void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
1699{
1700 Q_Q(QFileSystemModel);
1701 if (vLocation == -1)
1702 return;
1703 QModelIndex parent = index(parentNode);
1704 bool indexHidden = isHiddenByFilter(parentNode, parent);
1705 if (!indexHidden)
1706 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1707 translateVisibleLocation(parentNode, vLocation));
1708 parentNode->children[parentNode->visibleChildren.at(vLocation)]->isVisible = false;
1709 parentNode->visibleChildren.removeAt(vLocation);
1710 if (!indexHidden)
1711 q->endRemoveRows();
1712}
1713
1714/*!
1715 \internal
1716
1717 The thread has received new information about files,
1718 update and emit dataChanged if it has actually changed.
1719 */
1720void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &updates)
1721{
1722 Q_Q(QFileSystemModel);
1723 QVector<QString> rowsToUpdate;
1724 QStringList newFiles;
1725 QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false);
1726 QModelIndex parentIndex = index(parentNode);
1727 for (int i = 0; i < updates.count(); ++i) {
1728 QString fileName = updates.at(i).first;
1729 Q_ASSERT(!fileName.isEmpty());
1730 QExtendedInformation info = fileInfoGatherer.getInfo(updates.at(i).second);
1731 bool previouslyHere = parentNode->children.contains(fileName);
1732 if (!previouslyHere) {
1733 addNode(parentNode, fileName, info.fileInfo());
1734 }
1735 QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName);
1736 bool isCaseSensitive = parentNode->caseSensitive();
1737 if (isCaseSensitive) {
1738 if (node->fileName != fileName)
1739 continue;
1740 } else {
1741 if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0)
1742 continue;
1743 }
1744 if (isCaseSensitive) {
1745 Q_ASSERT(node->fileName == fileName);
1746 } else {
1747 node->fileName = fileName;
1748 }
1749
1750 if (info.size() == -1) {
1751 removeNode(parentNode, fileName);
1752 continue;
1753 }
1754 if (*node != info ) {
1755 node->populate(info);
1756 bypassFilters.remove(node);
1757 // brand new information.
1758 if (filtersAcceptsNode(node)) {
1759 if (!node->isVisible) {
1760 newFiles.append(fileName);
1761 } else {
1762 rowsToUpdate.append(fileName);
1763 }
1764 } else {
1765 if (node->isVisible) {
1766 int visibleLocation = parentNode->visibleLocation(fileName);
1767 removeVisibleFile(parentNode, visibleLocation);
1768 } else {
1769 // The file is not visible, don't do anything
1770 }
1771 }
1772 }
1773 }
1774
1775 // bundle up all of the changed signals into as few as possible.
1776 qSort(rowsToUpdate.begin(), rowsToUpdate.end());
1777 QString min;
1778 QString max;
1779 for (int i = 0; i < rowsToUpdate.count(); ++i) {
1780 QString value = rowsToUpdate.at(i);
1781 //##TODO is there a way to bundle signals with QString as the content of the list?
1782 /*if (min.isEmpty()) {
1783 min = value;
1784 if (i != rowsToUpdate.count() - 1)
1785 continue;
1786 }
1787 if (i != rowsToUpdate.count() - 1) {
1788 if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
1789 max = value;
1790 continue;
1791 }
1792 }*/
1793 max = value;
1794 min = value;
1795 int visibleMin = parentNode->visibleLocation(min);
1796 int visibleMax = parentNode->visibleLocation(max);
1797 if (visibleMin >= 0
1798 && visibleMin < parentNode->visibleChildren.count()
1799 && parentNode->visibleChildren.at(visibleMin) == min
1800 && visibleMax >= 0) {
1801 QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex);
1802 QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex);
1803 emit q->dataChanged(bottom, top);
1804 }
1805
1806 /*min = QString();
1807 max = QString();*/
1808 }
1809
1810 if (newFiles.count() > 0) {
1811 addVisibleFiles(parentNode, newFiles);
1812 }
1813
1814 if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) {
1815 forceSort = true;
1816 delayedSort();
1817 }
1818}
1819
1820/*!
1821 \internal
1822*/
1823void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
1824{
1825 resolvedSymLinks[fileName] = resolvedName;
1826}
1827
1828/*!
1829 \internal
1830*/
1831void QFileSystemModelPrivate::init()
1832{
1833 Q_Q(QFileSystemModel);
1834 qRegisterMetaType<QList<QPair<QString,QFileInfo> > >("QList<QPair<QString,QFileInfo> >");
1835 q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(const QString &, const QStringList &)),
1836 q, SLOT(_q_directoryChanged(const QString &, const QStringList &)));
1837 q->connect(&fileInfoGatherer, SIGNAL(updates(const QString &, const QList<QPair<QString, QFileInfo> > &)),
1838 q, SLOT(_q_fileSystemChanged(const QString &, const QList<QPair<QString, QFileInfo> > &)));
1839 q->connect(&fileInfoGatherer, SIGNAL(nameResolved(const QString &, const QString &)),
1840 q, SLOT(_q_resolvedName(const QString &, const QString &)));
1841 q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
1842}
1843
1844/*!
1845 \internal
1846
1847 Returns false if node doesn't pass the filters otherwise true
1848
1849 QDir::Modified is not supported
1850 QDir::Drives is not supported
1851*/
1852bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
1853{
1854 // always accept drives
1855 if (node->parent == &root || bypassFilters.contains(node))
1856 return true;
1857
1858 // If we don't know anything yet don't accept it
1859 if (!node->hasInformation())
1860 return false;
1861
1862 const bool filterPermissions = ((filters & QDir::PermissionMask)
1863 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
1864 const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
1865 const bool hideFiles = !(filters & QDir::Files);
1866 const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
1867 const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
1868 const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
1869 const bool hideHidden = !(filters & QDir::Hidden);
1870 const bool hideSystem = !(filters & QDir::System);
1871 const bool hideSymlinks = (filters & QDir::NoSymLinks);
1872 const bool hideDotAndDotDot = (filters & QDir::NoDotAndDotDot);
1873
1874 // Note that we match the behavior of entryList and not QFileInfo on this and this
1875 // incompatibility won't be fixed until Qt 5 at least
1876 bool isDotOrDot = ( (node->fileName == QLatin1String(".")
1877 || node->fileName == QLatin1String("..")));
1878 if ( (hideHidden && (!isDotOrDot && node->isHidden()))
1879 || (hideSystem && node->isSystem())
1880 || (hideDirs && node->isDir())
1881 || (hideFiles && node->isFile())
1882 || (hideSymlinks && node->isSymLink())
1883 || (hideReadable && node->isReadable())
1884 || (hideWritable && node->isWritable())
1885 || (hideExecutable && node->isExecutable())
1886 || (hideDotAndDotDot && isDotOrDot))
1887 return false;
1888
1889 return nameFilterDisables || passNameFilters(node);
1890}
1891
1892/*
1893 \internal
1894
1895 Returns true if node passes the name filters and should be visible.
1896 */
1897bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
1898{
1899#ifndef QT_NO_REGEXP
1900 if (nameFilters.isEmpty())
1901 return true;
1902
1903 // Check the name regularexpression filters
1904 if (!(node->isDir() && (filters & QDir::AllDirs))) {
1905 for (int i = 0; i < nameFilters.size(); ++i) {
1906 if (nameFilters.at(i).exactMatch(node->fileName))
1907 return true;
1908 }
1909 return false;
1910 }
1911#endif
1912 return true;
1913}
1914
1915#include "moc_qfilesystemmodel.cpp"
1916
1917#endif // QT_NO_FILESYSTEMMODEL
1918
1919QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.