1 /****************************************************************************
3 ** Copyright (C) 1992-2007 Trolltech ASA. All rights reserved.
5 ** This file is part of the QtGui module of the Qt Toolkit.
7 ** This file may be used under the terms of the GNU General Public
8 ** License version 2.0 as published by the Free Software Foundation
9 ** and appearing in the file LICENSE.GPL included in the packaging of
10 ** this file. Please review the following information to ensure GNU
11 ** General Public Licensing requirements will be met:
12 ** http://www.trolltech.com/products/qt/opensource.html
14 ** If you are unsure which license is appropriate for your use, please
15 ** review the following information:
16 ** http://www.trolltech.com/products/qt/licensing.html or contact the
17 ** sales department at sales@trolltech.com.
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22 ****************************************************************************/
24 #include "qfilesystemmodel_p.h"
29 #include <qmessagebox.h>
32 \enum QFileSystemModel::Roles
38 QFileSystemModel::QFileSystemModel(QObject
*parent
)
39 : QAbstractItemModel(parent
)
41 d
= new QFileSystemModelPrivate();
46 QFileSystemModel::~QFileSystemModel()
54 QModelIndex
QFileSystemModel::index(int row
, int column
, const QModelIndex
&parent
) const
57 if (!hasIndex(row
, column
, parent
))
60 // get the parent node
61 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= (parent
.isValid() ? d
->node(parent
) :
62 const_cast<QFileSystemModelPrivate::QFileSystemNode
*>(&d
->root
));
65 // now get the internal pointer for the index
66 int realRow
= parentNode
->visibleChildren
[d
->translateVisibleLocation(parentNode
, row
)];
67 const QFileSystemModelPrivate::QFileSystemNode
*indexNode
= &parentNode
->children
.at(realRow
);
70 return createIndex(row
, column
, const_cast<QFileSystemModelPrivate::QFileSystemNode
*>(indexNode
));
76 Returns the model item index for the given \a path.
78 QModelIndex
QFileSystemModel::index(const QString
&path
, int column
) const
81 QFileSystemModelPrivate::QFileSystemNode
*node
= d
->node(path
, false);
82 QModelIndex idx
= d
->index(node
);
83 if (idx
.column() != column
)
84 idx
= idx
.sibling(idx
.row(), column
);
91 Return the QFileSystemNode that goes to index.
93 QFileSystemModelPrivate::QFileSystemNode
*QFileSystemModelPrivate::node(const QModelIndex
&index
) const
97 return const_cast<QFileSystemNode
*>(&root
);
99 Q_ASSERT(index
.model() == q
);
100 QFileSystemModelPrivate::QFileSystemNode
*indexNode
= static_cast<QFileSystemModelPrivate::QFileSystemNode
*>(index
.internalPointer());
108 Given a path return the matching QFileSystemNode or &root if invalid
110 QFileSystemModelPrivate::QFileSystemNode
*QFileSystemModelPrivate::node(const QString
&path
, bool fetch
) const
114 if (path
.isEmpty() || path
== myComputer())
115 return const_cast<QFileSystemModelPrivate::QFileSystemNode
*>(&root
);
117 // Construct the nodes up to the new root path if they need to be built
118 QString absolutePath
;
119 if (path
== rootDir
.path())
120 absolutePath
= rootDir
.absolutePath();
122 absolutePath
= QDir(path
).absolutePath();
124 // ### TODO can we use bool QAbstractFileEngine::caseSensitive() const?
125 QStringList pathElements
= absolutePath
.split(QLatin1Char('/'), QString::SkipEmptyParts
);
126 if ((pathElements
.isEmpty())
128 && path
!= QLatin1String("/")
131 return const_cast<QFileSystemModelPrivate::QFileSystemNode
*>(&root
);
132 QModelIndex index
= QModelIndex(); // start with "My Computer"
134 if (absolutePath
.startsWith(QLatin1String("//"))) { // UNC path
135 QString host
= QLatin1String("\\\\") + pathElements
.first();
137 for (; r
< root
.children
.count(); ++r
)
138 if (root
.children
.at(r
).fileName
== host
)
140 QFileSystemModelPrivate::QFileSystemNode
*rootNode
= const_cast<QFileSystemModelPrivate::QFileSystemNode
*>(&root
);
141 if (r
>= root
.children
.count()) {
142 QFileSystemModelPrivate
*p
= const_cast<QFileSystemModelPrivate
*>(this);
143 r
= p
->addNode(rootNode
, host
);
144 p
->addVisibleFiles(rootNode
, QStringList(host
));
146 r
= rootNode
->visibleLocation(r
);
147 r
= translateVisibleLocation(rootNode
, r
);
148 index
= q
->index(r
, 0, QModelIndex());
149 pathElements
.pop_front();
151 if (!pathElements
.at(0).contains(QLatin1String(":")))
152 pathElements
.prepend(QDir(path
).rootPath());
153 if (pathElements
.at(0).endsWith(QLatin1Char('/')))
154 pathElements
[0].chop(1);
157 // add the "/" item, since it is a valid path element on Unix
158 pathElements
.prepend(QLatin1String("/"));
161 QFileSystemModelPrivate::QFileSystemNode
*parent
= node(index
);
162 for (int i
= 0; i
< pathElements
.count(); ++i
) {
163 QString element
= pathElements
.at(i
);
165 // On Windows, "filename......." and "filename" are equivalent Task #133928
166 while (element
.endsWith(QLatin1Char('.')))
170 if (parent
->children
.count() != 0)
171 row
= findChild(parent
, QFileSystemNode(element
));
173 // we couldn't find the path element, we create a new node since we
174 // _know_ that the path is valid
176 && (parent
->children
.count() == 0 || parent
->children
[row
].fileName
!= element
))
180 // Someone might call ::index("file://cookie/monster/doesn't/like/veggies"),
181 // a path that doesn't exists, I.E. don't blindly create directories.
182 QStringList currentPath
= pathElements
.mid(0, i
+ 1);
183 QFileInfo
info(currentPath
.join(QLatin1String("/")));
185 return const_cast<QFileSystemModelPrivate::QFileSystemNode
*>(&root
);
186 QFileSystemModelPrivate
*p
= const_cast<QFileSystemModelPrivate
*>(this);
187 row
= p
->addNode(parent
, element
);
188 parent
->children
[row
].populate(fileInfoGatherer
.getInfo(info
));
191 if (parent
->visibleLocation(row
) == -1) {
192 QFileSystemModelPrivate
*p
= const_cast<QFileSystemModelPrivate
*>(this);
193 p
->addVisibleFiles(parent
, QStringList(element
));
194 if (!p
->bypassFilters
.contains(&parent
->children
.at(row
)))
195 p
->bypassFilters
.append(&parent
->children
.at(row
));
196 QString dir
= q
->filePath(this->index(parent
));
197 if (!parent
->children
.at(row
).hasInformation() && fetch
) {
201 f
.node
= &parent
->children
.at(row
);
202 p
->toFetch
.append(f
);
203 p
->fetchingTimer
.start(0, const_cast<QFileSystemModel
*>(q
));
206 parent
= &parent
->children
[row
];
212 void QFileSystemModel::timerEvent(QTimerEvent
*event
)
215 if (event
->timerId() == d
->fetchingTimer
.timerId()) {
216 d
->fetchingTimer
.stop();
217 for (int i
= 0; i
< d
->toFetch
.count(); ++i
) {
218 const QFileSystemModelPrivate::QFileSystemNode
*node
= d
->toFetch
.at(i
).node
;
219 if (!node
->hasInformation()) {
220 d
->fileInfoGatherer
.fetchExtendedInformation(d
->toFetch
.at(i
).dir
,
221 QStringList(d
->toFetch
.at(i
).file
));
223 // qDebug() << "yah!, you saved a little gerbil soul";
231 Returns true if the model item \a index represents a directory;
232 otherwise returns false.
234 bool QFileSystemModel::isDir(const QModelIndex
&index
) const
236 // This function is for public usage only because it could create a file info
238 if (!index
.isValid())
240 QFileSystemModelPrivate::QFileSystemNode
*n
= d
->node(index
);
241 if (n
->hasInformation())
243 return fileInfo(index
).isDir();
246 qint64
QFileSystemModel::size(const QModelIndex
&index
) const
249 if (!index
.isValid())
251 return d
->node(index
)->size();
254 QString
QFileSystemModel::type(const QModelIndex
&index
) const
257 if (!index
.isValid())
259 return d
->node(index
)->type();
262 QDateTime
QFileSystemModel::lastModified(const QModelIndex
&index
) const
265 if (!index
.isValid())
267 return d
->node(index
)->lastModified();
273 QModelIndex
QFileSystemModel::parent(const QModelIndex
&index
) const
276 if (!index
.isValid())
277 return QModelIndex();
279 QFileSystemModelPrivate::QFileSystemNode
*indexNode
= d
->node(index
);
280 Q_ASSERT(indexNode
!= 0);
281 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= (indexNode
? indexNode
->parent
: 0);
282 if (parentNode
== 0 || parentNode
== &d
->root
)
283 return QModelIndex();
285 // get the parent's row
286 QFileSystemModelPrivate::QFileSystemNode
*grandParentNode
= parentNode
->parent
;
287 int realRow
= d
->findChild(grandParentNode
, *parentNode
);
288 Q_ASSERT(realRow
>= 0);
289 int visualRow
= d
->translateVisibleLocation(grandParentNode
, grandParentNode
->visibleLocation(realRow
));
291 return QModelIndex();
292 return createIndex(visualRow
, 0, parentNode
);
298 return the index for node
300 QModelIndex
QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode
*node
) const
303 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= (node
? node
->parent
: 0);
305 return QModelIndex();
307 // get the parent's row
308 int realRow
= findChild(parentNode
, *node
);
309 Q_ASSERT(realRow
>= 0);
310 int visualRow
= translateVisibleLocation(parentNode
, parentNode
->visibleLocation(realRow
));
312 return QModelIndex();
313 return q
->createIndex(visualRow
, 0, const_cast<QFileSystemNode
*>(node
));
319 bool QFileSystemModel::hasChildren(const QModelIndex
&parent
) const
322 if (parent
.column() > 0)
325 if (!parent
.isValid()) // drives
328 const QFileSystemModelPrivate::QFileSystemNode
*indexNode
= d
->node(parent
);
330 return (indexNode
->isDir());
336 bool QFileSystemModel::canFetchMore(const QModelIndex
&parent
) const
339 const QFileSystemModelPrivate::QFileSystemNode
*indexNode
= d
->node(parent
);
340 return (!indexNode
->populatedChildren
);
346 void QFileSystemModel::fetchMore(const QModelIndex
&parent
)
349 QFileSystemModelPrivate::QFileSystemNode
*indexNode
= d
->node(parent
);
350 if (indexNode
->populatedChildren
)
352 indexNode
->populatedChildren
= true;
353 d
->fileInfoGatherer
.list(filePath(parent
));
359 int QFileSystemModel::rowCount(const QModelIndex
&parent
) const
362 if (parent
.column() > 0)
365 if (!parent
.isValid())
366 return d
->root
.visibleChildren
.count();
368 const QFileSystemModelPrivate::QFileSystemNode
*parentNode
= d
->node(parent
);
369 return parentNode
->visibleChildren
.count();
375 int QFileSystemModel::columnCount(const QModelIndex
&parent
) const
377 return (parent
.column() > 0) ? 0 : 4;
380 QVariant
QFileSystemModel::myComputer(int role
) const
384 case Qt::DisplayRole
:
385 return d
->myComputer();
386 case Qt::DecorationRole
:
387 return d
->fileInfoGatherer
.iconProvider()->icon(QFileIconProvider::Computer
);
395 QVariant
QFileSystemModel::data(const QModelIndex
&index
, int role
) const
398 if (!index
.isValid() || index
.model() != this)
403 case Qt::DisplayRole
:
404 switch (index
.column()) {
405 case 0: return d
->name(index
);
406 case 1: return d
->size(index
);
407 case 2: return d
->type(index
);
408 case 3: return d
->time(index
);
410 qWarning("data: invalid display value column %d", index
.column());
415 return filePath(index
);
417 return d
->name(index
);
418 case Qt::DecorationRole
:
419 if (index
.column() == 0) {
420 QIcon icon
= d
->icon(index
);
422 if (d
->node(index
)->isDir())
423 icon
= d
->fileInfoGatherer
.iconProvider()->icon(QFileIconProvider::Folder
);
425 icon
= d
->fileInfoGatherer
.iconProvider()->icon(QFileIconProvider::File
);
430 case Qt::TextAlignmentRole
:
431 if (index
.column() == 1)
432 return Qt::AlignRight
;
442 QString
QFileSystemModelPrivate::size(const QModelIndex
&index
) const
444 if (!index
.isValid())
446 const QFileSystemNode
*n
= node(index
);
449 return QLatin1String("--");
451 return QLatin1String("");
455 // Konqueror - "4 KB"
456 // Nautilus - "9 items" (the number of children)
458 return size(n
->size());
461 QString
QFileSystemModelPrivate::size(qint64 bytes
)
463 // According to the Si standard KB is 1000 bytes, KiB is 1024
464 // but on windows sizes are calculated by dividing by 1024 so we do what they do.
465 const qint64 kb
= 1024;
466 const qint64 mb
= 1024 * kb
;
467 const qint64 gb
= 1024 * mb
;
468 const qint64 tb
= 1024 * gb
;
470 return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes
) / tb
, 'f', 3));
472 return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes
) / gb
, 'f', 2));
474 return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes
) / mb
, 'f', 1));
476 return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes
/ kb
));
477 return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes
));
483 QString
QFileSystemModelPrivate::time(const QModelIndex
&index
) const
485 if (!index
.isValid())
487 #ifndef QT_NO_DATESTRING
488 return node(index
)->lastModified().toString(Qt::SystemLocaleDate
);
498 QString
QFileSystemModelPrivate::type(const QModelIndex
&index
) const
500 if (!index
.isValid())
502 return node(index
)->type();
508 QString
QFileSystemModelPrivate::name(const QModelIndex
&index
) const
510 if (!index
.isValid())
512 QFileSystemNode
*dirNode
= node(index
);
513 if (dirNode
->isSymLink() && fileInfoGatherer
.resolveSymlinks()
514 && resolvedSymLinks
.contains(dirNode
->fileName
)) {
515 return resolvedSymLinks
[dirNode
->fileName
];
517 // ### TODO it would be nice to grab the volume name if dirNode->parent == root
518 return dirNode
->fileName
;
524 QIcon
QFileSystemModelPrivate::icon(const QModelIndex
&index
) const
526 if (!index
.isValid())
528 return node(index
)->icon();
534 bool QFileSystemModel::setData(const QModelIndex
&index
, const QVariant
&value
, int role
)
538 || index
.column() != 0
539 || role
!= Qt::EditRole
540 || (flags(index
) & Qt::ItemIsEditable
) == 0) {
544 QString newName
= value
.toString();
545 if (newName
== index
.data().toString())
548 if (newName
.contains(QDir::separator())
549 || !d
->rootDir
.rename(index
.data().toString(), newName
)) {
550 #ifndef QT_NO_MESSAGEBOX
551 QMessageBox::information(0, QFileSystemModel::tr("Invalid filename"),
552 QFileSystemModel::tr("<b>The name \"%1\" can not be used.</b><p>Try using another name, with fewer characters or no punctuations marks.")
555 #endif // QT_NO_MESSAGEBOX
558 QFileSystemModelPrivate::QFileSystemNode
*indexNode
= d
->node(index
);
559 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= indexNode
->parent
;
560 int itemLocation
= d
->findChild(parentNode
, *indexNode
);
561 int visibleLocation
= parentNode
->visibleLocation(itemLocation
);
563 parentNode
->visibleChildren
.removeAt(visibleLocation
);
564 d
->removeNode(parentNode
, itemLocation
);
565 itemLocation
= d
->addNode(parentNode
, newName
);
566 QFileInfo
info(d
->rootDir
, newName
);
567 parentNode
->children
[itemLocation
].populate(d
->fileInfoGatherer
.getInfo(info
));
568 parentNode
->visibleChildren
.insert(visibleLocation
, itemLocation
);
577 QVariant
QFileSystemModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
580 case Qt::DecorationRole
:
582 // ### TODO oh man this is ugly and doesn't even work all the way!
583 // it is still 2 pixels off
584 QImage
pixmap(16, 1, QImage::Format_Mono
);
586 pixmap
.setAlphaChannel(pixmap
.createAlphaMask());
589 case Qt::TextAlignmentRole
:
591 case 0: return Qt::AlignLeft
;
592 case 1: return Qt::AlignRight
;
593 case 2: return Qt::AlignLeft
;
594 case 3: return Qt::AlignLeft
;
598 if (orientation
!= Qt::Horizontal
|| role
!= Qt::DisplayRole
)
599 return QAbstractItemModel::headerData(section
, orientation
, role
);
603 case 0: returnValue
= tr("Name");
605 case 1: returnValue
= tr("Size");
607 case 2: returnValue
=
609 tr("Kind", "Match OS X Finder");
611 tr("Type", "All other platforms");
616 // Konqueror - File Type
618 case 3: returnValue
= tr("Date Modified");
620 default: return QVariant();
628 Qt::ItemFlags
QFileSystemModel::flags(const QModelIndex
&index
) const
631 Qt::ItemFlags flags
= QAbstractItemModel::flags(index
);
632 if (!index
.isValid())
635 QFileSystemModelPrivate::QFileSystemNode
*indexNode
= d
->node(index
);
636 if (d
->nameFilterDisables
&& !d
->passNameFilters(indexNode
)) {
637 flags
&= ~Qt::ItemIsEnabled
;
638 // ### TODO you shouldn't be able to set this as the current item, task 119433
642 flags
|= Qt::ItemIsDragEnabled
;
645 if ((index
.column() == 0) && indexNode
->permissions() & QFile::WriteUser
) {
646 flags
|= Qt::ItemIsEditable
;
647 if (indexNode
->isDir())
648 flags
|= Qt::ItemIsDropEnabled
;
656 void QFileSystemModelPrivate::_q_performDelayedSort()
659 q
->sort(sortColumn
, sortOrder
);
662 static inline QChar
getNextChar(const QString
&s
, int location
)
664 return (location
< s
.length()) ? s
.at(location
) : QChar();
668 Natural number sort, skips spaces.
672 01.jpg, 2.jpg, 10.jpg
674 Note on the algorithm:
675 Only as many characters as necessary are looked at and at most they all
678 Slower then QString::compare() (of course)
680 int QFileSystemModelPrivate::naturalCompare(const QString
&s1
, const QString
&s2
, Qt::CaseSensitivity cs
)
682 for (int l1
= 0, l2
= 0; l1
<= s1
.count() && l2
<= s2
.count(); ++l1
, ++l2
) {
683 // skip spaces, tabs and 0's
684 QChar c1
= getNextChar(s1
, l1
);
686 c1
= getNextChar(s1
, ++l1
);
687 QChar c2
= getNextChar(s2
, l2
);
689 c2
= getNextChar(s2
, ++l2
);
691 if (c1
.isDigit() && c2
.isDigit()) {
692 while (c1
.digitValue() == 0)
693 c1
= getNextChar(s1
, ++l1
);
694 while (c2
.digitValue() == 0)
695 c2
= getNextChar(s2
, ++l2
);
697 int lookAheadLocation1
= l1
;
698 int lookAheadLocation2
= l2
;
699 int currentReturnValue
= 0;
700 // find the last digit, setting currentReturnValue as we go if it isn't equal
702 QChar lookAhead1
= c1
, lookAhead2
= c2
;
703 (lookAheadLocation1
<= s1
.length() && lookAheadLocation2
<= s2
.length());
704 lookAhead1
= getNextChar(s1
, ++lookAheadLocation1
),
705 lookAhead2
= getNextChar(s2
, ++lookAheadLocation2
)
707 bool is1ADigit
= !lookAhead1
.isNull() && lookAhead1
.isDigit();
708 bool is2ADigit
= !lookAhead2
.isNull() && lookAhead2
.isDigit();
709 if (!is1ADigit
&& !is2ADigit
)
715 if (currentReturnValue
== 0) {
716 if (lookAhead1
< lookAhead2
) {
717 currentReturnValue
= -1;
718 } else if (lookAhead1
> lookAhead2
) {
719 currentReturnValue
= 1;
723 if (currentReturnValue
!= 0)
724 return currentReturnValue
;
727 if (cs
== Qt::CaseInsensitive
) {
728 if (!c1
.isLower()) c1
= c1
.toLower();
729 if (!c2
.isLower()) c2
= c2
.toLower();
731 int r
= QString::localeAwareCompare(c1
, c2
);
737 // The two strings are the same (02 == 2) so fall back to the normal sort
738 return QString::compare(s1
, s2
, cs
);
743 Helper functor used by sort()
745 class QFileSystemModelSorter
748 inline QFileSystemModelSorter(int column
) : sortColumn(column
) {}
750 bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode
*l
,
751 const QFileSystemModelPrivate::QFileSystemNode
*r
) const
753 switch (sortColumn
) {
756 // place directories before files
757 bool left
= l
->isDir();
758 bool right
= r
->isDir();
762 return QFileSystemModelPrivate::naturalCompare(l
->fileName
,
763 r
->fileName
, Qt::CaseInsensitive
) < 0;
766 // Directories go first
767 if (l
->isDir() && !r
->isDir())
769 return l
->size() < r
->size();
771 return l
->type() < r
->type();
773 return l
->lastModified() < r
->lastModified();
779 bool operator()(const QPair
<const QFileSystemModelPrivate::QFileSystemNode
*, int> &l
,
780 const QPair
<const QFileSystemModelPrivate::QFileSystemNode
*, int> &r
) const
782 return compareNodes(l
.first
, r
.first
);
793 Sort all of the children of parent (including their children)
795 void QFileSystemModelPrivate::sortChildren(int column
, Qt::SortOrder order
, const QModelIndex
&parent
)
798 QFileSystemModelPrivate::QFileSystemNode
*indexNode
= node(parent
);
799 if (indexNode
->children
.count() == 0)
802 QList
<QPair
<const QFileSystemModelPrivate::QFileSystemNode
*, int> > values
;
803 for (int i
= 0; i
< indexNode
->children
.count(); ++i
) {
804 if (filtersAcceptsNode(&indexNode
->children
.at(i
)))
805 values
.append(QPair
<const QFileSystemModelPrivate::QFileSystemNode
*, int>(&(indexNode
->children
[i
]), i
));
807 QFileSystemModelSorter
ms(column
);
808 qStableSort(values
.begin(), values
.end(), ms
);
809 // First update the new visible list
810 indexNode
->visibleChildren
.clear();
811 for (int i
= 0; i
< values
.count(); ++i
)
812 indexNode
->visibleChildren
.append(values
.at(i
).second
);
814 for (int i
= 0; i
< q
->rowCount(parent
); ++i
)
815 sortChildren(column
, order
, q
->index(i
, 0, parent
));
821 void QFileSystemModel::sort(int column
, Qt::SortOrder order
)
824 if (d
->sortOrder
== order
&& d
->sortColumn
== column
&& !d
->forceSort
)
827 emit
layoutAboutToBeChanged();
828 QModelIndexList oldList
= persistentIndexList();
829 QList
<QPair
<QFileSystemModelPrivate::QFileSystemNode
*, int> > oldNodes
;
830 for (int i
= 0; i
< oldList
.count(); ++i
) {
831 QPair
<QFileSystemModelPrivate::QFileSystemNode
*, int> pair(d
->node(oldList
.at(i
)), oldList
.at(i
).column());
832 oldNodes
.append(pair
);
835 if (!(d
->sortColumn
== column
&& d
->sortOrder
!= order
&& !d
->forceSort
)) {
836 d
->sortChildren(column
, order
, QModelIndex());
837 d
->sortColumn
= column
;
838 d
->forceSort
= false;
840 d
->sortOrder
= order
;
842 QModelIndexList newList
;
843 for (int i
= 0; i
< oldNodes
.count(); ++i
) {
844 QModelIndex idx
= d
->index(oldNodes
.at(i
).first
);
845 idx
= idx
.sibling(idx
.row(), oldNodes
.at(i
).second
);
848 changePersistentIndexList(oldList
, newList
);
849 emit
layoutChanged();
853 Returns a list of MIME types that can be used to describe a list of items
856 QStringList
QFileSystemModel::mimeTypes() const
858 return QStringList(QLatin1String("text/uri-list"));
862 Returns an object that contains a serialized description of the specified
863 \a indexes. The format used to describe the items corresponding to the
864 indexes is obtained from the mimeTypes() function.
866 If the list of indexes is empty, 0 is returned rather than a serialized
869 QMimeData
*QFileSystemModel::mimeData(const QModelIndexList
&indexes
) const
872 QList
<QModelIndex
>::const_iterator it
= indexes
.begin();
873 for (; it
!= indexes
.end(); ++it
)
874 if ((*it
).column() == 0)
875 urls
<< QUrl::fromLocalFile(filePath(*it
));
876 QMimeData
*data
= new QMimeData();
882 Handles the \a data supplied by a drag and drop operation that ended with
883 the given \a action over the row in the model specified by the \a row and
884 \a column and by the \a parent index.
886 \sa supportedDropActions()
888 bool QFileSystemModel::dropMimeData(const QMimeData
*data
, Qt::DropAction action
,
889 int row
, int column
, const QModelIndex
&parent
)
893 if (!parent
.isValid() || isReadOnly())
897 QString to
= filePath(parent
) + QDir::separator();
899 QList
<QUrl
> urls
= data
->urls();
900 QList
<QUrl
>::const_iterator it
= urls
.constBegin();
904 for (; it
!= urls
.constEnd(); ++it
) {
905 QString path
= (*it
).toLocalFile();
906 success
= QFile::copy(path
, to
+ QFileInfo(path
).fileName()) && success
;
910 for (; it
!= urls
.constEnd(); ++it
) {
911 QString path
= (*it
).toLocalFile();
912 success
= QFile::link(path
, to
+ QFileInfo(path
).fileName()) && success
;
916 for (; it
!= urls
.constEnd(); ++it
) {
917 QString path
= (*it
).toLocalFile();
918 success
= QFile::copy(path
, to
+ QFileInfo(path
).fileName())
919 && QFile::remove(path
) && success
;
932 Qt::DropActions
QFileSystemModel::supportedDropActions() const
934 return Qt::CopyAction
| Qt::MoveAction
| Qt::LinkAction
;
938 Returns the path of the item stored in the model under the
941 QString
QFileSystemModel::filePath(const QModelIndex
&index
) const
944 if (!index
.isValid())
946 Q_ASSERT(index
.model() == this);
949 QModelIndex idx
= index
;
950 while (idx
.isValid()) {
951 path
.prepend(d
->name(idx
));
954 QString fullPath
= path
.join(QDir::separator());
955 return QDir::toNativeSeparators(QDir::cleanPath(fullPath
));
959 Create a directory with the name in the parent model item
961 QModelIndex
QFileSystemModel::mkdir(const QModelIndex
&parent
, const QString
&name
)
964 QDir
dir(filePath(parent
));
965 if (!dir
.mkdir(name
))
966 return QModelIndex();
967 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= d
->node(parent
);
968 d
->addNode(parentNode
, name
);
969 int r
= d
->findChild(parentNode
, QFileSystemModelPrivate::QFileSystemNode(name
));
971 QFileSystemModelPrivate::QFileSystemNode
*node
= &parentNode
->children
[r
];
972 node
->populate(d
->fileInfoGatherer
.getInfo(QFileInfo(dir
.absolutePath() + QDir::separator() + name
)));
973 d
->addVisibleFiles(parentNode
, QStringList(name
));
974 return d
->index(node
);
977 QFile::Permissions
QFileSystemModel::permissions(const QModelIndex
&index
) const
980 return d
->node(index
)->permissions();
985 Sets the directory that is being watched by the model.
986 If the path is changed the model will be reset.
988 QModelIndex
QFileSystemModel::setRootPath(const QString
&newPath
)
991 if (d
->rootDir
.path() == newPath
)
992 return d
->index(rootPath());
994 QDir
newPathDir(newPath
);
995 bool showDrives
= (newPath
.isEmpty() || newPath
== d
->myComputer());
996 if (!showDrives
&& !newPathDir
.exists())
997 return d
->index(rootPath());
999 // We have a new valid root path
1000 d
->rootDir
= newPathDir
;
1001 QModelIndex newRootIndex
;
1003 // otherwise dir will become '.'
1004 d
->rootDir
.setPath(QLatin1String(""));
1006 newRootIndex
= d
->index(newPathDir
.path());
1008 fetchMore(newRootIndex
);
1009 emit
rootPathChanged(newPath
);
1010 d
->forceSort
= true;
1012 return newRootIndex
;
1016 The currently set root path
1020 QString
QFileSystemModel::rootPath() const
1023 return d
->rootDir
.path();
1027 The currently set directory
1031 QDir
QFileSystemModel::rootDirectory() const
1034 QDir
dir(d
->rootDir
);
1035 dir
.setNameFilters(nameFilters());
1036 dir
.setFilter(filter());
1041 Sets the \a provider of file icons for the directory model.
1043 void QFileSystemModel::setIconProvider(QFileIconProvider
*provider
)
1046 d
->fileInfoGatherer
.setIconProvider(provider
);
1050 Returns the file icon provider for this directory model.
1052 QFileIconProvider
*QFileSystemModel::iconProvider() const
1055 return d
->fileInfoGatherer
.iconProvider();
1059 Sets the directory model's filter to that specified by \a filters.
1061 Note that the filter you set should always include the QDir::AllDirs enum value,
1062 otherwise QFileSystemModel won't be able to read the directory structure.
1066 void QFileSystemModel::setFilter(QDir::Filters filters
)
1069 d
->filters
= filters
;
1070 // CaseSensitivity might have changed
1071 setNameFilters(nameFilters());
1072 d
->forceSort
= true;
1077 Returns the filter specification for the directory model.
1081 QDir::Filters
QFileSystemModel::filter() const
1088 \property QFileSystemModel::resolveSymlinks
1089 \brief Whether the directory model should resolve symbolic links
1091 This is only relevant on operating systems that support symbolic links.
1093 void QFileSystemModel::setResolveSymlinks(bool enable
)
1096 d
->fileInfoGatherer
.setResolveSymlinks(enable
);
1099 bool QFileSystemModel::resolveSymlinks() const
1102 return d
->fileInfoGatherer
.resolveSymlinks();
1106 \property QFileSystemModel::readOnly
1107 \brief Whether the directory model allows writing to the file system
1109 If this property is set to false, the directory model will allow renaming, copying
1110 and deleting of files and directories.
1112 This property is true by default
1114 void QFileSystemModel::setReadOnly(bool enable
)
1117 d
->readOnly
= enable
;
1120 bool QFileSystemModel::isReadOnly() const
1127 \property QFileSystemModel::nameFilterDisables
1128 \brief Whether files that don't pass the name filter are hidden or disabled
1130 This property is true by default
1132 void QFileSystemModel::setNameFilterDisables(bool enable
)
1135 if (d
->nameFilterDisables
== enable
)
1137 d
->nameFilterDisables
= enable
;
1138 d
->forceSort
= true;
1142 bool QFileSystemModel::nameFilterDisables() const
1145 return d
->nameFilterDisables
;
1149 Sets the name \a filters to apply against the exisiting files.
1151 void QFileSystemModel::setNameFilters(const QStringList
&filters
)
1153 // Prep the regexp's ahead of time
1154 #ifndef QT_NO_REGEXP
1156 d
->nameFilters
.clear();
1157 const Qt::CaseSensitivity caseSensitive
=
1158 (filter() & QDir::CaseSensitive
) ? Qt::CaseSensitive
: Qt::CaseInsensitive
;
1159 for (int i
= 0; i
< filters
.size(); ++i
) {
1160 d
->nameFilters
<< QRegExp(filters
.at(i
), caseSensitive
, QRegExp::Wildcard
);
1162 d
->forceSort
= true;
1168 Returns a list of filters applied to the names in the model.
1170 QStringList
QFileSystemModel::nameFilters() const
1173 QStringList filters
;
1174 #ifndef QT_NO_REGEXP
1175 for (int i
= 0; i
< d
->nameFilters
.size(); ++i
) {
1176 filters
<< d
->nameFilters
.at(i
).pattern();
1185 Performed quick listing and see if any files have been added or removed,
1186 then fetch more information on visible files.
1188 void QFileSystemModelPrivate::_q_directoryChanged(const QString
&directory
, const QStringList
&files
)
1190 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= node(directory
, false);
1191 QStringList newFiles
= files
;
1192 // sort new files for faster inserting
1193 qSort(newFiles
.begin(), newFiles
.end());
1195 // Ignore files we already have and cleanup filtered files that were removed.
1196 // non-filtered files will be removed in a fileSystemChanged()
1197 for (int i
= parentNode
->children
.count() - 1; i
>= 0; --i
) {
1198 QStringList::const_iterator iterator
;
1199 if (parentNode
->caseSensitive())
1200 iterator
= qBinaryFind(newFiles
.begin(), newFiles
.end(),
1201 parentNode
->children
.at(i
).fileName
);
1203 iterator
= qBinaryFind(newFiles
.begin(), newFiles
.end(),
1204 parentNode
->children
.at(i
).fileName
, caseInsensitiveLessThan
);
1205 if (iterator
== newFiles
.constEnd()) {
1206 removeNode(parentNode
, i
);
1214 Adds a new file to the children of parentNode
1216 *WARNING* this will change the count of children
1218 int QFileSystemModelPrivate::addNode(QFileSystemNode
*parentNode
, const QString
&fileName
)
1220 // In the common case, itemLocation == count() so check there first
1221 QFileSystemModelPrivate::QFileSystemNode
node(fileName
, parentNode
);
1222 int itemLocation
= 0;
1223 if (parentNode
->children
.count() > 0) {
1224 if (parentNode
->children
.at(parentNode
->children
.count() - 1).hasInformation()
1225 && parentNode
->children
.at(parentNode
->children
.count() - 1) < fileName
) {
1226 itemLocation
= parentNode
->children
.count();
1228 itemLocation
= findWhereToInsertChild(parentNode
, &node
);
1231 parentNode
->children
.insert(itemLocation
, node
);
1233 for (int i
= 0; i
< parentNode
->visibleChildren
.count(); ++i
)
1234 if (parentNode
->visibleChildren
.at(i
) >= itemLocation
)
1235 ++parentNode
->visibleChildren
[i
];
1236 return itemLocation
;
1242 File at parentNode->children(itemLocation) has been removed, remove from the lists
1243 and emit signals if necessary
1245 *WARNING* this will change the count of children and could change visibleChildren
1247 void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode
*parentNode
, int itemLocation
)
1250 QModelIndex parent
= index(parentNode
);
1251 bool indexHidden
= isHiddenByFilter(parentNode
, parent
);
1253 int vLocation
= parentNode
->visibleLocation(itemLocation
);
1254 if (vLocation
>= 0 && !indexHidden
)
1255 q
->beginRemoveRows(parent
, translateVisibleLocation(parentNode
, vLocation
),
1256 translateVisibleLocation(parentNode
, vLocation
));
1257 parentNode
->children
.removeAt(itemLocation
);
1258 // cleanup sort files after removing rather then re-sorting which is O(n)
1260 parentNode
->visibleChildren
.removeAt(vLocation
);
1261 // update all the rest of the visible children
1262 for (int j
= 0; j
< parentNode
->visibleChildren
.count(); ++j
)
1263 if (parentNode
->visibleChildren
.at(j
) > itemLocation
)
1264 --parentNode
->visibleChildren
[j
];
1265 if (vLocation
>= 0 && !indexHidden
)
1271 Helper functor used by addVisibleFiles()
1273 class QFileSystemModelVisibleFinder
1276 inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode
*node
, QFileSystemModelSorter
*sorter
) : parentNode(node
), sorter(sorter
) {}
1278 bool operator()(const QString
&, int r
) const
1280 return sorter
->compareNodes(&(parentNode
->children
.at(location
)), &(parentNode
->children
.at(r
)));
1285 QFileSystemModelPrivate::QFileSystemNode
*parentNode
;
1286 QFileSystemModelSorter
*sorter
;
1292 File at parentNode->children(itemLocation) was not visible before, but now should be
1293 and emit signals if necessary.
1295 *WARNING* this will change the visible count
1297 void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode
*parentNode
, const QStringList
&newFiles
)
1300 QModelIndex parent
= index(parentNode
);
1301 bool indexHidden
= isHiddenByFilter(parentNode
, parent
);
1303 QFileSystemModelSorter
sorter(sortColumn
);
1304 QFileSystemModelVisibleFinder
vf(parentNode
, &sorter
);
1305 for (int i
= 0; i
< newFiles
.count(); ++i
) {
1306 QString newFile
= newFiles
.at(i
);
1307 int location
= findChild(parentNode
, QFileSystemNode(newFile
));
1308 Q_ASSERT(location
>= 0);
1310 vf
.location
= location
;
1311 QList
<int>::const_iterator iterator
= qUpperBound(parentNode
->visibleChildren
.begin(), parentNode
->visibleChildren
.end(), newFile
, vf
);
1312 int realHome
= (iterator
- parentNode
->visibleChildren
.begin());
1314 q
->beginInsertRows(parent
, realHome
, realHome
);
1315 parentNode
->visibleChildren
.insert(realHome
, location
);
1324 File was visible before, but now should NOT be
1326 *WARNING* this will change the visible count
1328 void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode
*parentNode
, int vLocation
)
1331 if (vLocation
== -1)
1333 QModelIndex parent
= index(parentNode
);
1334 bool indexHidden
= isHiddenByFilter(parentNode
, parent
);
1336 q
->beginRemoveRows(parent
, translateVisibleLocation(parentNode
, vLocation
),
1337 translateVisibleLocation(parentNode
, vLocation
));
1338 parentNode
->visibleChildren
.removeAt(vLocation
);
1346 The thread has received new information about files,
1347 update and emit dataChanged if it has actually changed.
1349 void QFileSystemModelPrivate::_q_fileSystemChanged(const QString
&path
, const QList
<QPair
<QString
, QExtendedInformation
> > &updates
)
1352 QVector
<int> rowsToUpdate
;
1353 QStringList newFiles
;
1354 QFileSystemModelPrivate::QFileSystemNode
*parentNode
= node(path
, false);
1355 QModelIndex parentIndex
= index(parentNode
);
1356 for (int i
= 0; i
< updates
.count(); ++i
) {
1357 QString fileName
= updates
.at(i
).first
;
1358 Q_ASSERT(!fileName
.isEmpty());
1359 QExtendedInformation info
= updates
.at(i
).second
;
1360 int itemLocation
= findChild(parentNode
, QFileSystemNode(fileName
));
1361 if (itemLocation
< 0) {
1362 itemLocation
= addNode(parentNode
, fileName
);
1363 for (int i
= 0; i
< rowsToUpdate
.count(); ++i
)
1364 if (rowsToUpdate
.at(i
) >= itemLocation
)
1367 if (parentNode
->caseSensitive()) {
1368 if (parentNode
->children
.at(itemLocation
).fileName
!= fileName
)
1371 if (parentNode
->children
.at(itemLocation
).fileName
.toLower() != fileName
.toLower())
1375 if (parentNode
->caseSensitive()) {
1376 Q_ASSERT(parentNode
->children
.at(itemLocation
).fileName
== fileName
);
1378 parentNode
->children
[itemLocation
].fileName
= fileName
;
1381 if (info
.size
== -1) {
1382 removeNode(parentNode
, itemLocation
);
1386 if (parentNode
->children
.at(itemLocation
) != info
) {
1387 parentNode
->children
[itemLocation
].populate(info
);
1388 int visibleLocation
= parentNode
->visibleLocation(itemLocation
);
1389 bypassFilters
.removeAll(&(parentNode
->children
.at(itemLocation
)));
1390 // brand new information.
1391 if (filtersAcceptsNode(&(parentNode
->children
[itemLocation
]))) {
1392 if (visibleLocation
== -1) {
1393 newFiles
.append(fileName
);
1395 rowsToUpdate
.append(itemLocation
);
1398 if (visibleLocation
!= -1) {
1399 removeVisibleFile(parentNode
, visibleLocation
);
1401 // The file is not visible, don't do anything
1407 // bundle up all of the changed signals into as few as possible.
1408 qSort(rowsToUpdate
.begin(), rowsToUpdate
.end());
1409 int min
= -2; // ### TODO double check that -2 isn't needed
1411 for (int i
= 0; i
< rowsToUpdate
.count(); ++i
) {
1412 int value
= rowsToUpdate
.at(i
);
1415 if (i
!= rowsToUpdate
.count() - 1)
1418 if (i
!= rowsToUpdate
.count() - 1) {
1419 if ((value
== min
+ 1 && max
== -2) || value
== max
+ 1) {
1424 int visibleMin
= parentNode
->visibleLocation(min
);
1425 Q_ASSERT(parentNode
->visibleChildren
.at(visibleMin
) == min
);
1426 int visibleMax
= max
>= 0 ? parentNode
->visibleLocation(max
) : visibleMin
;
1427 Q_ASSERT(visibleMin
>= 0 && visibleMax
>= 0);
1428 QModelIndex bottom
= q
->index(translateVisibleLocation(parentNode
, visibleMin
), 0, parentIndex
);
1429 QModelIndex top
= q
->index(translateVisibleLocation(parentNode
, visibleMax
), 3, parentIndex
);
1430 emit q
->dataChanged(bottom
, top
);
1435 if (newFiles
.count() > 0) {
1436 addVisibleFiles(parentNode
, newFiles
);
1439 if (sortColumn
!= 0 && rowsToUpdate
.count() > 0) {
1448 void QFileSystemModelPrivate::_q_resolvedName(const QString
&fileName
, const QString
&resolvedName
)
1450 resolvedSymLinks
[fileName
] = resolvedName
;
1456 void QFileSystemModelPrivate::init()
1459 qRegisterMetaType
<QList
<QPair
<QString
,QExtendedInformation
> > >("QList<QPair<QString,QExtendedInformation> >");
1460 q
->connect(&fileInfoGatherer
, SIGNAL(newListOfFiles(const QString
&, const QStringList
&)),
1461 q
, SLOT(_q_directoryChanged(const QString
&, const QStringList
&)));
1462 q
->connect(&fileInfoGatherer
, SIGNAL(updates(const QString
&, const QList
<QPair
<QString
, QExtendedInformation
> > &)),
1463 q
, SLOT(_q_fileSystemChanged(const QString
&, const QList
<QPair
<QString
, QExtendedInformation
> > &)));
1464 q
->connect(&fileInfoGatherer
, SIGNAL(nameResolved(const QString
&, const QString
&)),
1465 q
, SLOT(_q_resolvedName(const QString
&, const QString
&)));
1466 q
->connect(&delayedSortTimer
, SIGNAL(timeout()), q
, SLOT(_q_performDelayedSort()), Qt::QueuedConnection
);
1472 Returns false if node doesn't pass the filters otherwise true
1474 QDir::Modified is not supported
1475 QDir::Drives is not supported
1477 bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode
*node
) const
1479 // always accept drives
1480 if (node
->parent
== &root
|| bypassFilters
.contains(node
))
1483 // If we don't know anything yet don't accept it
1484 if (!node
->hasInformation())
1487 const bool filterPermissions
= ((filters
& QDir::PermissionMask
)
1488 && (filters
& QDir::PermissionMask
) != QDir::PermissionMask
);
1489 const bool hideDirs
= !(filters
& (QDir::Dirs
| QDir::AllDirs
));
1490 const bool hideFiles
= !(filters
& QDir::Files
);
1491 const bool hideReadable
= !(!filterPermissions
|| (filters
& QDir::Readable
));
1492 const bool hideWritable
= !(!filterPermissions
|| (filters
& QDir::Writable
));
1493 const bool hideExecutable
= !(!filterPermissions
|| (filters
& QDir::Executable
));
1494 const bool hideHidden
= !(filters
& QDir::Hidden
);
1495 const bool hideSystem
= !(filters
& QDir::System
);
1496 const bool hideSymlinks
= (filters
& QDir::NoSymLinks
);
1497 const bool hideDotAndDotDot
= (filters
& QDir::NoDotAndDotDot
);
1499 // Note that we match the behavior of entryList and not QFileInfo on this and this
1500 // incompatibility won't be fixed until Qt 5 at least
1501 bool isDotOrDot
= ( (node
->fileName
== QLatin1String(".")
1502 || node
->fileName
== QLatin1String("..")));
1503 if ( (hideHidden
&& (!isDotOrDot
&& node
->isHidden()))
1504 || (hideSystem
&& node
->isSystem())
1505 || (hideDirs
&& node
->isDir())
1506 || (hideFiles
&& node
->isFile())
1507 || (hideSymlinks
&& node
->isSymLink())
1508 || (hideReadable
&& node
->isReadable())
1509 || (hideWritable
&& node
->isWritable())
1510 || (hideExecutable
&& node
->isExecutable())
1511 || (hideDotAndDotDot
&& isDotOrDot
))
1514 return nameFilterDisables
|| passNameFilters(node
);
1520 Returns true if node passes the name filters and should be visible.
1522 bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode
*node
) const
1524 #ifndef QT_NO_REGEXP
1525 if (nameFilters
.isEmpty())
1528 // Check the name regularexpression filters
1529 if (!(node
->isDir() && (filters
& QDir::AllDirs
))) {
1530 for (int i
= 0; i
< nameFilters
.size(); ++i
) {
1531 if (nameFilters
.at(i
).exactMatch(node
->fileName
))
1540 //#include "moc_qfilesystemmodel.cpp"