diff options
Diffstat (limited to 'src/gui/itemmodels/qfilesystemmodel.cpp')
-rw-r--r-- | src/gui/itemmodels/qfilesystemmodel.cpp | 451 |
1 files changed, 254 insertions, 197 deletions
diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp index 70c274b816..290891322f 100644 --- a/src/gui/itemmodels/qfilesystemmodel.cpp +++ b/src/gui/itemmodels/qfilesystemmodel.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWidgets module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qfilesystemmodel_p.h" #include "qfilesystemmodel.h" @@ -59,12 +23,15 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + /*! \enum QFileSystemModel::Roles \value FileIconRole \value FilePathRole \value FileNameRole \value FilePermissions + \value FileInfoRole The QFileInfo object for the index */ /*! @@ -142,7 +109,7 @@ QT_BEGIN_NAMESPACE */ /*! - \fn QIcon QFileSystemModel::fileName(const QModelIndex &index) const + \fn QString QFileSystemModel::fileName(const QModelIndex &index) const Returns the file name for the item stored in the model under the given \a index. @@ -177,7 +144,7 @@ QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const \fn void QFileSystemModel::fileRenamed(const QString &path, const QString &oldName, const QString &newName) This signal is emitted whenever a file with the \a oldName is successfully - renamed to \a newName. The file is located in in the directory \a path. + renamed to \a newName. The file is located in the directory \a path. */ /*! @@ -274,7 +241,7 @@ QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &pare */ QModelIndex QFileSystemModel::sibling(int row, int column, const QModelIndex &idx) const { - if (row == idx.row() && column < QFileSystemModelPrivate::NumColumns) { + if (row == idx.row() && column < columnCount(idx.parent())) { // cheap sibling operation: just adjust the column: return createIndex(row, column, idx.internalPointer()); } else { @@ -314,17 +281,17 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QM static QString qt_GetLongPathName(const QString &strShortPath) { if (strShortPath.isEmpty() - || strShortPath == QLatin1String(".") || strShortPath == QLatin1String("..")) + || strShortPath == "."_L1 || strShortPath == ".."_L1) return strShortPath; - if (strShortPath.length() == 2 && strShortPath.endsWith(QLatin1Char(':'))) + if (strShortPath.length() == 2 && strShortPath.endsWith(u':')) return strShortPath.toUpper(); const QString absPath = QDir(strShortPath).absolutePath(); - if (absPath.startsWith(QLatin1String("//")) - || absPath.startsWith(QLatin1String("\\\\"))) // unc + if (absPath.startsWith("//"_L1) + || absPath.startsWith("\\\\"_L1)) // unc return QDir::fromNativeSeparators(absPath); - if (absPath.startsWith(QLatin1Char('/'))) + if (absPath.startsWith(u'/')) return QString(); - const QString inputString = QLatin1String("\\\\?\\") + QDir::toNativeSeparators(absPath); + const QString inputString = "\\\\?\\"_L1 + QDir::toNativeSeparators(absPath); QVarLengthArray<TCHAR, MAX_PATH> buffer(MAX_PATH); DWORD result = ::GetLongPathName((wchar_t*)inputString.utf16(), buffer.data(), @@ -354,7 +321,7 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS { Q_Q(const QFileSystemModel); Q_UNUSED(q); - if (path.isEmpty() || path == myComputer() || path.startsWith(QLatin1Char(':'))) + if (path.isEmpty() || path == myComputer() || path.startsWith(u':')) return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); // Construct the nodes up to the new root path if they need to be built @@ -370,30 +337,33 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS absolutePath = QDir(longPath).absolutePath(); // ### TODO can we use bool QAbstractFileEngine::caseSensitive() const? - QStringList pathElements = absolutePath.split(QLatin1Char('/'), Qt::SkipEmptyParts); + QStringList pathElements = absolutePath.split(u'/', Qt::SkipEmptyParts); if ((pathElements.isEmpty()) #if !defined(Q_OS_WIN) - && QDir::fromNativeSeparators(longPath) != QLatin1String("/") + && QDir::fromNativeSeparators(longPath) != "/"_L1 #endif ) return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); QModelIndex index = QModelIndex(); // start with "My Computer" QString elementPath; - QChar separator = QLatin1Char('/'); + QChar separator = u'/'; QString trailingSeparator; #if defined(Q_OS_WIN) - if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path - QString host = QLatin1String("\\\\") + pathElements.constFirst(); + if (absolutePath.startsWith("//"_L1)) { // UNC path + QString host = "\\\\"_L1 + pathElements.constFirst(); if (absolutePath == QDir::fromNativeSeparators(host)) - absolutePath.append(QLatin1Char('/')); - if (longPath.endsWith(QLatin1Char('/')) && !absolutePath.endsWith(QLatin1Char('/'))) - absolutePath.append(QLatin1Char('/')); - if (absolutePath.endsWith(QLatin1Char('/'))) - trailingSeparator = QLatin1String("\\"); + absolutePath.append(u'/'); + if (longPath.endsWith(u'/') && !absolutePath.endsWith(u'/')) + absolutePath.append(u'/'); + if (absolutePath.endsWith(u'/')) + trailingSeparator = "\\"_L1; int r = 0; - QFileSystemModelPrivate::QFileSystemNode *rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); - if (!root.children.contains(host.toLower())) { - if (pathElements.count() == 1 && !absolutePath.endsWith(QLatin1Char('/'))) + auto rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); + auto it = root.children.constFind(host); + if (it != root.children.cend()) { + host = it.key(); // Normalize case for lookup in visibleLocation() + } else { + if (pathElements.count() == 1 && !absolutePath.endsWith(u'/')) return rootNode; QFileInfo info(host); if (!info.exists()) @@ -406,31 +376,31 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS r = translateVisibleLocation(rootNode, r); index = q->index(r, 0, QModelIndex()); pathElements.pop_front(); - separator = QLatin1Char('\\'); + separator = u'\\'; elementPath = host; elementPath.append(separator); } else { - if (!pathElements.at(0).contains(QLatin1Char(':'))) { + if (!pathElements.at(0).contains(u':')) { QString rootPath = QDir(longPath).rootPath(); pathElements.prepend(rootPath); } - if (pathElements.at(0).endsWith(QLatin1Char('/'))) + if (pathElements.at(0).endsWith(u'/')) pathElements[0].chop(1); } #else // add the "/" item, since it is a valid path element on Unix - if (absolutePath[0] == QLatin1Char('/')) - pathElements.prepend(QLatin1String("/")); + if (absolutePath[0] == u'/') + pathElements.prepend("/"_L1); #endif QFileSystemModelPrivate::QFileSystemNode *parent = node(index); - for (int i = 0; i < pathElements.count(); ++i) { + for (int i = 0; i < pathElements.size(); ++i) { QString element = pathElements.at(i); if (i != 0) elementPath.append(separator); elementPath.append(element); - if (i == pathElements.count() - 1) + if (i == pathElements.size() - 1) elementPath.append(trailingSeparator); #ifdef Q_OS_WIN // On Windows, "filename " and "filename" are equivalent and @@ -440,7 +410,7 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS // If after stripping the characters there is nothing left then we // just return the parent directory as it is assumed that the path // is referring to the parent - while (element.endsWith(QLatin1Char('.')) || element.endsWith(QLatin1Char(' '))) + while (element.endsWith(u'.') || element.endsWith(u' ')) element.chop(1); // Only filenames that can't possibly exist will be end up being empty if (element.isEmpty()) @@ -451,7 +421,7 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS // we couldn't find the path element, we create a new node since we // _know_ that the path is valid if (alreadyExisted) { - if ((parent->children.count() == 0) + if ((parent->children.size() == 0) || (parent->caseSensitive() && parent->children.value(element)->fileName != element) || (!parent->caseSensitive() @@ -469,7 +439,7 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this); node = p->addNode(parent, element,info); #if QT_CONFIG(filesystemwatcher) - node->populate(fileInfoGatherer.getInfo(info)); + node->populate(fileInfoGatherer->getInfo(info)); #endif } else { node = parent->children.value(element); @@ -507,10 +477,10 @@ void QFileSystemModel::timerEvent(QTimerEvent *event) if (event->timerId() == d->fetchingTimer.timerId()) { d->fetchingTimer.stop(); #if QT_CONFIG(filesystemwatcher) - for (int i = 0; i < d->toFetch.count(); ++i) { + for (int i = 0; i < d->toFetch.size(); ++i) { const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node; if (!node->hasInformation()) { - d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir, + d->fileInfoGatherer->fetchExtendedInformation(d->toFetch.at(i).dir, QStringList(d->toFetch.at(i).file)); } else { // qDebug("yah!, you saved a little gerbil soul"); @@ -560,14 +530,38 @@ QString QFileSystemModel::type(const QModelIndex &index) const } /*! - Returns the date and time when \a index was last modified. + Returns the date and time (in local time) when \a index was last modified. + + This is an overloaded function, equivalent to calling: + \code + lastModified(index, QTimeZone::LocalTime); + \endcode + + If \a index is invalid, a default constructed QDateTime is returned. */ QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const { + return lastModified(index, QTimeZone::LocalTime); +} + +/*! + \since 6.6 + Returns the date and time, in the time zone \a tz, when + \a index was last modified. + + Typical arguments for \a tz are \c QTimeZone::UTC or \c QTimeZone::LocalTime. + UTC does not require any conversion from the time returned by the native file + system API, therefore getting the time in UTC is potentially faster. LocalTime + is typically chosen if the time is shown to the user. + + If \a index is invalid, a default constructed QDateTime is returned. + */ +QDateTime QFileSystemModel::lastModified(const QModelIndex &index, const QTimeZone &tz) const +{ Q_D(const QFileSystemModel); if (!index.isValid()) return QDateTime(); - return d->node(index)->lastModified(); + return d->node(index)->lastModified(tz); } /*! @@ -657,7 +651,7 @@ void QFileSystemModel::fetchMore(const QModelIndex &parent) return; indexNode->populatedChildren = true; #if QT_CONFIG(filesystemwatcher) - d->fileInfoGatherer.list(filePath(parent)); + d->fileInfoGatherer->list(filePath(parent)); #endif } @@ -671,10 +665,10 @@ int QFileSystemModel::rowCount(const QModelIndex &parent) const return 0; if (!parent.isValid()) - return d->root.visibleChildren.count(); + return d->root.visibleChildren.size(); const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent); - return parentNode->visibleChildren.count(); + return parentNode->visibleChildren.size(); } /*! @@ -700,7 +694,9 @@ QVariant QFileSystemModel::myComputer(int role) const return QFileSystemModelPrivate::myComputer(); #if QT_CONFIG(filesystemwatcher) case Qt::DecorationRole: - return d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Computer); + if (auto *provider = d->fileInfoGatherer->iconProvider()) + return provider->icon(QAbstractFileIconProvider::Computer); + break; #endif } return QVariant(); @@ -717,12 +713,15 @@ QVariant QFileSystemModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::EditRole: + if (index.column() == QFileSystemModelPrivate::NameColumn) + return d->name(index); + Q_FALLTHROUGH(); case Qt::DisplayRole: switch (index.column()) { - case 0: return d->displayName(index); - case 1: return d->size(index); - case 2: return d->type(index); - case 3: return d->time(index); + case QFileSystemModelPrivate::NameColumn: return d->displayName(index); + case QFileSystemModelPrivate::SizeColumn: return d->size(index); + case QFileSystemModelPrivate::TypeColumn: return d->type(index); + case QFileSystemModelPrivate::TimeColumn: return d->time(index); default: qWarning("data: invalid display value column %d", index.column()); break; @@ -732,22 +731,23 @@ QVariant QFileSystemModel::data(const QModelIndex &index, int role) const return filePath(index); case FileNameRole: return d->name(index); + case FileInfoRole: + return QVariant::fromValue(fileInfo(index)); case Qt::DecorationRole: - if (index.column() == 0) { + if (index.column() == QFileSystemModelPrivate::NameColumn) { QIcon icon = d->icon(index); #if QT_CONFIG(filesystemwatcher) if (icon.isNull()) { - if (d->node(index)->isDir()) - icon = d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Folder); - else - icon = d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::File); + using P = QAbstractFileIconProvider; + if (auto *provider = d->fileInfoGatherer->iconProvider()) + icon = provider->icon(d->node(index)->isDir() ? P::Folder: P::File); } #endif // filesystemwatcher return icon; } break; case Qt::TextAlignmentRole: - if (index.column() == 1) + if (index.column() == QFileSystemModelPrivate::SizeColumn) return QVariant(Qt::AlignTrailing | Qt::AlignVCenter); break; case FilePermissions: @@ -768,9 +768,9 @@ QString QFileSystemModelPrivate::size(const QModelIndex &index) const const QFileSystemNode *n = node(index); if (n->isDir()) { #ifdef Q_OS_MAC - return QLatin1String("--"); + return "--"_L1; #else - return QLatin1String(""); + return ""_L1; #endif // Windows - "" // OS X - "--" @@ -793,7 +793,7 @@ QString QFileSystemModelPrivate::time(const QModelIndex &index) const if (!index.isValid()) return QString(); #if QT_CONFIG(datestring) - return QLocale::system().toString(node(index)->lastModified(), QLocale::ShortFormat); + return QLocale::system().toString(node(index)->lastModified(QTimeZone::LocalTime), QLocale::ShortFormat); #else Q_UNUSED(index); return QString(); @@ -820,7 +820,7 @@ QString QFileSystemModelPrivate::name(const QModelIndex &index) const QFileSystemNode *dirNode = node(index); if ( #if QT_CONFIG(filesystemwatcher) - fileInfoGatherer.resolveSymlinks() && + fileInfoGatherer->resolveSymlinks() && #endif !resolvedSymLinks.isEmpty() && dirNode->isSymLink(/* ignoreNtfsSymLinks = */ true)) { QString fullPath = QDir::fromNativeSeparators(filePath(index)); @@ -890,7 +890,7 @@ bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, in *After re-naming something we don't want the selection to change* - can't remove rows and later insert - can't quickly remove and insert - - index pointer can't change because treeview doesn't use persistant index's + - index pointer can't change because treeview doesn't use persistent index's - if this get any more complicated think of changing it to just use layoutChanged @@ -901,14 +901,14 @@ bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, in int visibleLocation = parentNode->visibleLocation(parentNode->children.value(indexNode->fileName)->fileName); parentNode->visibleChildren.removeAt(visibleLocation); - QScopedPointer<QFileSystemModelPrivate::QFileSystemNode> nodeToRename(parentNode->children.take(oldName)); + std::unique_ptr<QFileSystemModelPrivate::QFileSystemNode> nodeToRename(parentNode->children.take(oldName)); nodeToRename->fileName = newName; nodeToRename->parent = parentNode; #if QT_CONFIG(filesystemwatcher) - nodeToRename->populate(d->fileInfoGatherer.getInfo(QFileInfo(parentPath, newName))); + nodeToRename->populate(d->fileInfoGatherer->getInfo(QFileInfo(parentPath, newName))); #endif nodeToRename->isVisible = true; - parentNode->children[newName] = nodeToRename.take(); + parentNode->children[newName] = nodeToRename.release(); parentNode->visibleChildren.insert(visibleLocation, newName); d->delayedSort(); @@ -941,23 +941,27 @@ QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, QString returnValue; switch (section) { - case 0: returnValue = tr("Name"); - break; - case 1: returnValue = tr("Size"); - break; - case 2: returnValue = + case QFileSystemModelPrivate::NameColumn: + returnValue = tr("Name"); + break; + case QFileSystemModelPrivate::SizeColumn: + returnValue = tr("Size"); + break; + case QFileSystemModelPrivate::TypeColumn: + returnValue = #ifdef Q_OS_MAC - tr("Kind", "Match OS X Finder"); + tr("Kind", "Match OS X Finder"); #else - tr("Type", "All other platforms"); + tr("Type", "All other platforms"); #endif - break; + break; // Windows - Type // OS X - Kind // Konqueror - File Type // Nautilus - Type - case 3: returnValue = tr("Date Modified"); - break; + case QFileSystemModelPrivate::TimeColumn: + returnValue = tr("Date Modified"); + break; default: return QVariant(); } return returnValue; @@ -981,14 +985,15 @@ Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const } flags |= Qt::ItemIsDragEnabled; + + if (!indexNode->isDir()) + flags |= Qt::ItemNeverHasChildren; if (d->readOnly) return flags; if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) { flags |= Qt::ItemIsEditable; if (indexNode->isDir()) flags |= Qt::ItemIsDropEnabled; - else - flags |= Qt::ItemNeverHasChildren; } return flags; } @@ -996,7 +1001,7 @@ Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const /*! \internal */ -void QFileSystemModelPrivate::_q_performDelayedSort() +void QFileSystemModelPrivate::performDelayedSort() { Q_Q(QFileSystemModel); q->sort(sortColumn, sortOrder); @@ -1020,7 +1025,7 @@ public: const QFileSystemModelPrivate::QFileSystemNode *r) const { switch (sortColumn) { - case 0: { + case QFileSystemModelPrivate::NameColumn: { #ifndef Q_OS_MAC // place directories before files bool left = l->isDir(); @@ -1030,7 +1035,7 @@ public: #endif return naturalCompare.compare(l->fileName, r->fileName) < 0; } - case 1: + case QFileSystemModelPrivate::SizeColumn: { // Directories go first bool left = l->isDir(); @@ -1044,7 +1049,7 @@ public: return sizeDifference < 0; } - case 2: + case QFileSystemModelPrivate::TypeColumn: { int compare = naturalCompare.compare(l->type(), r->type()); if (compare == 0) @@ -1052,12 +1057,14 @@ public: return compare < 0; } - case 3: + case QFileSystemModelPrivate::TimeColumn: { - if (l->lastModified() == r->lastModified()) + const QDateTime left = l->lastModified(QTimeZone::UTC); + const QDateTime right = r->lastModified(QTimeZone::UTC); + if (left == right) return naturalCompare.compare(l->fileName, r->fileName) < 0; - return l->lastModified() < r->lastModified(); + return left < right; } } Q_ASSERT(false); @@ -1085,7 +1092,7 @@ void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent { Q_Q(QFileSystemModel); QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent); - if (indexNode->children.count() == 0) + if (indexNode->children.size() == 0) return; QList<QFileSystemModelPrivate::QFileSystemNode *> values; @@ -1103,11 +1110,10 @@ void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent indexNode->visibleChildren.clear(); //No more dirty item we reset our internal dirty index indexNode->dirtyChildrenIndex = -1; - const int numValues = values.count(); - indexNode->visibleChildren.reserve(numValues); - for (int i = 0; i < numValues; ++i) { - indexNode->visibleChildren.append(values.at(i)->fileName); - values.at(i)->isVisible = true; + indexNode->visibleChildren.reserve(values.size()); + for (QFileSystemNode *node : std::as_const(values)) { + indexNode->visibleChildren.append(node->fileName); + node->isVisible = true; } if (!disableRecursiveSort) { @@ -1133,10 +1139,8 @@ void QFileSystemModel::sort(int column, Qt::SortOrder order) emit layoutAboutToBeChanged(); QModelIndexList oldList = persistentIndexList(); QList<QPair<QFileSystemModelPrivate::QFileSystemNode *, int>> oldNodes; - const int nodeCount = oldList.count(); - oldNodes.reserve(nodeCount); - for (int i = 0; i < nodeCount; ++i) { - const QModelIndex &oldNode = oldList.at(i); + oldNodes.reserve(oldList.size()); + for (const QModelIndex &oldNode : oldList) { QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(oldNode), oldNode.column()); oldNodes.append(pair); } @@ -1150,12 +1154,10 @@ void QFileSystemModel::sort(int column, Qt::SortOrder order) d->sortOrder = order; QModelIndexList newList; - const int numOldNodes = oldNodes.size(); - newList.reserve(numOldNodes); - for (int i = 0; i < numOldNodes; ++i) { - const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &oldNode = oldNodes.at(i); - newList.append(d->index(oldNode.first, oldNode.second)); - } + newList.reserve(oldNodes.size()); + for (const auto &[node, col]: std::as_const(oldNodes)) + newList.append(d->index(node, col)); + changePersistentIndexList(oldList, newList); emit layoutChanged(); } @@ -1166,7 +1168,7 @@ void QFileSystemModel::sort(int column, Qt::SortOrder order) */ QStringList QFileSystemModel::mimeTypes() const { - return QStringList(QLatin1String("text/uri-list")); + return QStringList("text/uri-list"_L1); } /*! @@ -1182,7 +1184,7 @@ QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const QList<QUrl> urls; QList<QModelIndex>::const_iterator it = indexes.begin(); for (; it != indexes.end(); ++it) - if ((*it).column() == 0) + if ((*it).column() == QFileSystemModelPrivate::NameColumn) urls << QUrl::fromLocalFile(filePath(*it)); QMimeData *data = new QMimeData(); data->setUrls(urls); @@ -1256,6 +1258,7 @@ QHash<int, QByteArray> QFileSystemModel::roleNames() const ret.insert(QFileSystemModel::FilePathRole, QByteArrayLiteral("filePath")); ret.insert(QFileSystemModel::FileNameRole, QByteArrayLiteral("fileName")); ret.insert(QFileSystemModel::FilePermissions, QByteArrayLiteral("filePermissions")); + ret.insert(QFileSystemModel::FileInfoRole, QByteArrayLiteral("fileInfo")); return ret; } @@ -1328,7 +1331,7 @@ void QFileSystemModel::setOptions(Options options) #if QT_CONFIG(filesystemwatcher) Q_D(QFileSystemModel); if (changed.testFlag(DontWatchForChanges)) - d->fileInfoGatherer.setWatching(!options.testFlag(DontWatchForChanges)); + d->fileInfoGatherer->setWatching(!options.testFlag(DontWatchForChanges)); #endif if (changed.testFlag(DontUseCustomDirectoryIcons)) { @@ -1349,7 +1352,7 @@ QFileSystemModel::Options QFileSystemModel::options() const result.setFlag(DontResolveSymlinks, !resolveSymlinks()); #if QT_CONFIG(filesystemwatcher) Q_D(const QFileSystemModel); - result.setFlag(DontWatchForChanges, !d->fileInfoGatherer.isWatching()); + result.setFlag(DontWatchForChanges, !d->fileInfoGatherer->isWatching()); #else result.setFlag(DontWatchForChanges); #endif @@ -1371,7 +1374,7 @@ QString QFileSystemModel::filePath(const QModelIndex &index) const QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index); if (dirNode->isSymLink() #if QT_CONFIG(filesystemwatcher) - && d->fileInfoGatherer.resolveSymlinks() + && d->fileInfoGatherer->resolveSymlinks() #endif && d->resolvedSymLinks.contains(fullPath) && dirNode->isDir()) { @@ -1407,11 +1410,11 @@ QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const } QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator())); #if !defined(Q_OS_WIN) - if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/')) + if ((fullPath.size() > 2) && fullPath[0] == u'/' && fullPath[1] == u'/') fullPath = fullPath.mid(1); #else - if (fullPath.length() == 2 && fullPath.endsWith(QLatin1Char(':'))) - fullPath.append(QLatin1Char('/')); + if (fullPath.length() == 2 && fullPath.endsWith(u':')) + fullPath.append(u'/'); #endif return fullPath; } @@ -1433,7 +1436,7 @@ QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &na Q_ASSERT(parentNode->children.contains(name)); QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name]; #if QT_CONFIG(filesystemwatcher) - node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name))); + node->populate(d->fileInfoGatherer->getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name))); #endif d->addVisibleFiles(parentNode, QStringList(name)); return d->index(node); @@ -1498,10 +1501,10 @@ QModelIndex QFileSystemModel::setRootPath(const QString &newPath) return d->index(rootPath()); //We remove the watcher on the previous path - if (!rootPath().isEmpty() && rootPath() != QLatin1String(".")) { + if (!rootPath().isEmpty() && rootPath() != "."_L1) { //This remove the watcher for the old rootPath #if QT_CONFIG(filesystemwatcher) - d->fileInfoGatherer.removePath(rootPath()); + d->fileInfoGatherer->removePath(rootPath()); #endif //This line "marks" the node as dirty, so the next fetchMore //call on the path will ask the gatherer to install a watcher again @@ -1514,7 +1517,7 @@ QModelIndex QFileSystemModel::setRootPath(const QString &newPath) QModelIndex newRootIndex; if (showDrives) { // otherwise dir will become '.' - d->rootDir.setPath(QLatin1String("")); + d->rootDir.setPath(""_L1); } else { newRootIndex = d->index(d->rootDir.path()); } @@ -1557,7 +1560,7 @@ void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider) { Q_D(QFileSystemModel); #if QT_CONFIG(filesystemwatcher) - d->fileInfoGatherer.setIconProvider(provider); + d->fileInfoGatherer->setIconProvider(provider); #endif d->root.updateIcon(provider, QString()); } @@ -1569,9 +1572,9 @@ QAbstractFileIconProvider *QFileSystemModel::iconProvider() const { #if QT_CONFIG(filesystemwatcher) Q_D(const QFileSystemModel); - return d->fileInfoGatherer.iconProvider(); + return d->fileInfoGatherer->iconProvider(); #else - return 0; + return nullptr; #endif } @@ -1588,9 +1591,11 @@ void QFileSystemModel::setFilter(QDir::Filters filters) Q_D(QFileSystemModel); if (d->filters == filters) return; + const bool changingCaseSensitivity = + filters.testFlag(QDir::CaseSensitive) != d->filters.testFlag(QDir::CaseSensitive); d->filters = filters; - // CaseSensitivity might have changed - setNameFilters(nameFilters()); + if (changingCaseSensitivity) + d->rebuildNameFilterRegexps(); d->forceSort = true; d->delayedSort(); } @@ -1623,7 +1628,7 @@ void QFileSystemModel::setResolveSymlinks(bool enable) { #if QT_CONFIG(filesystemwatcher) Q_D(QFileSystemModel); - d->fileInfoGatherer.setResolveSymlinks(enable); + d->fileInfoGatherer->setResolveSymlinks(enable); #else Q_UNUSED(enable); #endif @@ -1633,7 +1638,7 @@ bool QFileSystemModel::resolveSymlinks() const { #if QT_CONFIG(filesystemwatcher) Q_D(const QFileSystemModel); - return d->fileInfoGatherer.resolveSymlinks(); + return d->fileInfoGatherer->resolveSymlinks(); #else return false; #endif @@ -1687,7 +1692,6 @@ bool QFileSystemModel::nameFilterDisables() const */ void QFileSystemModel::setNameFilters(const QStringList &filters) { - // Prep the regexp's ahead of time #if QT_CONFIG(regularexpression) Q_D(QFileSystemModel); @@ -1710,6 +1714,7 @@ void QFileSystemModel::setNameFilters(const QStringList &filters) } d->nameFilters = filters; + d->rebuildNameFilterRegexps(); d->forceSort = true; d->delayedSort(); #else @@ -1738,7 +1743,7 @@ bool QFileSystemModel::event(QEvent *event) #if QT_CONFIG(filesystemwatcher) Q_D(QFileSystemModel); if (event->type() == QEvent::LanguageChange) { - d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString()); + d->root.retranslateStrings(d->fileInfoGatherer->iconProvider(), QString()); return true; } #endif @@ -1752,7 +1757,7 @@ bool QFileSystemModel::rmdir(const QModelIndex &aindex) #if QT_CONFIG(filesystemwatcher) if (success) { QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func()); - d->fileInfoGatherer.removePath(path); + d->fileInfoGatherer->removePath(path); } #endif return success; @@ -1764,10 +1769,10 @@ bool QFileSystemModel::rmdir(const QModelIndex &aindex) Performed quick listing and see if any files have been added or removed, then fetch more information on visible files. */ -void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files) +void QFileSystemModelPrivate::directoryChanged(const QString &directory, const QStringList &files) { QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false); - if (parentNode->children.count() == 0) + if (parentNode->children.size() == 0) return; QStringList toRemove; QStringList newFiles = files; @@ -1777,7 +1782,7 @@ void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, cons if ((iterator == newFiles.end()) || (i.value()->fileName < *iterator)) toRemove.append(i.value()->fileName); } - for (int i = 0 ; i < toRemove.count() ; ++i ) + for (int i = 0 ; i < toRemove.size() ; ++i ) removeNode(parentNode, toRemove[i]); } @@ -1869,11 +1874,11 @@ void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QModelIndex parent = index(parentNode); bool indexHidden = isHiddenByFilter(parentNode, parent); if (!indexHidden) { - q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1); + q->beginInsertRows(parent, parentNode->visibleChildren.size() , parentNode->visibleChildren.size() + newFiles.size() - 1); } if (parentNode->dirtyChildrenIndex == -1) - parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count(); + parentNode->dirtyChildrenIndex = parentNode->visibleChildren.size(); for (const auto &newFile : newFiles) { parentNode->visibleChildren.append(newFile); @@ -1912,8 +1917,8 @@ void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int The thread has received new information about files, update and emit dataChanged if it has actually changed. */ -void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, - const QList<QPair<QString, QFileInfo>> &updates) +void QFileSystemModelPrivate::fileSystemChanged(const QString &path, + const QList<std::pair<QString, QFileInfo>> &updates) { #if QT_CONFIG(filesystemwatcher) Q_Q(QFileSystemModel); @@ -1924,7 +1929,7 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, for (const auto &update : updates) { QString fileName = update.first; Q_ASSERT(!fileName.isEmpty()); - QExtendedInformation info = fileInfoGatherer.getInfo(update.second); + QExtendedInformation info = fileInfoGatherer->getInfo(update.second); bool previouslyHere = parentNode->children.contains(fileName); if (!previouslyHere) { addNode(parentNode, fileName, info.fileInfo()); @@ -1969,7 +1974,7 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, std::sort(rowsToUpdate.begin(), rowsToUpdate.end()); QString min; QString max; - for (const QString &value : qAsConst(rowsToUpdate)) { + for (const QString &value : std::as_const(rowsToUpdate)) { //##TODO is there a way to bundle signals with QString as the content of the list? /*if (min.isEmpty()) { min = value; @@ -1987,23 +1992,30 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, int visibleMin = parentNode->visibleLocation(min); int visibleMax = parentNode->visibleLocation(max); if (visibleMin >= 0 - && visibleMin < parentNode->visibleChildren.count() + && visibleMin < parentNode->visibleChildren.size() && parentNode->visibleChildren.at(visibleMin) == min && visibleMax >= 0) { - QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex); - QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex); - emit q->dataChanged(bottom, top); + // don't use NumColumns here, a subclass might override columnCount + const int lastColumn = q->columnCount(parentIndex) - 1; + const QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMin), + QFileSystemModelPrivate::NameColumn, parentIndex); + const QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMax), + lastColumn, parentIndex); + // We document that emitting dataChanged with indexes that don't have the + // same parent is undefined behavior. + Q_ASSERT(bottom.parent() == top.parent()); + emit q->dataChanged(top, bottom); } /*min = QString(); max = QString();*/ } - if (newFiles.count() > 0) { + if (newFiles.size() > 0) { addVisibleFiles(parentNode, newFiles); } - if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) { + if (newFiles.size() > 0 || (sortColumn != 0 && rowsToUpdate.size() > 0)) { forceSort = true; delayedSort(); } @@ -2016,7 +2028,7 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, /*! \internal */ -void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName) +void QFileSystemModelPrivate::resolvedName(const QString &fileName, const QString &resolvedName) { resolvedSymLinks[fileName] = resolvedName; } @@ -2042,25 +2054,48 @@ QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index) if (pathSize == watchedPath.size()) { return path.compare(watchedPath, caseSensitivity) == 0; } else if (watchedPath.size() > pathSize) { - return watchedPath.at(pathSize) == QLatin1Char('/') + return watchedPath.at(pathSize) == u'/' && watchedPath.startsWith(path, caseSensitivity); } return false; }; - const QStringList &watchedFiles = fileInfoGatherer.watchedFiles(); + const QStringList &watchedFiles = fileInfoGatherer->watchedFiles(); std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(), std::back_inserter(result), filter); - const QStringList &watchedDirectories = fileInfoGatherer.watchedDirectories(); + const QStringList &watchedDirectories = fileInfoGatherer->watchedDirectories(); std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(), std::back_inserter(result), filter); - fileInfoGatherer.unwatchPaths(result); + fileInfoGatherer->unwatchPaths(result); return result; } #endif // filesystemwatcher && Q_OS_WIN +QFileSystemModelPrivate::QFileSystemModelPrivate() +#if QT_CONFIG(filesystemwatcher) + : fileInfoGatherer(new QFileInfoGatherer) +#endif // filesystemwatcher +{ +} + +QFileSystemModelPrivate::~QFileSystemModelPrivate() +{ +#if QT_CONFIG(filesystemwatcher) + fileInfoGatherer->requestAbort(); + if (!fileInfoGatherer->wait(1000)) { + // If the thread hangs, perhaps because the network was disconnected + // while the gatherer was stat'ing a remote file, then don't block + // shutting down the model (which might block a file dialog and the + // main thread). Schedule the gatherer for later deletion; it's + // destructor will wait for the thread to finish. + auto *rawGatherer = fileInfoGatherer.release(); + rawGatherer->deleteLater(); + } +#endif // filesystemwatcher +} + /*! \internal */ @@ -2070,18 +2105,20 @@ void QFileSystemModelPrivate::init() delayedSortTimer.setSingleShot(true); - qRegisterMetaType<QList<QPair<QString, QFileInfo>>>(); + qRegisterMetaType<QList<std::pair<QString, QFileInfo>>>(); #if QT_CONFIG(filesystemwatcher) - q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)), - q, SLOT(_q_directoryChanged(QString,QStringList))); - q->connect(&fileInfoGatherer, SIGNAL(updates(QString, QList<QPair<QString, QFileInfo>>)), q, - SLOT(_q_fileSystemChanged(QString, QList<QPair<QString, QFileInfo>>))); - q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)), - q, SLOT(_q_resolvedName(QString,QString))); - q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)), - q, SIGNAL(directoryLoaded(QString))); + QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::newListOfFiles, + this, &QFileSystemModelPrivate::directoryChanged); + QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::updates, + this, &QFileSystemModelPrivate::fileSystemChanged); + QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::nameResolved, + this, &QFileSystemModelPrivate::resolvedName); + q->connect(fileInfoGatherer.get(), &QFileInfoGatherer::directoryLoaded, + q, &QFileSystemModel::directoryLoaded); #endif // filesystemwatcher - q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection); + QObjectPrivate::connect(&delayedSortTimer, &QTimer::timeout, + this, &QFileSystemModelPrivate::performDelayedSort, + Qt::QueuedConnection); } /*! @@ -2094,8 +2131,14 @@ void QFileSystemModelPrivate::init() */ bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const { + // When the model is set to only show files, then a node representing a dir + // should be hidden regardless of bypassFilters. + // QTBUG-74471 + const bool hideDirs = (filters & (QDir::Dirs | QDir::AllDirs)) == 0; + const bool shouldHideDirNode = hideDirs && node->isDir(); + // always accept drives - if (node->parent == &root || bypassFilters.contains(node)) + if (node->parent == &root || (!shouldHideDirNode && bypassFilters.contains(node))) return true; // If we don't know anything yet don't accept it @@ -2104,7 +2147,6 @@ bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) co const bool filterPermissions = ((filters & QDir::PermissionMask) && (filters & QDir::PermissionMask) != QDir::PermissionMask); - const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); const bool hideFiles = !(filters & QDir::Files); const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable)); const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable)); @@ -2116,8 +2158,8 @@ bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) co const bool hideDotDot = (filters & QDir::NoDotDot); // Note that we match the behavior of entryList and not QFileInfo on this. - bool isDot = (node->fileName == QLatin1String(".")); - bool isDotDot = (node->fileName == QLatin1String("..")); + bool isDot = (node->fileName == "."_L1); + bool isDotDot = (node->fileName == ".."_L1); if ( (hideHidden && !(isDot || isDotDot) && node->isHidden()) || (hideSystem && node->isSystem()) || (hideDirs && node->isDir()) @@ -2146,15 +2188,13 @@ bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const // Check the name regularexpression filters if (!(node->isDir() && (filters & QDir::AllDirs))) { - auto cs = (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; - - for (const auto &nameFilter : nameFilters) { - auto rx = QRegularExpression::fromWildcard(nameFilter, cs); - QRegularExpressionMatch match = rx.match(node->fileName); - if (match.hasMatch()) - return true; - } - return false; + const auto matchesNodeFileName = [node](const QRegularExpression &re) + { + return node->fileName.contains(re); + }; + return std::any_of(nameFiltersRegexps.begin(), + nameFiltersRegexps.end(), + matchesNodeFileName); } #else Q_UNUSED(node); @@ -2162,6 +2202,23 @@ bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const return true; } +#if QT_CONFIG(regularexpression) +void QFileSystemModelPrivate::rebuildNameFilterRegexps() +{ + nameFiltersRegexps.clear(); + nameFiltersRegexps.reserve(nameFilters.size()); + const auto cs = (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; + const auto convertWildcardToRegexp = [cs](const QString &nameFilter) + { + return QRegularExpression::fromWildcard(nameFilter, cs); + }; + std::transform(nameFilters.constBegin(), + nameFilters.constEnd(), + std::back_inserter(nameFiltersRegexps), + convertWildcardToRegexp); +} +#endif + QT_END_NAMESPACE #include "moc_qfilesystemmodel.cpp" |