source: trunk/src/gui/itemviews/qdirmodel.cpp@ 376

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

gui: Ported QDirModel to OS/2.

File size: 39.2 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 "qdirmodel.h"
43
44#ifndef QT_NO_DIRMODEL
45#include <qstack.h>
46#include <qfile.h>
47#include <qurl.h>
48#include <qmime.h>
49#include <qpair.h>
50#include <qvector.h>
51#include <qobject.h>
52#include <qdatetime.h>
53#include <qlocale.h>
54#include <qstyle.h>
55#include <qapplication.h>
56#include <private/qabstractitemmodel_p.h>
57#include <qdebug.h>
58
59/*!
60 \enum QDirModel::Roles
61 \value FileIconRole
62 \value FilePathRole
63 \value FileNameRole
64*/
65
66QT_BEGIN_NAMESPACE
67
68class QDirModelPrivate : public QAbstractItemModelPrivate
69{
70 Q_DECLARE_PUBLIC(QDirModel)
71
72public:
73 struct QDirNode
74 {
75 QDirNode() : parent(0), populated(false), stat(false) {}
76 ~QDirNode() { children.clear(); }
77 QDirNode *parent;
78 QFileInfo info;
79 QIcon icon; // cache the icon
80 mutable QVector<QDirNode> children;
81 mutable bool populated; // have we read the children
82 mutable bool stat;
83 };
84
85 QDirModelPrivate()
86 : resolveSymlinks(true),
87 readOnly(true),
88 lazyChildCount(false),
89 allowAppendChild(true),
90 iconProvider(&defaultProvider),
91 shouldStat(true) // ### This is set to false by QFileDialog
92 { }
93
94 void init();
95 QDirNode *node(int row, QDirNode *parent) const;
96 QVector<QDirNode> children(QDirNode *parent, bool stat) const;
97
98 void _q_refresh();
99
100 void savePersistentIndexes();
101 void restorePersistentIndexes();
102
103 QFileInfoList entryInfoList(const QString &path) const;
104 QStringList entryList(const QString &path) const;
105
106 QString name(const QModelIndex &index) const;
107 QString size(const QModelIndex &index) const;
108 QString type(const QModelIndex &index) const;
109 QString time(const QModelIndex &index) const;
110
111 void appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const;
112 static QFileInfo resolvedInfo(QFileInfo info);
113
114 inline QDirNode *node(const QModelIndex &index) const;
115 inline void populate(QDirNode *parent) const;
116 inline void clear(QDirNode *parent) const;
117
118 void invalidate();
119
120 mutable QDirNode root;
121 bool resolveSymlinks;
122 bool readOnly;
123 bool lazyChildCount;
124 bool allowAppendChild;
125
126 QDir::Filters filters;
127 QDir::SortFlags sort;
128 QStringList nameFilters;
129
130 QFileIconProvider *iconProvider;
131 QFileIconProvider defaultProvider;
132
133 struct SavedPersistent {
134 QString path;
135 int column;
136 QPersistentModelIndexData *data;
137 QPersistentModelIndex index;
138 };
139 QList<SavedPersistent> savedPersistent;
140 QPersistentModelIndex toBeRefreshed;
141
142 bool shouldStat; // use the "carefull not to stat directories" mode
143};
144
145void qt_setDirModelShouldNotStat(QDirModelPrivate *modelPrivate)
146{
147 modelPrivate->shouldStat = false;
148}
149
150QDirModelPrivate::QDirNode *QDirModelPrivate::node(const QModelIndex &index) const
151{
152 QDirModelPrivate::QDirNode *n =
153 static_cast<QDirModelPrivate::QDirNode*>(index.internalPointer());
154 Q_ASSERT(n);
155 return n;
156}
157
158void QDirModelPrivate::populate(QDirNode *parent) const
159{
160 Q_ASSERT(parent);
161 parent->children = children(parent, parent->stat);
162 parent->populated = true;
163}
164
165void QDirModelPrivate::clear(QDirNode *parent) const
166{
167 Q_ASSERT(parent);
168 parent->children.clear();
169 parent->populated = false;
170}
171
172void QDirModelPrivate::invalidate()
173{
174 QStack<const QDirNode*> nodes;
175 nodes.push(&root);
176 while (!nodes.empty()) {
177 const QDirNode *current = nodes.pop();
178 current->stat = false;
179 const QVector<QDirNode> children = current->children;
180 for (int i = 0; i < children.count(); ++i)
181 nodes.push(&children.at(i));
182 }
183}
184
185/*!
186 \class QDirModel
187
188 \brief The QDirModel class provides a data model for the local filesystem.
189
190 \ingroup model-view
191
192 This class provides access to the local filesystem, providing functions
193 for renaming and removing files and directories, and for creating new
194 directories. In the simplest case, it can be used with a suitable display
195 widget as part of a browser or filer.
196
197 QDirModel keeps a cache with file information. The cache needs to be
198 updated with refresh().
199
200 A directory model that displays the contents of a default directory
201 is usually constructed with a parent object:
202
203 \snippet doc/src/snippets/shareddirmodel/main.cpp 2
204
205 A tree view can be used to display the contents of the model
206
207 \snippet doc/src/snippets/shareddirmodel/main.cpp 4
208
209 and the contents of a particular directory can be displayed by
210 setting the tree view's root index:
211
212 \snippet doc/src/snippets/shareddirmodel/main.cpp 7
213
214 The view's root index can be used to control how much of a
215 hierarchical model is displayed. QDirModel provides a convenience
216 function that returns a suitable model index for a path to a
217 directory within the model.
218
219 QDirModel can be accessed using the standard interface provided by
220 QAbstractItemModel, but it also provides some convenience functions
221 that are specific to a directory model. The fileInfo() and isDir()
222 functions provide information about the underlying files and directories
223 related to items in the model.
224
225 Directories can be created and removed using mkdir(), rmdir(), and the
226 model will be automatically updated to take the changes into account.
227
228 \note QDirModel requires an instance of a GUI application.
229
230 \sa nameFilters(), setFilter(), filter(), QListView, QTreeView,
231 {Dir View Example}, {Model Classes}
232*/
233
234/*!
235 Constructs a new directory model with the given \a parent.
236 Only those files matching the \a nameFilters and the
237 \a filters are included in the model. The sort order is given by the
238 \a sort flags.
239*/
240
241QDirModel::QDirModel(const QStringList &nameFilters,
242 QDir::Filters filters,
243 QDir::SortFlags sort,
244 QObject *parent)
245 : QAbstractItemModel(*new QDirModelPrivate, parent)
246{
247 Q_D(QDirModel);
248 // we always start with QDir::drives()
249 d->nameFilters = nameFilters.isEmpty() ? QStringList(QLatin1String("*")) : nameFilters;
250 d->filters = filters;
251 d->sort = sort;
252 d->root.parent = 0;
253 d->root.info = QFileInfo();
254 d->clear(&d->root);
255}
256
257/*!
258 Constructs a directory model with the given \a parent.
259*/
260
261QDirModel::QDirModel(QObject *parent)
262 : QAbstractItemModel(*new QDirModelPrivate, parent)
263{
264 Q_D(QDirModel);
265 d->init();
266}
267
268/*!
269 \internal
270*/
271QDirModel::QDirModel(QDirModelPrivate &dd, QObject *parent)
272 : QAbstractItemModel(dd, parent)
273{
274 Q_D(QDirModel);
275 d->init();
276}
277
278/*!
279 Destroys this directory model.
280*/
281
282QDirModel::~QDirModel()
283{
284
285}
286
287/*!
288 Returns the model item index for the item in the \a parent with the
289 given \a row and \a column.
290
291*/
292
293QModelIndex QDirModel::index(int row, int column, const QModelIndex &parent) const
294{
295 Q_D(const QDirModel);
296 // note that rowCount does lazy population
297 if (column < 0 || column >= columnCount(parent) || row < 0 || parent.column() > 0)
298 return QModelIndex();
299 // make sure the list of children is up to date
300 QDirModelPrivate::QDirNode *p = (d->indexValid(parent) ? d->node(parent) : &d->root);
301 Q_ASSERT(p);
302 if (!p->populated)
303 d->populate(p); // populate without stat'ing
304 if (row >= p->children.count())
305 return QModelIndex();
306 // now get the internal pointer for the index
307 QDirModelPrivate::QDirNode *n = d->node(row, d->indexValid(parent) ? p : 0);
308 Q_ASSERT(n);
309
310 return createIndex(row, column, n);
311}
312
313/*!
314 Return the parent of the given \a child model item.
315*/
316
317QModelIndex QDirModel::parent(const QModelIndex &child) const
318{
319 Q_D(const QDirModel);
320
321 if (!d->indexValid(child))
322 return QModelIndex();
323 QDirModelPrivate::QDirNode *node = d->node(child);
324 QDirModelPrivate::QDirNode *par = (node ? node->parent : 0);
325 if (par == 0) // parent is the root node
326 return QModelIndex();
327
328 // get the parent's row
329 const QVector<QDirModelPrivate::QDirNode> children =
330 par->parent ? par->parent->children : d->root.children;
331 Q_ASSERT(children.count() > 0);
332 int row = (par - &(children.at(0)));
333 Q_ASSERT(row >= 0);
334
335 return createIndex(row, 0, par);
336}
337
338/*!
339 Returns the number of rows in the \a parent model item.
340
341*/
342
343int QDirModel::rowCount(const QModelIndex &parent) const
344{
345 Q_D(const QDirModel);
346 if (parent.column() > 0)
347 return 0;
348
349 if (!parent.isValid()) {
350 if (!d->root.populated) // lazy population
351 d->populate(&d->root);
352 return d->root.children.count();
353 }
354 if (parent.model() != this)
355 return 0;
356 QDirModelPrivate::QDirNode *p = d->node(parent);
357 if (p->info.isDir() && !p->populated) // lazy population
358 d->populate(p);
359 return p->children.count();
360}
361
362/*!
363 Returns the number of columns in the \a parent model item.
364
365*/
366
367int QDirModel::columnCount(const QModelIndex &parent) const
368{
369 if (parent.column() > 0)
370 return 0;
371 return 4;
372}
373
374/*!
375 Returns the data for the model item \a index with the given \a role.
376*/
377QVariant QDirModel::data(const QModelIndex &index, int role) const
378{
379 Q_D(const QDirModel);
380 if (!d->indexValid(index))
381 return QVariant();
382
383 if (role == Qt::DisplayRole || role == Qt::EditRole) {
384 switch (index.column()) {
385 case 0: return d->name(index);
386 case 1: return d->size(index);
387 case 2: return d->type(index);
388 case 3: return d->time(index);
389 default:
390 qWarning("data: invalid display value column %d", index.column());
391 return QVariant();
392 }
393 }
394
395 if (index.column() == 0) {
396 if (role == FileIconRole)
397 return fileIcon(index);
398 if (role == FilePathRole)
399 return filePath(index);
400 if (role == FileNameRole)
401 return fileName(index);
402 }
403
404 if (index.column() == 1 && Qt::TextAlignmentRole == role) {
405 return Qt::AlignRight;
406 }
407 return QVariant();
408}
409
410/*!
411 Sets the data for the model item \a index with the given \a role to
412 the data referenced by the \a value. Returns true if successful;
413 otherwise returns false.
414
415 \sa Qt::ItemDataRole
416*/
417
418bool QDirModel::setData(const QModelIndex &index, const QVariant &value, int role)
419{
420 Q_D(QDirModel);
421 if (!d->indexValid(index) || index.column() != 0
422 || (flags(index) & Qt::ItemIsEditable) == 0 || role != Qt::EditRole)
423 return false;
424
425 QDirModelPrivate::QDirNode *node = d->node(index);
426 QDir dir = node->info.dir();
427 QString name = value.toString();
428 if (dir.rename(node->info.fileName(), name)) {
429 node->info = QFileInfo(dir, name);
430 QModelIndex sibling = index.sibling(index.row(), 3);
431 emit dataChanged(index, sibling);
432
433 d->toBeRefreshed = index.parent();
434 QMetaObject::invokeMethod(this, "_q_refresh", Qt::QueuedConnection);
435
436 return true;
437 }
438
439 return false;
440}
441
442/*!
443 Returns the data stored under the given \a role for the specified \a section
444 of the header with the given \a orientation.
445*/
446
447QVariant QDirModel::headerData(int section, Qt::Orientation orientation, int role) const
448{
449 if (orientation == Qt::Horizontal) {
450 if (role != Qt::DisplayRole)
451 return QVariant();
452 switch (section) {
453 case 0: return tr("Name");
454 case 1: return tr("Size");
455 case 2: return
456#ifdef Q_OS_MAC
457 tr("Kind", "Match OS X Finder");
458#else
459 tr("Type", "All other platforms");
460#endif
461 // Windows - Type
462 // OS X - Kind
463 // Konqueror - File Type
464 // Nautilus - Type
465 case 3: return tr("Date Modified");
466 default: return QVariant();
467 }
468 }
469 return QAbstractItemModel::headerData(section, orientation, role);
470}
471
472/*!
473 Returns true if the \a parent model item has children; otherwise
474 returns false.
475*/
476
477bool QDirModel::hasChildren(const QModelIndex &parent) const
478{
479 Q_D(const QDirModel);
480 if (parent.column() > 0)
481 return false;
482
483 if (!parent.isValid()) // the invalid index is the "My Computer" item
484 return true; // the drives
485 QDirModelPrivate::QDirNode *p = d->node(parent);
486 Q_ASSERT(p);
487
488 if (d->lazyChildCount) // optimization that only checks for children if the node has been populated
489 return p->info.isDir();
490 return p->info.isDir() && rowCount(parent) > 0;
491}
492
493/*!
494 Returns the item flags for the given \a index in the model.
495
496 \sa Qt::ItemFlags
497*/
498Qt::ItemFlags QDirModel::flags(const QModelIndex &index) const
499{
500 Q_D(const QDirModel);
501 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
502 if (!d->indexValid(index))
503 return flags;
504 flags |= Qt::ItemIsDragEnabled;
505 if (d->readOnly)
506 return flags;
507 QDirModelPrivate::QDirNode *node = d->node(index);
508 if ((index.column() == 0) && node->info.isWritable()) {
509 flags |= Qt::ItemIsEditable;
510 if (fileInfo(index).isDir()) // is directory and is editable
511 flags |= Qt::ItemIsDropEnabled;
512 }
513 return flags;
514}
515
516/*!
517 Sort the model items in the \a column using the \a order given.
518 The order is a value defined in \l Qt::SortOrder.
519*/
520
521void QDirModel::sort(int column, Qt::SortOrder order)
522{
523 QDir::SortFlags sort = QDir::DirsFirst | QDir::IgnoreCase;
524 if (order == Qt::DescendingOrder)
525 sort |= QDir::Reversed;
526
527 switch (column) {
528 case 0:
529 sort |= QDir::Name;
530 break;
531 case 1:
532 sort |= QDir::Size;
533 break;
534 case 2:
535 sort |= QDir::Type;
536 break;
537 case 3:
538 sort |= QDir::Time;
539 break;
540 default:
541 break;
542 }
543
544 setSorting(sort);
545}
546
547/*!
548 Returns a list of MIME types that can be used to describe a list of items
549 in the model.
550*/
551
552QStringList QDirModel::mimeTypes() const
553{
554 return QStringList(QLatin1String("text/uri-list"));
555}
556
557/*!
558 Returns an object that contains a serialized description of the specified
559 \a indexes. The format used to describe the items corresponding to the
560 indexes is obtained from the mimeTypes() function.
561
562 If the list of indexes is empty, 0 is returned rather than a serialized
563 empty list.
564*/
565
566QMimeData *QDirModel::mimeData(const QModelIndexList &indexes) const
567{
568 QList<QUrl> urls;
569 QList<QModelIndex>::const_iterator it = indexes.begin();
570 for (; it != indexes.end(); ++it)
571 if ((*it).column() == 0)
572 urls << QUrl::fromLocalFile(filePath(*it));
573 QMimeData *data = new QMimeData();
574 data->setUrls(urls);
575 return data;
576}
577
578/*!
579 Handles the \a data supplied by a drag and drop operation that ended with
580 the given \a action over the row in the model specified by the \a row and
581 \a column and by the \a parent index.
582
583 \sa supportedDropActions()
584*/
585
586bool QDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
587 int /* row */, int /* column */, const QModelIndex &parent)
588{
589 Q_D(QDirModel);
590 if (!d->indexValid(parent) || isReadOnly())
591 return false;
592
593 bool success = true;
594 QString to = filePath(parent) + QDir::separator();
595 QModelIndex _parent = parent;
596
597 QList<QUrl> urls = data->urls();
598 QList<QUrl>::const_iterator it = urls.constBegin();
599
600 switch (action) {
601 case Qt::CopyAction:
602 for (; it != urls.constEnd(); ++it) {
603 QString path = (*it).toLocalFile();
604 success = QFile::copy(path, to + QFileInfo(path).fileName()) && success;
605 }
606 break;
607 case Qt::LinkAction:
608 for (; it != urls.constEnd(); ++it) {
609 QString path = (*it).toLocalFile();
610 success = QFile::link(path, to + QFileInfo(path).fileName()) && success;
611 }
612 break;
613 case Qt::MoveAction:
614 for (; it != urls.constEnd(); ++it) {
615 QString path = (*it).toLocalFile();
616 if (QFile::copy(path, to + QFileInfo(path).fileName())
617 && QFile::remove(path)) {
618 QModelIndex idx=index(QFileInfo(path).path());
619 if (idx.isValid()) {
620 refresh(idx);
621 //the previous call to refresh may invalidate the _parent. so recreate a new QModelIndex
622 _parent = index(to);
623 }
624 } else {
625 success = false;
626 }
627 }
628 break;
629 default:
630 return false;
631 }
632
633 if (success)
634 refresh(_parent);
635
636 return success;
637}
638
639/*!
640 Returns the drop actions supported by this model.
641
642 \sa Qt::DropActions
643*/
644
645Qt::DropActions QDirModel::supportedDropActions() const
646{
647 return Qt::CopyAction | Qt::MoveAction; // FIXME: LinkAction is not supported yet
648}
649
650/*!
651 Sets the \a provider of file icons for the directory model.
652
653*/
654
655void QDirModel::setIconProvider(QFileIconProvider *provider)
656{
657 Q_D(QDirModel);
658 d->iconProvider = provider;
659}
660
661/*!
662 Returns the file icon provider for this directory model.
663*/
664
665QFileIconProvider *QDirModel::iconProvider() const
666{
667 Q_D(const QDirModel);
668 return d->iconProvider;
669}
670
671/*!
672 Sets the name \a filters for the directory model.
673*/
674
675void QDirModel::setNameFilters(const QStringList &filters)
676{
677 Q_D(QDirModel);
678 d->nameFilters = filters;
679 emit layoutAboutToBeChanged();
680 if (d->shouldStat)
681 refresh(QModelIndex());
682 else
683 d->invalidate();
684 emit layoutChanged();
685}
686
687/*!
688 Returns a list of filters applied to the names in the model.
689*/
690
691QStringList QDirModel::nameFilters() const
692{
693 Q_D(const QDirModel);
694 return d->nameFilters;
695}
696
697/*!
698 Sets the directory model's filter to that specified by \a filters.
699
700 Note that the filter you set should always include the QDir::AllDirs enum value,
701 otherwise QDirModel won't be able to read the directory structure.
702
703 \sa QDir::Filters
704*/
705
706void QDirModel::setFilter(QDir::Filters filters)
707{
708 Q_D(QDirModel);
709 d->filters = filters;
710 emit layoutAboutToBeChanged();
711 if (d->shouldStat)
712 refresh(QModelIndex());
713 else
714 d->invalidate();
715 emit layoutChanged();
716}
717
718/*!
719 Returns the filter specification for the directory model.
720
721 \sa QDir::Filters
722*/
723
724QDir::Filters QDirModel::filter() const
725{
726 Q_D(const QDirModel);
727 return d->filters;
728}
729
730/*!
731 Sets the directory model's sorting order to that specified by \a sort.
732
733 \sa QDir::SortFlags
734*/
735
736void QDirModel::setSorting(QDir::SortFlags sort)
737{
738 Q_D(QDirModel);
739 d->sort = sort;
740 emit layoutAboutToBeChanged();
741 if (d->shouldStat)
742 refresh(QModelIndex());
743 else
744 d->invalidate();
745 emit layoutChanged();
746}
747
748/*!
749 Returns the sorting method used for the directory model.
750
751 \sa QDir::SortFlags */
752
753QDir::SortFlags QDirModel::sorting() const
754{
755 Q_D(const QDirModel);
756 return d->sort;
757}
758
759/*!
760 \property QDirModel::resolveSymlinks
761 \brief Whether the directory model should resolve symbolic links
762
763 This is only relevant on operating systems that support symbolic
764 links.
765*/
766void QDirModel::setResolveSymlinks(bool enable)
767{
768 Q_D(QDirModel);
769 d->resolveSymlinks = enable;
770}
771
772bool QDirModel::resolveSymlinks() const
773{
774 Q_D(const QDirModel);
775 return d->resolveSymlinks;
776}
777
778/*!
779 \property QDirModel::readOnly
780 \brief Whether the directory model allows writing to the file system
781
782 If this property is set to false, the directory model will allow renaming, copying
783 and deleting of files and directories.
784
785 This property is true by default
786*/
787
788void QDirModel::setReadOnly(bool enable)
789{
790 Q_D(QDirModel);
791 d->readOnly = enable;
792}
793
794bool QDirModel::isReadOnly() const
795{
796 Q_D(const QDirModel);
797 return d->readOnly;
798}
799
800/*!
801 \property QDirModel::lazyChildCount
802 \brief Whether the directory model optimizes the hasChildren function
803 to only check if the item is a directory.
804
805 If this property is set to false, the directory model will make sure that a directory
806 actually containes any files before reporting that it has children.
807 Otherwise the directory model will report that an item has children if the item
808 is a directory.
809
810 This property is false by default
811*/
812
813void QDirModel::setLazyChildCount(bool enable)
814{
815 Q_D(QDirModel);
816 d->lazyChildCount = enable;
817}
818
819bool QDirModel::lazyChildCount() const
820{
821 Q_D(const QDirModel);
822 return d->lazyChildCount;
823}
824
825/*!
826 QDirModel caches file information. This function updates the
827 cache. The \a parent parameter is the directory from which the
828 model is updated; the default value will update the model from
829 root directory of the file system (the entire model).
830*/
831
832void QDirModel::refresh(const QModelIndex &parent)
833{
834 Q_D(QDirModel);
835
836 QDirModelPrivate::QDirNode *n = d->indexValid(parent) ? d->node(parent) : &(d->root);
837
838 int rows = n->children.count();
839 if (rows == 0) {
840 emit layoutAboutToBeChanged();
841 n->stat = true; // make sure that next time we read all the info
842 n->populated = false;
843 emit layoutChanged();
844 return;
845 }
846
847 emit layoutAboutToBeChanged();
848 d->savePersistentIndexes();
849 d->rowsAboutToBeRemoved(parent, 0, rows - 1);
850 n->stat = true; // make sure that next time we read all the info
851 d->clear(n);
852 d->rowsRemoved(parent, 0, rows - 1);
853 d->restorePersistentIndexes();
854 emit layoutChanged();
855}
856
857/*!
858 \overload
859
860 Returns the model item index for the given \a path.
861*/
862
863QModelIndex QDirModel::index(const QString &path, int column) const
864{
865 Q_D(const QDirModel);
866
867 if (path.isEmpty() || path == QCoreApplication::translate("QFileDialog", "My Computer"))
868 return QModelIndex();
869
870 QString absolutePath = QDir(path).absolutePath();
871#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_OS2)
872 absolutePath = absolutePath.toLower();
873 // On Windows, "filename......." and "filename" are equivalent
874 if (absolutePath.endsWith(QLatin1Char('.'))) {
875 int i;
876 for (i = absolutePath.count() - 1; i >= 0; --i) {
877 if (absolutePath.at(i) != QLatin1Char('.'))
878 break;
879 }
880 absolutePath = absolutePath.left(i+1);
881 }
882#endif
883
884 QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts);
885 if ((pathElements.isEmpty() || !QFileInfo(path).exists())
886#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_OS2)
887 && path != QLatin1String("/")
888#endif
889 )
890 return QModelIndex();
891
892 QModelIndex idx; // start with "My Computer"
893 if (!d->root.populated) // make sure the root is populated
894 d->populate(&d->root);
895
896#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_OS2)
897 if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path
898 QString host = pathElements.first();
899 int r = 0;
900 for (; r < d->root.children.count(); ++r)
901 if (d->root.children.at(r).info.fileName() == host)
902 break;
903 bool childAppended = false;
904 if (r >= d->root.children.count() && d->allowAppendChild) {
905 d->appendChild(&d->root, QLatin1String("//") + host);
906 childAppended = true;
907 }
908 idx = index(r, 0, QModelIndex());
909 pathElements.pop_front();
910 if (childAppended)
911 emit const_cast<QDirModel*>(this)->layoutChanged();
912 } else if (pathElements.at(0).endsWith(QLatin1Char(':'))) {
913 pathElements[0] += QLatin1Char('/');
914 }
915#else
916 // add the "/" item, since it is a valid path element on unix
917 pathElements.prepend(QLatin1String("/"));
918#endif
919
920 for (int i = 0; i < pathElements.count(); ++i) {
921 Q_ASSERT(!pathElements.at(i).isEmpty());
922 QString element = pathElements.at(i);
923 QDirModelPrivate::QDirNode *parent = (idx.isValid() ? d->node(idx) : &d->root);
924
925 Q_ASSERT(parent);
926 if (!parent->populated)
927 d->populate(parent);
928
929 // search for the element in the child nodes first
930 int row = -1;
931 for (int j = parent->children.count() - 1; j >= 0; --j) {
932 const QFileInfo& fi = parent->children.at(j).info;
933 QString childFileName;
934#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_OS2)
935 childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath();
936 childFileName = childFileName.toLower();
937#else
938 childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath();
939#endif
940 if (childFileName == element) {
941 if (i == pathElements.count() - 1)
942 parent->children[j].stat = true;
943 row = j;
944 break;
945 }
946 }
947
948 // we couldn't find the path element, we create a new node since we _know_ that the path is valid
949 if (row == -1) {
950#if defined(Q_OS_WINCE)
951 QString newPath;
952 if (parent->info.isRoot())
953 newPath = parent->info.absoluteFilePath() + element;
954 else
955 newPath= parent->info.absoluteFilePath() + QLatin1Char('/') + element;
956#else
957 QString newPath = parent->info.absoluteFilePath() + QLatin1Char('/') + element;
958#endif
959 if (!d->allowAppendChild || !QFileInfo(newPath).isDir())
960 return QModelIndex();
961 d->appendChild(parent, newPath);
962 row = parent->children.count() - 1;
963 if (i == pathElements.count() - 1) // always stat children of the last element
964 parent->children[row].stat = true;
965 emit const_cast<QDirModel*>(this)->layoutChanged();
966 }
967
968 Q_ASSERT(row >= 0);
969 idx = createIndex(row, 0, static_cast<void*>(&parent->children[row]));
970 Q_ASSERT(idx.isValid());
971 }
972
973 if (column != 0)
974 return idx.sibling(idx.row(), column);
975 return idx;
976}
977
978/*!
979 Returns true if the model item \a index represents a directory;
980 otherwise returns false.
981*/
982
983bool QDirModel::isDir(const QModelIndex &index) const
984{
985 Q_D(const QDirModel);
986 Q_ASSERT(d->indexValid(index));
987 QDirModelPrivate::QDirNode *node = d->node(index);
988 return node->info.isDir();
989}
990
991/*!
992 Create a directory with the \a name in the \a parent model item.
993*/
994
995QModelIndex QDirModel::mkdir(const QModelIndex &parent, const QString &name)
996{
997 Q_D(QDirModel);
998 if (!d->indexValid(parent) || isReadOnly())
999 return QModelIndex();
1000
1001 QDirModelPrivate::QDirNode *p = d->node(parent);
1002 QString path = p->info.absoluteFilePath();
1003 // For the indexOf() method to work, the new directory has to be a direct child of
1004 // the parent directory.
1005
1006 QDir newDir(name);
1007 QDir dir(path);
1008 if (newDir.isRelative())
1009 newDir = QDir(path + QLatin1Char('/') + name);
1010 QString childName = newDir.dirName(); // Get the singular name of the directory
1011 newDir.cdUp();
1012
1013 if (newDir.absolutePath() != dir.absolutePath() || !dir.mkdir(name))
1014 return QModelIndex(); // nothing happened
1015
1016 refresh(parent);
1017
1018 QStringList entryList = d->entryList(path);
1019 int r = entryList.indexOf(childName);
1020 QModelIndex i = index(r, 0, parent); // return an invalid index
1021
1022 return i;
1023}
1024
1025/*!
1026 Removes the directory corresponding to the model item \a index in the
1027 directory model and \bold{deletes the corresponding directory from the
1028 file system}, returning true if successful. If the directory cannot be
1029 removed, false is returned.
1030
1031 \warning This function deletes directories from the file system; it does
1032 \bold{not} move them to a location where they can be recovered.
1033
1034 \sa remove()
1035*/
1036
1037bool QDirModel::rmdir(const QModelIndex &index)
1038{
1039 Q_D(QDirModel);
1040 if (!d->indexValid(index) || isReadOnly())
1041 return false;
1042
1043 QDirModelPrivate::QDirNode *n = d_func()->node(index);
1044 if (!n->info.isDir()) {
1045 qWarning("rmdir: the node is not a directory");
1046 return false;
1047 }
1048
1049 QModelIndex par = parent(index);
1050 QDirModelPrivate::QDirNode *p = d_func()->node(par);
1051 QDir dir = p->info.dir(); // parent dir
1052 QString path = n->info.absoluteFilePath();
1053 if (!dir.rmdir(path))
1054 return false;
1055
1056 refresh(par);
1057
1058 return true;
1059}
1060
1061/*!
1062 Removes the model item \a index from the directory model and \bold{deletes the
1063 corresponding file from the file system}, returning true if successful. If the
1064 item cannot be removed, false is returned.
1065
1066 \warning This function deletes files from the file system; it does \bold{not}
1067 move them to a location where they can be recovered.
1068
1069 \sa rmdir()
1070*/
1071
1072bool QDirModel::remove(const QModelIndex &index)
1073{
1074 Q_D(QDirModel);
1075 if (!d->indexValid(index) || isReadOnly())
1076 return false;
1077
1078 QDirModelPrivate::QDirNode *n = d_func()->node(index);
1079 if (n->info.isDir())
1080 return false;
1081
1082 QModelIndex par = parent(index);
1083 QDirModelPrivate::QDirNode *p = d_func()->node(par);
1084 QDir dir = p->info.dir(); // parent dir
1085 QString path = n->info.absoluteFilePath();
1086 if (!dir.remove(path))
1087 return false;
1088
1089 refresh(par);
1090
1091 return true;
1092}
1093
1094/*!
1095 Returns the path of the item stored in the model under the
1096 \a index given.
1097
1098*/
1099
1100QString QDirModel::filePath(const QModelIndex &index) const
1101{
1102 Q_D(const QDirModel);
1103 if (d->indexValid(index)) {
1104 QFileInfo fi = fileInfo(index);
1105 if (d->resolveSymlinks && fi.isSymLink())
1106 fi = d->resolvedInfo(fi);
1107 return QDir::cleanPath(fi.absoluteFilePath());
1108 }
1109 return QString(); // root path
1110}
1111
1112/*!
1113 Returns the name of the item stored in the model under the
1114 \a index given.
1115
1116*/
1117
1118QString QDirModel::fileName(const QModelIndex &index) const
1119{
1120 Q_D(const QDirModel);
1121 if (!d->indexValid(index))
1122 return QString();
1123 QFileInfo info = fileInfo(index);
1124 if (info.isRoot())
1125 return info.absoluteFilePath();
1126 if (d->resolveSymlinks && info.isSymLink())
1127 info = d->resolvedInfo(info);
1128 return info.fileName();
1129}
1130
1131/*!
1132 Returns the icons for the item stored in the model under the given
1133 \a index.
1134*/
1135
1136QIcon QDirModel::fileIcon(const QModelIndex &index) const
1137{
1138 Q_D(const QDirModel);
1139 if (!d->indexValid(index))
1140 return d->iconProvider->icon(QFileIconProvider::Computer);
1141 QDirModelPrivate::QDirNode *node = d->node(index);
1142 if (node->icon.isNull())
1143 node->icon = d->iconProvider->icon(node->info);
1144 return node->icon;
1145}
1146
1147/*!
1148 Returns the file information for the specified model \a index.
1149
1150 \bold{Note:} If the model index represents a symbolic link in the
1151 underlying filing system, the file information returned will contain
1152 information about the symbolic link itself, regardless of whether
1153 resolveSymlinks is enabled or not.
1154
1155 \sa QFileInfo::symLinkTarget()
1156*/
1157
1158QFileInfo QDirModel::fileInfo(const QModelIndex &index) const
1159{
1160 Q_D(const QDirModel);
1161 Q_ASSERT(d->indexValid(index));
1162
1163 QDirModelPrivate::QDirNode *node = d->node(index);
1164 return node->info;
1165}
1166
1167/*!
1168 \fn QObject *QDirModel::parent() const
1169 \internal
1170*/
1171
1172/*
1173 The root node is never seen outside the model.
1174*/
1175
1176void QDirModelPrivate::init()
1177{
1178 filters = QDir::AllEntries | QDir::NoDotAndDotDot;
1179 sort = QDir::Name;
1180 nameFilters << QLatin1String("*");
1181 root.parent = 0;
1182 root.info = QFileInfo();
1183 clear(&root);
1184}
1185
1186QDirModelPrivate::QDirNode *QDirModelPrivate::node(int row, QDirNode *parent) const
1187{
1188 if (row < 0)
1189 return 0;
1190
1191 bool isDir = !parent || parent->info.isDir();
1192 QDirNode *p = (parent ? parent : &root);
1193 if (isDir && !p->populated)
1194 populate(p); // will also resolve symlinks
1195
1196 if (row >= p->children.count()) {
1197 qWarning("node: the row does not exist");
1198 return 0;
1199 }
1200
1201 return const_cast<QDirNode*>(&p->children.at(row));
1202}
1203
1204QVector<QDirModelPrivate::QDirNode> QDirModelPrivate::children(QDirNode *parent, bool stat) const
1205{
1206 Q_ASSERT(parent);
1207 QFileInfoList infoList;
1208 if (parent == &root) {
1209 parent = 0;
1210 infoList = QDir::drives();
1211 } else if (parent->info.isDir()) {
1212 //resolve directory links only if requested.
1213 if (parent->info.isSymLink() && resolveSymlinks) {
1214 QString link = parent->info.symLinkTarget();
1215 if (link.size() > 1 && link.at(link.size() - 1) == QDir::separator())
1216 link.chop(1);
1217 if (stat)
1218 infoList = entryInfoList(link);
1219 else
1220 infoList = QDir(link).entryInfoList(nameFilters, QDir::AllEntries | QDir::System);
1221 } else {
1222 if (stat)
1223 infoList = entryInfoList(parent->info.absoluteFilePath());
1224 else
1225 infoList = QDir(parent->info.absoluteFilePath()).entryInfoList(nameFilters, QDir::AllEntries | QDir::System);
1226 }
1227 }
1228
1229 QVector<QDirNode> nodes(infoList.count());
1230 for (int i = 0; i < infoList.count(); ++i) {
1231 QDirNode &node = nodes[i];
1232 node.parent = parent;
1233 node.info = infoList.at(i);
1234 node.populated = false;
1235 node.stat = shouldStat;
1236 }
1237
1238 return nodes;
1239}
1240
1241void QDirModelPrivate::_q_refresh()
1242{
1243 Q_Q(QDirModel);
1244 q->refresh(toBeRefreshed);
1245 toBeRefreshed = QModelIndex();
1246}
1247
1248void QDirModelPrivate::savePersistentIndexes()
1249{
1250 Q_Q(QDirModel);
1251 savedPersistent.clear();
1252 foreach (QPersistentModelIndexData *data, persistent.indexes) {
1253 SavedPersistent saved;
1254 QModelIndex index = data->index;
1255 saved.path = q->filePath(index);
1256 saved.column = index.column();
1257 saved.data = data;
1258 saved.index = index;
1259 savedPersistent.append(saved);
1260 }
1261}
1262
1263void QDirModelPrivate::restorePersistentIndexes()
1264{
1265 Q_Q(QDirModel);
1266 bool allow = allowAppendChild;
1267 allowAppendChild = false;
1268 for (int i = 0; i < savedPersistent.count(); ++i) {
1269 QPersistentModelIndexData *data = savedPersistent.at(i).data;
1270 QString path = savedPersistent.at(i).path;
1271 int column = savedPersistent.at(i).column;
1272 QModelIndex idx = q->index(path, column);
1273 if (idx != data->index || data->model == 0) {
1274 //data->model may be equal to 0 if the model is getting destroyed
1275 persistent.indexes.remove(data->index);
1276 data->index = idx;
1277 data->model = q;
1278 if (idx.isValid())
1279 persistent.indexes.insert(idx, data);
1280 }
1281 }
1282 savedPersistent.clear();
1283 allowAppendChild = allow;
1284}
1285
1286QFileInfoList QDirModelPrivate::entryInfoList(const QString &path) const
1287{
1288 const QDir dir(path);
1289 return dir.entryInfoList(nameFilters, filters, sort);
1290}
1291
1292QStringList QDirModelPrivate::entryList(const QString &path) const
1293{
1294 const QDir dir(path);
1295 return dir.entryList(nameFilters, filters, sort);
1296}
1297
1298QString QDirModelPrivate::name(const QModelIndex &index) const
1299{
1300 const QDirNode *n = node(index);
1301 const QFileInfo info = n->info;
1302 if (info.isRoot()) {
1303 QString name = info.absoluteFilePath();
1304#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_OS2)
1305 if (name.startsWith(QLatin1Char('/'))) // UNC host
1306 return info.fileName();
1307 if (name.endsWith(QLatin1Char('/')))
1308 name.chop(1);
1309#endif
1310 return name;
1311 }
1312 return info.fileName();
1313}
1314
1315QString QDirModelPrivate::size(const QModelIndex &index) const
1316{
1317 const QDirNode *n = node(index);
1318 if (n->info.isDir()) {
1319#ifdef Q_OS_MAC
1320 return QLatin1String("--");
1321#else
1322 return QLatin1String("");
1323#endif
1324 // Windows - ""
1325 // OS X - "--"
1326 // Konqueror - "4 KB"
1327 // Nautilus - "9 items" (the number of children)
1328 }
1329
1330 // According to the Si standard KB is 1000 bytes, KiB is 1024
1331 // but on windows sizes are calulated by dividing by 1024 so we do what they do.
1332 const quint64 kb = 1024;
1333 const quint64 mb = 1024 * kb;
1334 const quint64 gb = 1024 * mb;
1335 const quint64 tb = 1024 * gb;
1336 quint64 bytes = n->info.size();
1337 if (bytes >= tb)
1338 return QLocale().toString(bytes / tb) + QString::fromLatin1(" TB");
1339 if (bytes >= gb)
1340 return QLocale().toString(bytes / gb) + QString::fromLatin1(" GB");
1341 if (bytes >= mb)
1342 return QLocale().toString(bytes / mb) + QString::fromLatin1(" MB");
1343 if (bytes >= kb)
1344 return QLocale().toString(bytes / kb) + QString::fromLatin1(" KB");
1345 return QLocale().toString(bytes) + QString::fromLatin1(" bytes");
1346}
1347
1348QString QDirModelPrivate::type(const QModelIndex &index) const
1349{
1350 return iconProvider->type(node(index)->info);
1351}
1352
1353QString QDirModelPrivate::time(const QModelIndex &index) const
1354{
1355#ifndef QT_NO_DATESTRING
1356 return node(index)->info.lastModified().toString(Qt::LocalDate);
1357#else
1358 Q_UNUSED(index);
1359 return QString();
1360#endif
1361}
1362
1363void QDirModelPrivate::appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const
1364{
1365 QDirModelPrivate::QDirNode node;
1366 node.populated = false;
1367 node.stat = shouldStat;
1368 node.parent = (parent == &root ? 0 : parent);
1369 node.info = QFileInfo(path);
1370 node.info.setCaching(true);
1371
1372 // The following append(node) may reallocate the vector, thus
1373 // we need to update the pointers to the childnodes parent.
1374 QDirModelPrivate *that = const_cast<QDirModelPrivate *>(this);
1375 that->savePersistentIndexes();
1376 parent->children.append(node);
1377 for (int i = 0; i < parent->children.count(); ++i) {
1378 QDirNode *childNode = &parent->children[i];
1379 for (int j = 0; j < childNode->children.count(); ++j)
1380 childNode->children[j].parent = childNode;
1381 }
1382 that->restorePersistentIndexes();
1383}
1384
1385QFileInfo QDirModelPrivate::resolvedInfo(QFileInfo info)
1386{
1387#ifdef Q_OS_WIN
1388 // On windows, we cannot create a shortcut to a shortcut.
1389 return QFileInfo(info.symLinkTarget());
1390#else
1391 QStringList paths;
1392 do {
1393 QFileInfo link(info.symLinkTarget());
1394 if (link.isRelative())
1395 info.setFile(info.absolutePath(), link.filePath());
1396 else
1397 info = link;
1398 if (paths.contains(info.absoluteFilePath()))
1399 return QFileInfo();
1400 paths.append(info.absoluteFilePath());
1401 } while (info.isSymLink());
1402 return info;
1403#endif
1404}
1405
1406QT_END_NAMESPACE
1407
1408#include "moc_qdirmodel.cpp"
1409
1410#endif // QT_NO_DIRMODEL
Note: See TracBrowser for help on using the repository browser.