only include Applications by default on OS X
[ambit.git] / src / qfilesystemmodel.cpp
blob746fd9dacff485893694191a2d77d97d33e778bf
1 /****************************************************************************
2 **
3 ** Copyright (C) 1992-2007 Trolltech ASA. All rights reserved.
4 **
5 ** This file is part of the QtGui module of the Qt Toolkit.
6 **
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"
25 #include <qlocale.h>
26 #include <qmime.h>
27 #include <qurl.h>
28 #include <qdebug.h>
29 #include <qmessagebox.h>
31 /*!
32 \enum QFileSystemModel::Roles
33 \value FileIconRole
34 \value FilePathRole
35 \value FileNameRole
38 QFileSystemModel::QFileSystemModel(QObject *parent)
39 : QAbstractItemModel(parent)
41 d = new QFileSystemModelPrivate();
42 d->q = this;
43 d->init();
46 QFileSystemModel::~QFileSystemModel()
48 delete d;
51 /*!
52 \reimp
54 QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent) const
57 if (!hasIndex(row, column, parent))
58 return QModelIndex();
60 // get the parent node
61 QFileSystemModelPrivate::QFileSystemNode *parentNode = (parent.isValid() ? d->node(parent) :
62 const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&d->root));
63 Q_ASSERT(parentNode);
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);
68 Q_ASSERT(indexNode);
70 return createIndex(row, column, const_cast<QFileSystemModelPrivate::QFileSystemNode*>(indexNode));
73 /*!
74 \overload
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);
85 return idx;
88 /*!
89 \internal
91 Return the QFileSystemNode that goes to index.
93 QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QModelIndex &index) const
96 if (!index.isValid())
97 return const_cast<QFileSystemNode*>(&root);
99 Q_ASSERT(index.model() == q);
100 QFileSystemModelPrivate::QFileSystemNode *indexNode = static_cast<QFileSystemModelPrivate::QFileSystemNode*>(index.internalPointer());
101 Q_ASSERT(indexNode);
102 return indexNode;
106 \internal
108 Given a path return the matching QFileSystemNode or &root if invalid
110 QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const
113 Q_UNUSED(q);
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();
121 else
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())
127 #ifndef Q_OS_WIN
128 && path != QLatin1String("/")
129 #endif
131 return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
132 QModelIndex index = QModelIndex(); // start with "My Computer"
133 #ifdef Q_OS_WIN
134 if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path
135 QString host = QLatin1String("\\\\") + pathElements.first();
136 int r = 0;
137 for (; r < root.children.count(); ++r)
138 if (root.children.at(r).fileName == host)
139 break;
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();
150 } else {
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);
156 #else
157 // add the "/" item, since it is a valid path element on Unix
158 pathElements.prepend(QLatin1String("/"));
159 #endif
161 QFileSystemModelPrivate::QFileSystemNode *parent = node(index);
162 for (int i = 0; i < pathElements.count(); ++i) {
163 QString element = pathElements.at(i);
164 #ifdef Q_OS_WIN
165 // On Windows, "filename......." and "filename" are equivalent Task #133928
166 while (element.endsWith(QLatin1Char('.')))
167 element.chop(1);
168 #endif
169 int row = -1;
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
175 if (row != -1
176 && (parent->children.count() == 0 || parent->children[row].fileName != element))
177 row = -1;
179 if (row == -1) {
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("/")));
184 if (!info.exists())
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));
190 Q_ASSERT(row >= 0);
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) {
198 Fetching f;
199 f.dir = dir;
200 f.file = element;
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];
209 return parent;
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));
222 } else {
223 // qDebug() << "yah!, you saved a little gerbil soul";
226 d->toFetch.clear();
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())
239 return true;
240 QFileSystemModelPrivate::QFileSystemNode *n = d->node(index);
241 if (n->hasInformation())
242 return n->isDir();
243 return fileInfo(index).isDir();
246 qint64 QFileSystemModel::size(const QModelIndex &index) const
249 if (!index.isValid())
250 return 0;
251 return d->node(index)->size();
254 QString QFileSystemModel::type(const QModelIndex &index) const
257 if (!index.isValid())
258 return QString();
259 return d->node(index)->type();
262 QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const
265 if (!index.isValid())
266 return QDateTime();
267 return d->node(index)->lastModified();
271 \reimp
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));
290 if (visualRow == -1)
291 return QModelIndex();
292 return createIndex(visualRow, 0, parentNode);
296 \internal
298 return the index for node
300 QModelIndex QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode *node) const
303 QFileSystemModelPrivate::QFileSystemNode *parentNode = (node ? node->parent : 0);
304 if (node == &root)
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));
311 if (visualRow == -1)
312 return QModelIndex();
313 return q->createIndex(visualRow, 0, const_cast<QFileSystemNode*>(node));
317 \reimp
319 bool QFileSystemModel::hasChildren(const QModelIndex &parent) const
322 if (parent.column() > 0)
323 return false;
325 if (!parent.isValid()) // drives
326 return true;
328 const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
329 Q_ASSERT(indexNode);
330 return (indexNode->isDir());
334 \reimp
336 bool QFileSystemModel::canFetchMore(const QModelIndex &parent) const
339 const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
340 return (!indexNode->populatedChildren);
344 \reimp
346 void QFileSystemModel::fetchMore(const QModelIndex &parent)
349 QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
350 if (indexNode->populatedChildren)
351 return;
352 indexNode->populatedChildren = true;
353 d->fileInfoGatherer.list(filePath(parent));
357 \reimp
359 int QFileSystemModel::rowCount(const QModelIndex &parent) const
362 if (parent.column() > 0)
363 return 0;
365 if (!parent.isValid())
366 return d->root.visibleChildren.count();
368 const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
369 return parentNode->visibleChildren.count();
373 \reimp
375 int QFileSystemModel::columnCount(const QModelIndex &parent) const
377 return (parent.column() > 0) ? 0 : 4;
380 QVariant QFileSystemModel::myComputer(int role) const
383 switch (role) {
384 case Qt::DisplayRole:
385 return d->myComputer();
386 case Qt::DecorationRole:
387 return d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Computer);
389 return QVariant();
393 \reimp
395 QVariant QFileSystemModel::data(const QModelIndex &index, int role) const
398 if (!index.isValid() || index.model() != this)
399 return QVariant();
401 switch (role) {
402 case Qt::EditRole:
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);
409 default:
410 qWarning("data: invalid display value column %d", index.column());
411 break;
413 break;
414 case FilePathRole:
415 return filePath(index);
416 case FileNameRole:
417 return d->name(index);
418 case Qt::DecorationRole:
419 if (index.column() == 0) {
420 QIcon icon = d->icon(index);
421 if (icon.isNull()) {
422 if (d->node(index)->isDir())
423 icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Folder);
424 else
425 icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::File);
427 return icon;
429 break;
430 case Qt::TextAlignmentRole:
431 if (index.column() == 1)
432 return Qt::AlignRight;
433 break;
436 return QVariant();
440 \internal
442 QString QFileSystemModelPrivate::size(const QModelIndex &index) const
444 if (!index.isValid())
445 return QString();
446 const QFileSystemNode *n = node(index);
447 if (n->isDir()) {
448 #ifdef Q_OS_MAC
449 return QLatin1String("--");
450 #else
451 return QLatin1String("");
452 #endif
453 // Windows - ""
454 // OS X - "--"
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;
469 if (bytes >= tb)
470 return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3));
471 if (bytes >= gb)
472 return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2));
473 if (bytes >= mb)
474 return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1));
475 if (bytes >= kb)
476 return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb));
477 return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes));
481 \internal
483 QString QFileSystemModelPrivate::time(const QModelIndex &index) const
485 if (!index.isValid())
486 return QString();
487 #ifndef QT_NO_DATESTRING
488 return node(index)->lastModified().toString(Qt::SystemLocaleDate);
489 #else
490 Q_UNUSED(index);
491 return QString();
492 #endif
496 \internal
498 QString QFileSystemModelPrivate::type(const QModelIndex &index) const
500 if (!index.isValid())
501 return QString();
502 return node(index)->type();
506 \internal
508 QString QFileSystemModelPrivate::name(const QModelIndex &index) const
510 if (!index.isValid())
511 return QString();
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;
522 \internal
524 QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const
526 if (!index.isValid())
527 return QIcon();
528 return node(index)->icon();
532 \reimp
534 bool QFileSystemModel::setData(const QModelIndex &index, const QVariant &value, int role)
537 if (!index.isValid()
538 || index.column() != 0
539 || role != Qt::EditRole
540 || (flags(index) & Qt::ItemIsEditable) == 0) {
541 return false;
544 QString newName = value.toString();
545 if (newName == index.data().toString())
546 return true;
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.")
553 .arg(newName),
554 QMessageBox::Ok);
555 #endif // QT_NO_MESSAGEBOX
556 return false;
557 } else {
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);
569 d->delayedSort();
571 return true;
575 \reimp
577 QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
579 switch (role) {
580 case Qt::DecorationRole:
581 if (section == 0) {
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);
585 pixmap.fill(0);
586 pixmap.setAlphaChannel(pixmap.createAlphaMask());
587 return pixmap;
589 case Qt::TextAlignmentRole:
590 switch (section) {
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);
601 QString returnValue;
602 switch (section) {
603 case 0: returnValue = tr("Name");
604 break;
605 case 1: returnValue = tr("Size");
606 break;
607 case 2: returnValue =
608 #ifdef Q_OS_MAC
609 tr("Kind", "Match OS X Finder");
610 #else
611 tr("Type", "All other platforms");
612 #endif
613 break;
614 // Windows - Type
615 // OS X - Kind
616 // Konqueror - File Type
617 // Nautilus - Type
618 case 3: returnValue = tr("Date Modified");
619 break;
620 default: return QVariant();
622 return returnValue;
626 \reimp
628 Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const
631 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
632 if (!index.isValid())
633 return flags;
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
639 return flags;
642 flags |= Qt::ItemIsDragEnabled;
643 if (d->readOnly)
644 return flags;
645 if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) {
646 flags |= Qt::ItemIsEditable;
647 if (indexNode->isDir())
648 flags |= Qt::ItemIsDropEnabled;
650 return flags;
654 \internal
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.
670 Examples:
671 1, 2, 10, 55, 100
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
676 are looked at once.
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);
685 while (c1.isSpace())
686 c1 = getNextChar(s1, ++l1);
687 QChar c2 = getNextChar(s2, l2);
688 while (c2.isSpace())
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
701 for (
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)
710 break;
711 if (!is1ADigit)
712 return -1;
713 if (!is2ADigit)
714 return 1;
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);
732 if (r < 0)
733 return -1;
734 if (r > 0)
735 return 1;
737 // The two strings are the same (02 == 2) so fall back to the normal sort
738 return QString::compare(s1, s2, cs);
742 \internal
743 Helper functor used by sort()
745 class QFileSystemModelSorter
747 public:
748 inline QFileSystemModelSorter(int column) : sortColumn(column) {}
750 bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l,
751 const QFileSystemModelPrivate::QFileSystemNode *r) const
753 switch (sortColumn) {
754 case 0: {
755 #ifndef Q_OS_MAC
756 // place directories before files
757 bool left = l->isDir();
758 bool right = r->isDir();
759 if (left ^ right)
760 return left;
761 #endif
762 return QFileSystemModelPrivate::naturalCompare(l->fileName,
763 r->fileName, Qt::CaseInsensitive) < 0;
765 case 1:
766 // Directories go first
767 if (l->isDir() && !r->isDir())
768 return true;
769 return l->size() < r->size();
770 case 2:
771 return l->type() < r->type();
772 case 3:
773 return l->lastModified() < r->lastModified();
775 Q_ASSERT(false);
776 return false;
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);
786 private:
787 int sortColumn;
791 \internal
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)
800 return;
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));
819 \reimp
821 void QFileSystemModel::sort(int column, Qt::SortOrder order)
824 if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
825 return;
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);
846 newList.append(idx);
848 changePersistentIndexList(oldList, newList);
849 emit layoutChanged();
853 Returns a list of MIME types that can be used to describe a list of items
854 in the model.
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
867 empty list.
869 QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const
871 QList<QUrl> urls;
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();
877 data->setUrls(urls);
878 return data;
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)
891 Q_UNUSED(row);
892 Q_UNUSED(column);
893 if (!parent.isValid() || isReadOnly())
894 return false;
896 bool success = true;
897 QString to = filePath(parent) + QDir::separator();
899 QList<QUrl> urls = data->urls();
900 QList<QUrl>::const_iterator it = urls.constBegin();
902 switch (action) {
903 case Qt::CopyAction:
904 for (; it != urls.constEnd(); ++it) {
905 QString path = (*it).toLocalFile();
906 success = QFile::copy(path, to + QFileInfo(path).fileName()) && success;
908 break;
909 case Qt::LinkAction:
910 for (; it != urls.constEnd(); ++it) {
911 QString path = (*it).toLocalFile();
912 success = QFile::link(path, to + QFileInfo(path).fileName()) && success;
914 break;
915 case Qt::MoveAction:
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;
921 break;
922 default:
923 return false;
926 return success;
930 \reimp
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
939 \a index given.
941 QString QFileSystemModel::filePath(const QModelIndex &index) const
944 if (!index.isValid())
945 return QString();
946 Q_ASSERT(index.model() == this);
948 QStringList path;
949 QModelIndex idx = index;
950 while (idx.isValid()) {
951 path.prepend(d->name(idx));
952 idx = idx.parent();
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));
970 Q_ASSERT(r >= 0);
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;
1002 if (showDrives) {
1003 // otherwise dir will become '.'
1004 d->rootDir.setPath(QLatin1String(""));
1005 } else {
1006 newRootIndex = d->index(newPathDir.path());
1008 fetchMore(newRootIndex);
1009 emit rootPathChanged(newPath);
1010 d->forceSort = true;
1011 d->delayedSort();
1012 return newRootIndex;
1016 The currently set root path
1018 \sa rootDirectory()
1020 QString QFileSystemModel::rootPath() const
1023 return d->rootDir.path();
1027 The currently set directory
1029 \sa rootPath();
1031 QDir QFileSystemModel::rootDirectory() const
1034 QDir dir(d->rootDir);
1035 dir.setNameFilters(nameFilters());
1036 dir.setFilter(filter());
1037 return dir;
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.
1064 \sa QDir::Filters
1066 void QFileSystemModel::setFilter(QDir::Filters filters)
1069 d->filters = filters;
1070 // CaseSensitivity might have changed
1071 setNameFilters(nameFilters());
1072 d->forceSort = true;
1073 d->delayedSort();
1077 Returns the filter specification for the directory model.
1079 \sa QDir::Filters
1081 QDir::Filters QFileSystemModel::filter() const
1084 return d->filters;
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
1123 return d->readOnly;
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)
1136 return;
1137 d->nameFilterDisables = enable;
1138 d->forceSort = true;
1139 d->delayedSort();
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;
1163 d->delayedSort();
1164 #endif
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();
1178 #endif
1179 return filters;
1183 \internal
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);
1202 else
1203 iterator = qBinaryFind(newFiles.begin(), newFiles.end(),
1204 parentNode->children.at(i).fileName, caseInsensitiveLessThan);
1205 if (iterator == newFiles.constEnd()) {
1206 removeNode(parentNode, i);
1212 \internal
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();
1227 } else {
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;
1240 \internal
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)
1259 if (vLocation >= 0)
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)
1266 q->endRemoveRows();
1270 \internal
1271 Helper functor used by addVisibleFiles()
1273 class QFileSystemModelVisibleFinder
1275 public:
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)));
1283 int location;
1284 private:
1285 QFileSystemModelPrivate::QFileSystemNode *parentNode;
1286 QFileSystemModelSorter *sorter;
1290 \internal
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());
1313 if (!indexHidden)
1314 q->beginInsertRows(parent, realHome, realHome);
1315 parentNode->visibleChildren.insert(realHome, location);
1316 if (!indexHidden)
1317 q->endInsertRows();
1322 \internal
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)
1332 return;
1333 QModelIndex parent = index(parentNode);
1334 bool indexHidden = isHiddenByFilter(parentNode, parent);
1335 if (!indexHidden)
1336 q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
1337 translateVisibleLocation(parentNode, vLocation));
1338 parentNode->visibleChildren.removeAt(vLocation);
1339 if (!indexHidden)
1340 q->endRemoveRows();
1344 \internal
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)
1365 ++rowsToUpdate[i];
1367 if (parentNode->caseSensitive()) {
1368 if (parentNode->children.at(itemLocation).fileName != fileName)
1369 continue;
1370 } else {
1371 if (parentNode->children.at(itemLocation).fileName.toLower() != fileName.toLower())
1372 continue;
1375 if (parentNode->caseSensitive()) {
1376 Q_ASSERT(parentNode->children.at(itemLocation).fileName == fileName);
1377 } else {
1378 parentNode->children[itemLocation].fileName = fileName;
1381 if (info.size == -1) {
1382 removeNode(parentNode, itemLocation);
1383 continue;
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);
1394 } else {
1395 rowsToUpdate.append(itemLocation);
1397 } else {
1398 if (visibleLocation != -1) {
1399 removeVisibleFile(parentNode, visibleLocation);
1400 } else {
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
1410 int max = -2;
1411 for (int i = 0; i < rowsToUpdate.count(); ++i) {
1412 int value = rowsToUpdate.at(i);
1413 if (min == -2) {
1414 min = value;
1415 if (i != rowsToUpdate.count() - 1)
1416 continue;
1418 if (i != rowsToUpdate.count() - 1) {
1419 if ((value == min + 1 && max == -2) || value == max + 1) {
1420 max = value;
1421 continue;
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);
1431 min = -2;
1432 max = -2;
1435 if (newFiles.count() > 0) {
1436 addVisibleFiles(parentNode, newFiles);
1439 if (sortColumn != 0 && rowsToUpdate.count() > 0) {
1440 forceSort = true;
1441 delayedSort();
1446 \internal
1448 void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
1450 resolvedSymLinks[fileName] = resolvedName;
1454 \internal
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);
1470 \internal
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))
1481 return true;
1483 // If we don't know anything yet don't accept it
1484 if (!node->hasInformation())
1485 return false;
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))
1512 return false;
1514 return nameFilterDisables || passNameFilters(node);
1518 \internal
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())
1526 return true;
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))
1532 return true;
1534 return false;
1536 #endif
1537 return true;
1540 //#include "moc_qfilesystemmodel.cpp"