diff options
Diffstat (limited to 'src/gui/itemviews/qdirmodel.cpp')
-rw-r--r-- | src/gui/itemviews/qdirmodel.cpp | 1410 |
1 files changed, 1410 insertions, 0 deletions
diff --git a/src/gui/itemviews/qdirmodel.cpp b/src/gui/itemviews/qdirmodel.cpp new file mode 100644 index 0000000..7da7c7a --- /dev/null +++ b/src/gui/itemviews/qdirmodel.cpp @@ -0,0 +1,1410 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdirmodel.h" + +#ifndef QT_NO_DIRMODEL +#include <qstack.h> +#include <qfile.h> +#include <qurl.h> +#include <qmime.h> +#include <qpair.h> +#include <qvector.h> +#include <qobject.h> +#include <qdatetime.h> +#include <qlocale.h> +#include <qstyle.h> +#include <qapplication.h> +#include <private/qabstractitemmodel_p.h> +#include <qdebug.h> + +/*! + \enum QDirModel::Roles + \value FileIconRole + \value FilePathRole + \value FileNameRole +*/ + +QT_BEGIN_NAMESPACE + +class QDirModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QDirModel) + +public: + struct QDirNode + { + QDirNode() : parent(0), populated(false), stat(false) {} + ~QDirNode() { children.clear(); } + QDirNode *parent; + QFileInfo info; + QIcon icon; // cache the icon + mutable QVector<QDirNode> children; + mutable bool populated; // have we read the children + mutable bool stat; + }; + + QDirModelPrivate() + : resolveSymlinks(true), + readOnly(true), + lazyChildCount(false), + allowAppendChild(true), + iconProvider(&defaultProvider), + shouldStat(true) // ### This is set to false by QFileDialog + { } + + void init(); + QDirNode *node(int row, QDirNode *parent) const; + QVector<QDirNode> children(QDirNode *parent, bool stat) const; + + void _q_refresh(); + + void savePersistentIndexes(); + void restorePersistentIndexes(); + + QFileInfoList entryInfoList(const QString &path) const; + QStringList entryList(const QString &path) const; + + QString name(const QModelIndex &index) const; + QString size(const QModelIndex &index) const; + QString type(const QModelIndex &index) const; + QString time(const QModelIndex &index) const; + + void appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const; + static QFileInfo resolvedInfo(QFileInfo info); + + inline QDirNode *node(const QModelIndex &index) const; + inline void populate(QDirNode *parent) const; + inline void clear(QDirNode *parent) const; + + void invalidate(); + + mutable QDirNode root; + bool resolveSymlinks; + bool readOnly; + bool lazyChildCount; + bool allowAppendChild; + + QDir::Filters filters; + QDir::SortFlags sort; + QStringList nameFilters; + + QFileIconProvider *iconProvider; + QFileIconProvider defaultProvider; + + struct SavedPersistent { + QString path; + int column; + QPersistentModelIndexData *data; + QPersistentModelIndex index; + }; + QList<SavedPersistent> savedPersistent; + QPersistentModelIndex toBeRefreshed; + + bool shouldStat; // use the "carefull not to stat directories" mode +}; + +void qt_setDirModelShouldNotStat(QDirModelPrivate *modelPrivate) +{ + modelPrivate->shouldStat = false; +} + +QDirModelPrivate::QDirNode *QDirModelPrivate::node(const QModelIndex &index) const +{ + QDirModelPrivate::QDirNode *n = + static_cast<QDirModelPrivate::QDirNode*>(index.internalPointer()); + Q_ASSERT(n); + return n; +} + +void QDirModelPrivate::populate(QDirNode *parent) const +{ + Q_ASSERT(parent); + parent->children = children(parent, parent->stat); + parent->populated = true; +} + +void QDirModelPrivate::clear(QDirNode *parent) const +{ + Q_ASSERT(parent); + parent->children.clear(); + parent->populated = false; +} + +void QDirModelPrivate::invalidate() +{ + QStack<const QDirNode*> nodes; + nodes.push(&root); + while (!nodes.empty()) { + const QDirNode *current = nodes.pop(); + current->stat = false; + const QVector<QDirNode> children = current->children; + for (int i = 0; i < children.count(); ++i) + nodes.push(&children.at(i)); + } +} + +/*! + \class QDirModel + + \brief The QDirModel class provides a data model for the local filesystem. + + \ingroup model-view + + This class provides access to the local filesystem, providing functions + for renaming and removing files and directories, and for creating new + directories. In the simplest case, it can be used with a suitable display + widget as part of a browser or filer. + + QDirModel keeps a cache with file information. The cache needs to be + updated with refresh(). + + A directory model that displays the contents of a default directory + is usually constructed with a parent object: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 2 + + A tree view can be used to display the contents of the model + + \snippet doc/src/snippets/shareddirmodel/main.cpp 4 + + and the contents of a particular directory can be displayed by + setting the tree view's root index: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 7 + + The view's root index can be used to control how much of a + hierarchical model is displayed. QDirModel provides a convenience + function that returns a suitable model index for a path to a + directory within the model. + + QDirModel can be accessed using the standard interface provided by + QAbstractItemModel, but it also provides some convenience functions + that are specific to a directory model. The fileInfo() and isDir() + functions provide information about the underlying files and directories + related to items in the model. + + Directories can be created and removed using mkdir(), rmdir(), and the + model will be automatically updated to take the changes into account. + + \note QDirModel requires an instance of a GUI application. + + \sa nameFilters(), setFilter(), filter(), QListView, QTreeView, + {Dir View Example}, {Model Classes} +*/ + +/*! + Constructs a new directory model with the given \a parent. + Only those files matching the \a nameFilters and the + \a filters are included in the model. The sort order is given by the + \a sort flags. +*/ + +QDirModel::QDirModel(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort, + QObject *parent) + : QAbstractItemModel(*new QDirModelPrivate, parent) +{ + Q_D(QDirModel); + // we always start with QDir::drives() + d->nameFilters = nameFilters.isEmpty() ? QStringList(QLatin1String("*")) : nameFilters; + d->filters = filters; + d->sort = sort; + d->root.parent = 0; + d->root.info = QFileInfo(); + d->clear(&d->root); +} + +/*! + Constructs a directory model with the given \a parent. +*/ + +QDirModel::QDirModel(QObject *parent) + : QAbstractItemModel(*new QDirModelPrivate, parent) +{ + Q_D(QDirModel); + d->init(); +} + +/*! + \internal +*/ +QDirModel::QDirModel(QDirModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QDirModel); + d->init(); +} + +/*! + Destroys this directory model. +*/ + +QDirModel::~QDirModel() +{ + +} + +/*! + Returns the model item index for the item in the \a parent with the + given \a row and \a column. + +*/ + +QModelIndex QDirModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QDirModel); + // note that rowCount does lazy population + if (column < 0 || column >= columnCount(parent) || row < 0 || parent.column() > 0) + return QModelIndex(); + // make sure the list of children is up to date + QDirModelPrivate::QDirNode *p = (d->indexValid(parent) ? d->node(parent) : &d->root); + Q_ASSERT(p); + if (!p->populated) + d->populate(p); // populate without stat'ing + if (row >= p->children.count()) + return QModelIndex(); + // now get the internal pointer for the index + QDirModelPrivate::QDirNode *n = d->node(row, d->indexValid(parent) ? p : 0); + Q_ASSERT(n); + + return createIndex(row, column, n); +} + +/*! + Return the parent of the given \a child model item. +*/ + +QModelIndex QDirModel::parent(const QModelIndex &child) const +{ + Q_D(const QDirModel); + + if (!d->indexValid(child)) + return QModelIndex(); + QDirModelPrivate::QDirNode *node = d->node(child); + QDirModelPrivate::QDirNode *par = (node ? node->parent : 0); + if (par == 0) // parent is the root node + return QModelIndex(); + + // get the parent's row + const QVector<QDirModelPrivate::QDirNode> children = + par->parent ? par->parent->children : d->root.children; + Q_ASSERT(children.count() > 0); + int row = (par - &(children.at(0))); + Q_ASSERT(row >= 0); + + return createIndex(row, 0, par); +} + +/*! + Returns the number of rows in the \a parent model item. + +*/ + +int QDirModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QDirModel); + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) { + if (!d->root.populated) // lazy population + d->populate(&d->root); + return d->root.children.count(); + } + if (parent.model() != this) + return 0; + QDirModelPrivate::QDirNode *p = d->node(parent); + if (p->info.isDir() && !p->populated) // lazy population + d->populate(p); + return p->children.count(); +} + +/*! + Returns the number of columns in the \a parent model item. + +*/ + +int QDirModel::columnCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + return 4; +} + +/*! + Returns the data for the model item \a index with the given \a role. +*/ +QVariant QDirModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { + case 0: return d->name(index); + case 1: return d->size(index); + case 2: return d->type(index); + case 3: return d->time(index); + default: + qWarning("data: invalid display value column %d", index.column()); + return QVariant(); + } + } + + if (index.column() == 0) { + if (role == FileIconRole) + return fileIcon(index); + if (role == FilePathRole) + return filePath(index); + if (role == FileNameRole) + return fileName(index); + } + + if (index.column() == 1 && Qt::TextAlignmentRole == role) { + return Qt::AlignRight; + } + return QVariant(); +} + +/*! + Sets the data for the model item \a index with the given \a role to + the data referenced by the \a value. Returns true if successful; + otherwise returns false. + + \sa Qt::ItemDataRole +*/ + +bool QDirModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || index.column() != 0 + || (flags(index) & Qt::ItemIsEditable) == 0 || role != Qt::EditRole) + return false; + + QDirModelPrivate::QDirNode *node = d->node(index); + QDir dir = node->info.dir(); + QString name = value.toString(); + if (dir.rename(node->info.fileName(), name)) { + node->info = QFileInfo(dir, name); + QModelIndex sibling = index.sibling(index.row(), 3); + emit dataChanged(index, sibling); + + d->toBeRefreshed = index.parent(); + QMetaObject::invokeMethod(this, "_q_refresh", Qt::QueuedConnection); + + return true; + } + + return false; +} + +/*! + Returns the data stored under the given \a role for the specified \a section + of the header with the given \a orientation. +*/ + +QVariant QDirModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role != Qt::DisplayRole) + return QVariant(); + switch (section) { + case 0: return tr("Name"); + case 1: return tr("Size"); + case 2: return +#ifdef Q_OS_MAC + tr("Kind", "Match OS X Finder"); +#else + tr("Type", "All other platforms"); +#endif + // Windows - Type + // OS X - Kind + // Konqueror - File Type + // Nautilus - Type + case 3: return tr("Date Modified"); + default: return QVariant(); + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + +/*! + Returns true if the \a parent model item has children; otherwise + returns false. +*/ + +bool QDirModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QDirModel); + if (parent.column() > 0) + return false; + + if (!parent.isValid()) // the invalid index is the "My Computer" item + return true; // the drives + QDirModelPrivate::QDirNode *p = d->node(parent); + Q_ASSERT(p); + + if (d->lazyChildCount) // optimization that only checks for children if the node has been populated + return p->info.isDir(); + return p->info.isDir() && rowCount(parent) > 0; +} + +/*! + Returns the item flags for the given \a index in the model. + + \sa Qt::ItemFlags +*/ +Qt::ItemFlags QDirModel::flags(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (!d->indexValid(index)) + return flags; + flags |= Qt::ItemIsDragEnabled; + if (d->readOnly) + return flags; + QDirModelPrivate::QDirNode *node = d->node(index); + if ((index.column() == 0) && node->info.isWritable()) { + flags |= Qt::ItemIsEditable; + if (fileInfo(index).isDir()) // is directory and is editable + flags |= Qt::ItemIsDropEnabled; + } + return flags; +} + +/*! + Sort the model items in the \a column using the \a order given. + The order is a value defined in \l Qt::SortOrder. +*/ + +void QDirModel::sort(int column, Qt::SortOrder order) +{ + QDir::SortFlags sort = QDir::DirsFirst | QDir::IgnoreCase; + if (order == Qt::DescendingOrder) + sort |= QDir::Reversed; + + switch (column) { + case 0: + sort |= QDir::Name; + break; + case 1: + sort |= QDir::Size; + break; + case 2: + sort |= QDir::Type; + break; + case 3: + sort |= QDir::Time; + break; + default: + break; + } + + setSorting(sort); +} + +/*! + Returns a list of MIME types that can be used to describe a list of items + in the model. +*/ + +QStringList QDirModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + Returns an object that contains a serialized description of the specified + \a indexes. The format used to describe the items corresponding to the + indexes is obtained from the mimeTypes() function. + + If the list of indexes is empty, 0 is returned rather than a serialized + empty list. +*/ + +QMimeData *QDirModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QUrl> urls; + QList<QModelIndex>::const_iterator it = indexes.begin(); + for (; it != indexes.end(); ++it) + if ((*it).column() == 0) + urls << QUrl::fromLocalFile(filePath(*it)); + QMimeData *data = new QMimeData(); + data->setUrls(urls); + return data; +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action over the row in the model specified by the \a row and + \a column and by the \a parent index. + + \sa supportedDropActions() +*/ + +bool QDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int /* row */, int /* column */, const QModelIndex &parent) +{ + Q_D(QDirModel); + if (!d->indexValid(parent) || isReadOnly()) + return false; + + bool success = true; + QString to = filePath(parent) + QDir::separator(); + QModelIndex _parent = parent; + + QList<QUrl> urls = data->urls(); + QList<QUrl>::const_iterator it = urls.constBegin(); + + switch (action) { + case Qt::CopyAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::copy(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::LinkAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::link(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::MoveAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + if (QFile::copy(path, to + QFileInfo(path).fileName()) + && QFile::remove(path)) { + QModelIndex idx=index(QFileInfo(path).path()); + if (idx.isValid()) { + refresh(idx); + //the previous call to refresh may invalidate the _parent. so recreate a new QModelIndex + _parent = index(to); + } + } else { + success = false; + } + } + break; + default: + return false; + } + + if (success) + refresh(_parent); + + return success; +} + +/*! + Returns the drop actions supported by this model. + + \sa Qt::DropActions +*/ + +Qt::DropActions QDirModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; // FIXME: LinkAction is not supported yet +} + +/*! + Sets the \a provider of file icons for the directory model. + +*/ + +void QDirModel::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QDirModel); + d->iconProvider = provider; +} + +/*! + Returns the file icon provider for this directory model. +*/ + +QFileIconProvider *QDirModel::iconProvider() const +{ + Q_D(const QDirModel); + return d->iconProvider; +} + +/*! + Sets the name \a filters for the directory model. +*/ + +void QDirModel::setNameFilters(const QStringList &filters) +{ + Q_D(QDirModel); + d->nameFilters = filters; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns a list of filters applied to the names in the model. +*/ + +QStringList QDirModel::nameFilters() const +{ + Q_D(const QDirModel); + return d->nameFilters; +} + +/*! + Sets the directory model's filter to that specified by \a filters. + + Note that the filter you set should always include the QDir::AllDirs enum value, + otherwise QDirModel won't be able to read the directory structure. + + \sa QDir::Filters +*/ + +void QDirModel::setFilter(QDir::Filters filters) +{ + Q_D(QDirModel); + d->filters = filters; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns the filter specification for the directory model. + + \sa QDir::Filters +*/ + +QDir::Filters QDirModel::filter() const +{ + Q_D(const QDirModel); + return d->filters; +} + +/*! + Sets the directory model's sorting order to that specified by \a sort. + + \sa QDir::SortFlags +*/ + +void QDirModel::setSorting(QDir::SortFlags sort) +{ + Q_D(QDirModel); + d->sort = sort; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns the sorting method used for the directory model. + + \sa QDir::SortFlags */ + +QDir::SortFlags QDirModel::sorting() const +{ + Q_D(const QDirModel); + return d->sort; +} + +/*! + \property QDirModel::resolveSymlinks + \brief Whether the directory model should resolve symbolic links + + This is only relevant on operating systems that support symbolic + links. +*/ +void QDirModel::setResolveSymlinks(bool enable) +{ + Q_D(QDirModel); + d->resolveSymlinks = enable; +} + +bool QDirModel::resolveSymlinks() const +{ + Q_D(const QDirModel); + return d->resolveSymlinks; +} + +/*! + \property QDirModel::readOnly + \brief Whether the directory model allows writing to the file system + + If this property is set to false, the directory model will allow renaming, copying + and deleting of files and directories. + + This property is true by default +*/ + +void QDirModel::setReadOnly(bool enable) +{ + Q_D(QDirModel); + d->readOnly = enable; +} + +bool QDirModel::isReadOnly() const +{ + Q_D(const QDirModel); + return d->readOnly; +} + +/*! + \property QDirModel::lazyChildCount + \brief Whether the directory model optimizes the hasChildren function + to only check if the item is a directory. + + If this property is set to false, the directory model will make sure that a directory + actually containes any files before reporting that it has children. + Otherwise the directory model will report that an item has children if the item + is a directory. + + This property is false by default +*/ + +void QDirModel::setLazyChildCount(bool enable) +{ + Q_D(QDirModel); + d->lazyChildCount = enable; +} + +bool QDirModel::lazyChildCount() const +{ + Q_D(const QDirModel); + return d->lazyChildCount; +} + +/*! + QDirModel caches file information. This function updates the + cache. The \a parent parameter is the directory from which the + model is updated; the default value will update the model from + root directory of the file system (the entire model). +*/ + +void QDirModel::refresh(const QModelIndex &parent) +{ + Q_D(QDirModel); + + QDirModelPrivate::QDirNode *n = d->indexValid(parent) ? d->node(parent) : &(d->root); + + int rows = n->children.count(); + if (rows == 0) { + emit layoutAboutToBeChanged(); + n->stat = true; // make sure that next time we read all the info + n->populated = false; + emit layoutChanged(); + return; + } + + emit layoutAboutToBeChanged(); + d->savePersistentIndexes(); + d->rowsAboutToBeRemoved(parent, 0, rows - 1); + n->stat = true; // make sure that next time we read all the info + d->clear(n); + d->rowsRemoved(parent, 0, rows - 1); + d->restorePersistentIndexes(); + emit layoutChanged(); +} + +/*! + \overload + + Returns the model item index for the given \a path. +*/ + +QModelIndex QDirModel::index(const QString &path, int column) const +{ + Q_D(const QDirModel); + + if (path.isEmpty() || path == QCoreApplication::translate("QFileDialog", "My Computer")) + return QModelIndex(); + + QString absolutePath = QDir(path).absolutePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + absolutePath = absolutePath.toLower(); + // On Windows, "filename......." and "filename" are equivalent + if (absolutePath.endsWith(QLatin1Char('.'))) { + int i; + for (i = absolutePath.count() - 1; i >= 0; --i) { + if (absolutePath.at(i) != QLatin1Char('.')) + break; + } + absolutePath = absolutePath.left(i+1); + } +#endif + + QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts); + if ((pathElements.isEmpty() || !QFileInfo(path).exists()) +#if !defined(Q_OS_WIN) || defined(Q_OS_WINCE) + && path != QLatin1String("/") +#endif + ) + return QModelIndex(); + + QModelIndex idx; // start with "My Computer" + if (!d->root.populated) // make sure the root is populated + d->populate(&d->root); + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path + QString host = pathElements.first(); + int r = 0; + for (; r < d->root.children.count(); ++r) + if (d->root.children.at(r).info.fileName() == host) + break; + bool childAppended = false; + if (r >= d->root.children.count() && d->allowAppendChild) { + d->appendChild(&d->root, QLatin1String("//") + host); + childAppended = true; + } + idx = index(r, 0, QModelIndex()); + pathElements.pop_front(); + if (childAppended) + emit const_cast<QDirModel*>(this)->layoutChanged(); + } else if (pathElements.at(0).endsWith(QLatin1Char(':'))) { + pathElements[0] += QLatin1Char('/'); + } +#else + // add the "/" item, since it is a valid path element on unix + pathElements.prepend(QLatin1String("/")); +#endif + + for (int i = 0; i < pathElements.count(); ++i) { + Q_ASSERT(!pathElements.at(i).isEmpty()); + QString element = pathElements.at(i); + QDirModelPrivate::QDirNode *parent = (idx.isValid() ? d->node(idx) : &d->root); + + Q_ASSERT(parent); + if (!parent->populated) + d->populate(parent); + + // search for the element in the child nodes first + int row = -1; + for (int j = parent->children.count() - 1; j >= 0; --j) { + const QFileInfo& fi = parent->children.at(j).info; + QString childFileName; +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath(); + childFileName = childFileName.toLower(); +#else + childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath(); +#endif + if (childFileName == element) { + if (i == pathElements.count() - 1) + parent->children[j].stat = true; + row = j; + break; + } + } + + // we couldn't find the path element, we create a new node since we _know_ that the path is valid + if (row == -1) { +#if defined(Q_OS_WINCE) + QString newPath; + if (parent->info.isRoot()) + newPath = parent->info.absoluteFilePath() + element; + else + newPath= parent->info.absoluteFilePath() + QLatin1Char('/') + element; +#else + QString newPath = parent->info.absoluteFilePath() + QLatin1Char('/') + element; +#endif + if (!d->allowAppendChild || !QFileInfo(newPath).isDir()) + return QModelIndex(); + d->appendChild(parent, newPath); + row = parent->children.count() - 1; + if (i == pathElements.count() - 1) // always stat children of the last element + parent->children[row].stat = true; + emit const_cast<QDirModel*>(this)->layoutChanged(); + } + + Q_ASSERT(row >= 0); + idx = createIndex(row, 0, static_cast<void*>(&parent->children[row])); + Q_ASSERT(idx.isValid()); + } + + if (column != 0) + return idx.sibling(idx.row(), column); + return idx; +} + +/*! + Returns true if the model item \a index represents a directory; + otherwise returns false. +*/ + +bool QDirModel::isDir(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Q_ASSERT(d->indexValid(index)); + QDirModelPrivate::QDirNode *node = d->node(index); + return node->info.isDir(); +} + +/*! + Create a directory with the \a name in the \a parent model item. +*/ + +QModelIndex QDirModel::mkdir(const QModelIndex &parent, const QString &name) +{ + Q_D(QDirModel); + if (!d->indexValid(parent) || isReadOnly()) + return QModelIndex(); + + QDirModelPrivate::QDirNode *p = d->node(parent); + QString path = p->info.absoluteFilePath(); + // For the indexOf() method to work, the new directory has to be a direct child of + // the parent directory. + + QDir newDir(name); + QDir dir(path); + if (newDir.isRelative()) + newDir = QDir(path + QLatin1Char('/') + name); + QString childName = newDir.dirName(); // Get the singular name of the directory + newDir.cdUp(); + + if (newDir.absolutePath() != dir.absolutePath() || !dir.mkdir(name)) + return QModelIndex(); // nothing happened + + refresh(parent); + + QStringList entryList = d->entryList(path); + int r = entryList.indexOf(childName); + QModelIndex i = index(r, 0, parent); // return an invalid index + + return i; +} + +/*! + Removes the directory corresponding to the model item \a index in the + directory model and \bold{deletes the corresponding directory from the + file system}, returning true if successful. If the directory cannot be + removed, false is returned. + + \warning This function deletes directories from the file system; it does + \bold{not} move them to a location where they can be recovered. + + \sa remove() +*/ + +bool QDirModel::rmdir(const QModelIndex &index) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || isReadOnly()) + return false; + + QDirModelPrivate::QDirNode *n = d_func()->node(index); + if (!n->info.isDir()) { + qWarning("rmdir: the node is not a directory"); + return false; + } + + QModelIndex par = parent(index); + QDirModelPrivate::QDirNode *p = d_func()->node(par); + QDir dir = p->info.dir(); // parent dir + QString path = n->info.absoluteFilePath(); + if (!dir.rmdir(path)) + return false; + + refresh(par); + + return true; +} + +/*! + Removes the model item \a index from the directory model and \bold{deletes the + corresponding file from the file system}, returning true if successful. If the + item cannot be removed, false is returned. + + \warning This function deletes files from the file system; it does \bold{not} + move them to a location where they can be recovered. + + \sa rmdir() +*/ + +bool QDirModel::remove(const QModelIndex &index) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || isReadOnly()) + return false; + + QDirModelPrivate::QDirNode *n = d_func()->node(index); + if (n->info.isDir()) + return false; + + QModelIndex par = parent(index); + QDirModelPrivate::QDirNode *p = d_func()->node(par); + QDir dir = p->info.dir(); // parent dir + QString path = n->info.absoluteFilePath(); + if (!dir.remove(path)) + return false; + + refresh(par); + + return true; +} + +/*! + Returns the path of the item stored in the model under the + \a index given. + +*/ + +QString QDirModel::filePath(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (d->indexValid(index)) { + QFileInfo fi = fileInfo(index); + if (d->resolveSymlinks && fi.isSymLink()) + fi = d->resolvedInfo(fi); + return QDir::cleanPath(fi.absoluteFilePath()); + } + return QString(); // root path +} + +/*! + Returns the name of the item stored in the model under the + \a index given. + +*/ + +QString QDirModel::fileName(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return QString(); + QFileInfo info = fileInfo(index); + if (info.isRoot()) + return info.absoluteFilePath(); + if (d->resolveSymlinks && info.isSymLink()) + info = d->resolvedInfo(info); + return info.fileName(); +} + +/*! + Returns the icons for the item stored in the model under the given + \a index. +*/ + +QIcon QDirModel::fileIcon(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return d->iconProvider->icon(QFileIconProvider::Computer); + QDirModelPrivate::QDirNode *node = d->node(index); + if (node->icon.isNull()) + node->icon = d->iconProvider->icon(node->info); + return node->icon; +} + +/*! + Returns the file information for the specified model \a index. + + \bold{Note:} If the model index represents a symbolic link in the + underlying filing system, the file information returned will contain + information about the symbolic link itself, regardless of whether + resolveSymlinks is enabled or not. + + \sa QFileInfo::symLinkTarget() +*/ + +QFileInfo QDirModel::fileInfo(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Q_ASSERT(d->indexValid(index)); + + QDirModelPrivate::QDirNode *node = d->node(index); + return node->info; +} + +/*! + \fn QObject *QDirModel::parent() const + \internal +*/ + +/* + The root node is never seen outside the model. +*/ + +void QDirModelPrivate::init() +{ + filters = QDir::AllEntries | QDir::NoDotAndDotDot; + sort = QDir::Name; + nameFilters << QLatin1String("*"); + root.parent = 0; + root.info = QFileInfo(); + clear(&root); +} + +QDirModelPrivate::QDirNode *QDirModelPrivate::node(int row, QDirNode *parent) const +{ + if (row < 0) + return 0; + + bool isDir = !parent || parent->info.isDir(); + QDirNode *p = (parent ? parent : &root); + if (isDir && !p->populated) + populate(p); // will also resolve symlinks + + if (row >= p->children.count()) { + qWarning("node: the row does not exist"); + return 0; + } + + return const_cast<QDirNode*>(&p->children.at(row)); +} + +QVector<QDirModelPrivate::QDirNode> QDirModelPrivate::children(QDirNode *parent, bool stat) const +{ + Q_ASSERT(parent); + QFileInfoList infoList; + if (parent == &root) { + parent = 0; + infoList = QDir::drives(); + } else if (parent->info.isDir()) { + //resolve directory links only if requested. + if (parent->info.isSymLink() && resolveSymlinks) { + QString link = parent->info.symLinkTarget(); + if (link.size() > 1 && link.at(link.size() - 1) == QDir::separator()) + link.chop(1); + if (stat) + infoList = entryInfoList(link); + else + infoList = QDir(link).entryInfoList(nameFilters, QDir::AllEntries | QDir::System); + } else { + if (stat) + infoList = entryInfoList(parent->info.absoluteFilePath()); + else + infoList = QDir(parent->info.absoluteFilePath()).entryInfoList(nameFilters, QDir::AllEntries | QDir::System); + } + } + + QVector<QDirNode> nodes(infoList.count()); + for (int i = 0; i < infoList.count(); ++i) { + QDirNode &node = nodes[i]; + node.parent = parent; + node.info = infoList.at(i); + node.populated = false; + node.stat = shouldStat; + } + + return nodes; +} + +void QDirModelPrivate::_q_refresh() +{ + Q_Q(QDirModel); + q->refresh(toBeRefreshed); + toBeRefreshed = QModelIndex(); +} + +void QDirModelPrivate::savePersistentIndexes() +{ + Q_Q(QDirModel); + savedPersistent.clear(); + foreach (QPersistentModelIndexData *data, persistent.indexes) { + SavedPersistent saved; + QModelIndex index = data->index; + saved.path = q->filePath(index); + saved.column = index.column(); + saved.data = data; + saved.index = index; + savedPersistent.append(saved); + } +} + +void QDirModelPrivate::restorePersistentIndexes() +{ + Q_Q(QDirModel); + bool allow = allowAppendChild; + allowAppendChild = false; + for (int i = 0; i < savedPersistent.count(); ++i) { + QPersistentModelIndexData *data = savedPersistent.at(i).data; + QString path = savedPersistent.at(i).path; + int column = savedPersistent.at(i).column; + QModelIndex idx = q->index(path, column); + if (idx != data->index || data->model == 0) { + //data->model may be equal to 0 if the model is getting destroyed + persistent.indexes.remove(data->index); + data->index = idx; + data->model = q; + if (idx.isValid()) + persistent.indexes.insert(idx, data); + } + } + savedPersistent.clear(); + allowAppendChild = allow; +} + +QFileInfoList QDirModelPrivate::entryInfoList(const QString &path) const +{ + const QDir dir(path); + return dir.entryInfoList(nameFilters, filters, sort); +} + +QStringList QDirModelPrivate::entryList(const QString &path) const +{ + const QDir dir(path); + return dir.entryList(nameFilters, filters, sort); +} + +QString QDirModelPrivate::name(const QModelIndex &index) const +{ + const QDirNode *n = node(index); + const QFileInfo info = n->info; + if (info.isRoot()) { + QString name = info.absoluteFilePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (name.startsWith(QLatin1Char('/'))) // UNC host + return info.fileName(); + if (name.endsWith(QLatin1Char('/'))) + name.chop(1); +#endif + return name; + } + return info.fileName(); +} + +QString QDirModelPrivate::size(const QModelIndex &index) const +{ + const QDirNode *n = node(index); + if (n->info.isDir()) { +#ifdef Q_OS_MAC + return QLatin1String("--"); +#else + return QLatin1String(""); +#endif + // Windows - "" + // OS X - "--" + // Konqueror - "4 KB" + // Nautilus - "9 items" (the number of children) + } + + // According to the Si standard KB is 1000 bytes, KiB is 1024 + // but on windows sizes are calulated by dividing by 1024 so we do what they do. + const quint64 kb = 1024; + const quint64 mb = 1024 * kb; + const quint64 gb = 1024 * mb; + const quint64 tb = 1024 * gb; + quint64 bytes = n->info.size(); + if (bytes >= tb) + return QLocale().toString(bytes / tb) + QString::fromLatin1(" TB"); + if (bytes >= gb) + return QLocale().toString(bytes / gb) + QString::fromLatin1(" GB"); + if (bytes >= mb) + return QLocale().toString(bytes / mb) + QString::fromLatin1(" MB"); + if (bytes >= kb) + return QLocale().toString(bytes / kb) + QString::fromLatin1(" KB"); + return QLocale().toString(bytes) + QString::fromLatin1(" bytes"); +} + +QString QDirModelPrivate::type(const QModelIndex &index) const +{ + return iconProvider->type(node(index)->info); +} + +QString QDirModelPrivate::time(const QModelIndex &index) const +{ +#ifndef QT_NO_DATESTRING + return node(index)->info.lastModified().toString(Qt::LocalDate); +#else + Q_UNUSED(index); + return QString(); +#endif +} + +void QDirModelPrivate::appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const +{ + QDirModelPrivate::QDirNode node; + node.populated = false; + node.stat = shouldStat; + node.parent = (parent == &root ? 0 : parent); + node.info = QFileInfo(path); + node.info.setCaching(true); + + // The following append(node) may reallocate the vector, thus + // we need to update the pointers to the childnodes parent. + QDirModelPrivate *that = const_cast<QDirModelPrivate *>(this); + that->savePersistentIndexes(); + parent->children.append(node); + for (int i = 0; i < parent->children.count(); ++i) { + QDirNode *childNode = &parent->children[i]; + for (int j = 0; j < childNode->children.count(); ++j) + childNode->children[j].parent = childNode; + } + that->restorePersistentIndexes(); +} + +QFileInfo QDirModelPrivate::resolvedInfo(QFileInfo info) +{ +#ifdef Q_OS_WIN + // On windows, we cannot create a shortcut to a shortcut. + return QFileInfo(info.symLinkTarget()); +#else + QStringList paths; + do { + QFileInfo link(info.symLinkTarget()); + if (link.isRelative()) + info.setFile(info.absolutePath(), link.filePath()); + else + info = link; + if (paths.contains(info.absoluteFilePath())) + return QFileInfo(); + paths.append(info.absoluteFilePath()); + } while (info.isSymLink()); + return info; +#endif +} + +QT_END_NAMESPACE + +#include "moc_qdirmodel.cpp" + +#endif // QT_NO_DIRMODEL |