diff options
Diffstat (limited to 'src/gui/itemmodels')
-rw-r--r-- | src/gui/itemmodels/qfileinfogatherer.cpp | 126 | ||||
-rw-r--r-- | src/gui/itemmodels/qfileinfogatherer_p.h | 13 | ||||
-rw-r--r-- | src/gui/itemmodels/qfilesystemmodel.cpp | 205 | ||||
-rw-r--r-- | src/gui/itemmodels/qfilesystemmodel.h | 9 | ||||
-rw-r--r-- | src/gui/itemmodels/qfilesystemmodel_p.h | 27 | ||||
-rw-r--r-- | src/gui/itemmodels/qstandarditemmodel.cpp | 75 | ||||
-rw-r--r-- | src/gui/itemmodels/qstandarditemmodel_p.h | 8 |
7 files changed, 289 insertions, 174 deletions
diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp index 0e0a3b11a5..41fb0a0db5 100644 --- a/src/gui/itemmodels/qfileinfogatherer.cpp +++ b/src/gui/itemmodels/qfileinfogatherer.cpp @@ -2,8 +2,10 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qfileinfogatherer_p.h" +#include <qcoreapplication.h> #include <qdebug.h> -#include <qdiriterator.h> +#include <qdirlisting.h> +#include <private/qabstractfileiconprovider_p.h> #include <private/qfileinfo_p.h> #ifndef Q_OS_WIN # include <unistd.h> @@ -57,11 +59,40 @@ QFileInfoGatherer::QFileInfoGatherer(QObject *parent) */ QFileInfoGatherer::~QFileInfoGatherer() { - abort.storeRelaxed(true); + requestAbort(); + wait(); +} + +bool QFileInfoGatherer::event(QEvent *event) +{ + if (event->type() == QEvent::DeferredDelete && isRunning()) { + // We have been asked to shut down later but were blocked, + // so the owning QFileSystemModel proceeded with its shut-down + // and deferred the destruction of the gatherer. + // If we are still blocked now, then we have three bad options: + // terminate, wait forever (preventing the process from shutting down), + // or accept a memory leak. + requestAbort(); + if (!wait(5000)) { + // If the application is shutting down, then we terminate. + // Otherwise assume that sooner or later the thread will finish, + // and we delete it then. + if (QCoreApplication::closingDown()) + terminate(); + else + connect(this, &QThread::finished, this, [this]{ delete this; }); + return true; + } + } + + return QThread::event(event); +} + +void QFileInfoGatherer::requestAbort() +{ + requestInterruption(); QMutexLocker locker(&mutex); condition.wakeAll(); - locker.unlock(); - wait(); } void QFileInfoGatherer::setResolveSymlinks(bool enable) @@ -114,13 +145,12 @@ void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStr { QMutexLocker locker(&mutex); // See if we already have this dir/file in our queue - int loc = this->path.lastIndexOf(path); - while (loc > 0) { - if (this->files.at(loc) == files) { + qsizetype loc = 0; + while ((loc = this->path.lastIndexOf(path, loc - 1)) != -1) { + if (this->files.at(loc) == files) return; - } - loc = this->path.lastIndexOf(path, loc - 1); } + #if QT_CONFIG(thread) this->path.push(path); this->files.push(files); @@ -220,16 +250,23 @@ bool QFileInfoGatherer::isWatching() const return result; } +/*! \internal + + If \a v is \c false, the QFileSystemWatcher used internally will be deleted + and subsequent calls to watchPaths() will do nothing. + + If \a v is \c true, subsequent calls to watchPaths() will add those paths to + the filesystem watcher; watchPaths() will initialize a QFileSystemWatcher if + one hasn't already been initialized. +*/ void QFileInfoGatherer::setWatching(bool v) { #if QT_CONFIG(filesystemwatcher) QMutexLocker locker(&mutex); if (v != m_watching) { - if (!v) { - delete m_watcher; - m_watcher = nullptr; - } m_watching = v; + if (!m_watching) + delete std::exchange(m_watcher, nullptr); } #else Q_UNUSED(v); @@ -281,10 +318,13 @@ void QFileInfoGatherer::list(const QString &directoryPath) void QFileInfoGatherer::run() { forever { + // Disallow termination while we are holding a mutex or can be + // woken up cleanly. + setTerminationEnabled(false); QMutexLocker locker(&mutex); - while (!abort.loadRelaxed() && path.isEmpty()) + while (!isInterruptionRequested() && path.isEmpty()) condition.wait(&mutex); - if (abort.loadRelaxed()) + if (isInterruptionRequested()) return; const QString thisPath = std::as_const(path).front(); path.pop_front(); @@ -292,6 +332,10 @@ void QFileInfoGatherer::run() files.pop_front(); locker.unlock(); + // Some of the system APIs we call when gathering file infomration + // might hang (e.g. waiting for network), so we explicitly allow + // termination now. + setTerminationEnabled(true); getFileInfos(thisPath, thisList); } } @@ -299,8 +343,12 @@ void QFileInfoGatherer::run() QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const { QExtendedInformation info(fileInfo); - info.icon = m_iconProvider->icon(fileInfo); - info.displayType = m_iconProvider->type(fileInfo); + if (m_iconProvider) { + info.icon = m_iconProvider->icon(fileInfo); + info.displayType = m_iconProvider->type(fileInfo); + } else { + info.displayType = QAbstractFileIconProviderPrivate::getFileType(fileInfo); + } #if QT_CONFIG(filesystemwatcher) // ### Not ready to listen all modifications by default static const bool watchFiles = qEnvironmentVariableIsSet("QT_FILESYSTEMMODEL_WATCH_FILES"); @@ -340,21 +388,23 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil #ifdef QT_BUILD_INTERNAL fetchedRoot.storeRelaxed(true); #endif - QFileInfoList infoList; + QList<std::pair<QString, QFileInfo>> updatedFiles; + auto addToUpdatedFiles = [&updatedFiles](QFileInfo &&fileInfo) { + fileInfo.stat(); + updatedFiles.emplace_back(std::pair{translateDriveName(fileInfo), fileInfo}); + }; + if (files.isEmpty()) { - infoList = QDir::drives(); + // QDir::drives() calls QFSFileEngine::drives() which creates the QFileInfoList on + // the stack and return it, so this list is not shared, so no detaching. + QFileInfoList infoList = QDir::drives(); + updatedFiles.reserve(infoList.size()); + for (auto rit = infoList.rbegin(), rend = infoList.rend(); rit != rend; ++rit) + addToUpdatedFiles(std::move(*rit)); } else { - infoList.reserve(files.size()); - for (const auto &file : files) - infoList << QFileInfo(file); - } - QList<QPair<QString, QFileInfo>> updatedFiles; - updatedFiles.reserve(infoList.size()); - for (int i = infoList.size() - 1; i >= 0; --i) { - QFileInfo driveInfo = infoList.at(i); - driveInfo.stat(); - QString driveName = translateDriveName(driveInfo); - updatedFiles.append(QPair<QString,QFileInfo>(driveName, driveInfo)); + updatedFiles.reserve(files.size()); + for (auto rit = files.crbegin(), rend = files.crend(); rit != rend; ++rit) + addToUpdatedFiles(QFileInfo(*rit)); } emit updates(path, updatedFiles); return; @@ -364,14 +414,16 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil base.start(); QFileInfo fileInfo; bool firstTime = true; - QList<QPair<QString, QFileInfo>> updatedFiles; + QList<std::pair<QString, QFileInfo>> updatedFiles; QStringList filesToCheck = files; QStringList allFiles; if (files.isEmpty()) { - QDirIterator dirIt(path, QDir::AllEntries | QDir::System | QDir::Hidden); - while (!abort.loadRelaxed() && dirIt.hasNext()) { - fileInfo = dirIt.nextFileInfo(); + constexpr auto dirFilters = QDir::AllEntries | QDir::System | QDir::Hidden; + for (const auto &dirEntry : QDirListing(path, dirFilters)) { + if (isInterruptionRequested()) + break; + fileInfo = dirEntry.fileInfo(); fileInfo.stat(); allFiles.append(fileInfo.fileName()); fetch(fileInfo, base, firstTime, updatedFiles, path); @@ -381,7 +433,7 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil emit newListOfFiles(path, allFiles); QStringList::const_iterator filesIt = filesToCheck.constBegin(); - while (!abort.loadRelaxed() && filesIt != filesToCheck.constEnd()) { + while (!isInterruptionRequested() && filesIt != filesToCheck.constEnd()) { fileInfo.setFile(path + QDir::separator() + *filesIt); ++filesIt; fileInfo.stat(); @@ -393,9 +445,9 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil } void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime, - QList<QPair<QString, QFileInfo>> &updatedFiles, const QString &path) + QList<std::pair<QString, QFileInfo>> &updatedFiles, const QString &path) { - updatedFiles.append(QPair<QString, QFileInfo>(fileInfo.fileName(), fileInfo)); + updatedFiles.emplace_back(std::pair(fileInfo.fileName(), fileInfo)); QElapsedTimer current; current.start(); if ((firstTime && updatedFiles.size() > 100) || base.msecsTo(current) > 1000) { diff --git a/src/gui/itemmodels/qfileinfogatherer_p.h b/src/gui/itemmodels/qfileinfogatherer_p.h index e4b2bc889f..3d5f59c22e 100644 --- a/src/gui/itemmodels/qfileinfogatherer_p.h +++ b/src/gui/itemmodels/qfileinfogatherer_p.h @@ -24,7 +24,6 @@ #include <qfilesystemwatcher.h> #endif #include <qabstractfileiconprovider.h> -#include <qpair.h> #include <qstack.h> #include <qdatetime.h> #include <qdir.h> @@ -32,6 +31,8 @@ #include <private/qfilesystemengine_p.h> +#include <utility> + QT_REQUIRE_CONFIG(filesystemmodel); QT_BEGIN_NAMESPACE @@ -124,7 +125,7 @@ class Q_GUI_EXPORT QFileInfoGatherer : public QThread Q_OBJECT Q_SIGNALS: - void updates(const QString &directory, const QList<QPair<QString, QFileInfo>> &updates); + void updates(const QString &directory, const QList<std::pair<QString, QFileInfo>> &updates); void newListOfFiles(const QString &directory, const QStringList &listOfFiles) const; void nameResolved(const QString &fileName, const QString &resolvedName) const; void directoryLoaded(const QString &path); @@ -148,6 +149,8 @@ public: QAbstractFileIconProvider *iconProvider() const; bool resolveSymlinks() const; + void requestAbort(); + public Q_SLOTS: void list(const QString &directoryPath); void fetchExtendedInformation(const QString &path, const QStringList &files); @@ -159,12 +162,15 @@ private Q_SLOTS: void driveAdded(); void driveRemoved(); +protected: + bool event(QEvent *event) override; + private: void run() override; // called by run(): void getFileInfos(const QString &path, const QStringList &files); void fetch(const QFileInfo &info, QElapsedTimer &base, bool &firstTime, - QList<QPair<QString, QFileInfo>> &updatedFiles, const QString &path); + QList<std::pair<QString, QFileInfo>> &updatedFiles, const QString &path); private: void createWatcher(); @@ -175,7 +181,6 @@ private: QStack<QString> path; QStack<QStringList> files; // end protected by mutex - QAtomicInt abort; #if QT_CONFIG(filesystemwatcher) QFileSystemWatcher *m_watcher = nullptr; diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp index 9a54b8d8bd..5eca8ba4a0 100644 --- a/src/gui/itemmodels/qfilesystemmodel.cpp +++ b/src/gui/itemmodels/qfilesystemmodel.cpp @@ -240,7 +240,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 { @@ -438,7 +438,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); @@ -479,7 +479,7 @@ void QFileSystemModel::timerEvent(QTimerEvent *event) 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"); @@ -650,7 +650,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 } @@ -693,7 +693,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(); @@ -710,15 +712,15 @@ QVariant QFileSystemModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::EditRole: - if (index.column() == 0) + 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; @@ -729,21 +731,20 @@ QVariant QFileSystemModel::data(const QModelIndex &index, int role) const case FileNameRole: return d->name(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: @@ -816,7 +817,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)); @@ -901,7 +902,7 @@ bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, in 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.release(); @@ -937,23 +938,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; @@ -993,7 +998,7 @@ Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const /*! \internal */ -void QFileSystemModelPrivate::_q_performDelayedSort() +void QFileSystemModelPrivate::performDelayedSort() { Q_Q(QFileSystemModel); q->sort(sortColumn, sortOrder); @@ -1017,7 +1022,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(); @@ -1027,7 +1032,7 @@ public: #endif return naturalCompare.compare(l->fileName, r->fileName) < 0; } - case 1: + case QFileSystemModelPrivate::SizeColumn: { // Directories go first bool left = l->isDir(); @@ -1041,7 +1046,7 @@ public: return sizeDifference < 0; } - case 2: + case QFileSystemModelPrivate::TypeColumn: { int compare = naturalCompare.compare(l->type(), r->type()); if (compare == 0) @@ -1049,7 +1054,7 @@ public: return compare < 0; } - case 3: + case QFileSystemModelPrivate::TimeColumn: { const QDateTime left = l->lastModified(QTimeZone::UTC); const QDateTime right = r->lastModified(QTimeZone::UTC); @@ -1102,11 +1107,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.size(); - 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) { @@ -1132,10 +1136,8 @@ void QFileSystemModel::sort(int column, Qt::SortOrder order) emit layoutAboutToBeChanged(); QModelIndexList oldList = persistentIndexList(); QList<QPair<QFileSystemModelPrivate::QFileSystemNode *, int>> oldNodes; - const int nodeCount = oldList.size(); - 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); } @@ -1149,12 +1151,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(); } @@ -1181,7 +1181,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); @@ -1327,7 +1327,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)) { @@ -1348,7 +1348,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 @@ -1370,7 +1370,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()) { @@ -1432,7 +1432,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); @@ -1500,7 +1500,7 @@ QModelIndex QFileSystemModel::setRootPath(const QString &newPath) 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 @@ -1556,7 +1556,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()); } @@ -1568,9 +1568,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 } @@ -1624,7 +1624,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 @@ -1634,7 +1634,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 @@ -1739,7 +1739,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 @@ -1753,7 +1753,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; @@ -1765,7 +1765,7 @@ 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.size() == 0) @@ -1913,8 +1913,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); @@ -1925,7 +1925,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()); @@ -1991,9 +1991,16 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, && 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(); @@ -2017,7 +2024,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; } @@ -2049,22 +2056,41 @@ QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index) 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() = default; +QFileSystemModelPrivate::QFileSystemModelPrivate() +#if QT_CONFIG(filesystemwatcher) + : fileInfoGatherer(new QFileInfoGatherer) +#endif // filesystemwatcher +{ +} -QFileSystemModelPrivate::~QFileSystemModelPrivate() = default; +QFileSystemModelPrivate::~QFileSystemModelPrivate() +{ +#if QT_CONFIG(filesystemwatcher) + fileInfoGatherer->requestInterruption(); + 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 @@ -2075,18 +2101,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); } /*! @@ -2099,8 +2127,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 @@ -2109,7 +2143,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)); diff --git a/src/gui/itemmodels/qfilesystemmodel.h b/src/gui/itemmodels/qfilesystemmodel.h index d614ec2329..17bce1946f 100644 --- a/src/gui/itemmodels/qfilesystemmodel.h +++ b/src/gui/itemmodels/qfilesystemmodel.h @@ -9,7 +9,6 @@ #include <QtCore/qpair.h> #include <QtCore/qdir.h> #include <QtGui/qicon.h> -#include <QtCore/qdiriterator.h> QT_REQUIRE_CONFIG(filesystemmodel); @@ -114,7 +113,6 @@ public: qint64 size(const QModelIndex &index) const; QString type(const QModelIndex &index) const; - // ### Qt7 merge the two overloads, with tz QTimeZone::LocalTime QDateTime lastModified(const QModelIndex &index) const; QDateTime lastModified(const QModelIndex &index, const QTimeZone &tz) const; @@ -135,13 +133,6 @@ private: Q_DECLARE_PRIVATE(QFileSystemModel) Q_DISABLE_COPY(QFileSystemModel) - Q_PRIVATE_SLOT(d_func(), void _q_directoryChanged(const QString &directory, const QStringList &list)) - Q_PRIVATE_SLOT(d_func(), void _q_performDelayedSort()) - Q_PRIVATE_SLOT(d_func(), - void _q_fileSystemChanged(const QString &path, - const QList<QPair<QString, QFileInfo>> &)) - Q_PRIVATE_SLOT(d_func(), void _q_resolvedName(const QString &fileName, const QString &resolvedName)) - friend class QFileDialogPrivate; }; diff --git a/src/gui/itemmodels/qfilesystemmodel_p.h b/src/gui/itemmodels/qfilesystemmodel_p.h index f30b4801bc..e01b0d56e6 100644 --- a/src/gui/itemmodels/qfilesystemmodel_p.h +++ b/src/gui/itemmodels/qfilesystemmodel_p.h @@ -63,7 +63,13 @@ class Q_GUI_EXPORT QFileSystemModelPrivate : public QAbstractItemModelPrivate Q_DECLARE_PUBLIC(QFileSystemModel) public: - enum { NumColumns = 4 }; + enum { + NameColumn, + SizeColumn, + TypeColumn, + TimeColumn, + NumColumns = 4 + }; class QFileSystemNode { @@ -144,8 +150,12 @@ public: return visibleChildren.indexOf(childName); } void updateIcon(QAbstractFileIconProvider *iconProvider, const QString &path) { + if (!iconProvider) + return; + if (info) info->icon = iconProvider->icon(QFileInfo(path)); + for (QFileSystemNode *child : std::as_const(children)) { //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/) if (!path.isEmpty()) { @@ -159,6 +169,9 @@ public: } void retranslateStrings(QAbstractFileIconProvider *iconProvider, const QString &path) { + if (!iconProvider) + return; + if (info) info->displayType = iconProvider->type(QFileInfo(path)); for (QFileSystemNode *child : std::as_const(children)) { @@ -244,18 +257,18 @@ public: QString type(const QModelIndex &index) const; QString time(const QModelIndex &index) const; - void _q_directoryChanged(const QString &directory, const QStringList &list); - void _q_performDelayedSort(); - void _q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo>> &); - void _q_resolvedName(const QString &fileName, const QString &resolvedName); + void directoryChanged(const QString &directory, const QStringList &list); + void performDelayedSort(); + void fileSystemChanged(const QString &path, const QList<std::pair<QString, QFileInfo>> &); + void resolvedName(const QString &fileName, const QString &resolvedName); QDir rootDir; #if QT_CONFIG(filesystemwatcher) # ifdef Q_OS_WIN QStringList unwatchPathsAt(const QModelIndex &); - void watchPaths(const QStringList &paths) { fileInfoGatherer.watchPaths(paths); } + void watchPaths(const QStringList &paths) { fileInfoGatherer->watchPaths(paths); } # endif // Q_OS_WIN - QFileInfoGatherer fileInfoGatherer; + std::unique_ptr<QFileInfoGatherer> fileInfoGatherer; #endif // filesystemwatcher QTimer delayedSortTimer; QHash<const QFileSystemNode*, bool> bypassFilters; diff --git a/src/gui/itemmodels/qstandarditemmodel.cpp b/src/gui/itemmodels/qstandarditemmodel.cpp index 64ffdaa3b7..8b3e381431 100644 --- a/src/gui/itemmodels/qstandarditemmodel.cpp +++ b/src/gui/itemmodels/qstandarditemmodel.cpp @@ -19,6 +19,11 @@ QT_BEGIN_NAMESPACE +// Used internally to store the flags +namespace { +constexpr auto DataFlagsRole = Qt::ItemDataRole(Qt::UserRole - 1); +} + static inline QString qStandardItemModelDataListMimeType() { return QStringLiteral("application/x-qstandarditemmodeldatalist"); @@ -279,8 +284,7 @@ QMap<int, QVariant> QStandardItemPrivate::itemData() const { QMap<int, QVariant> result; for (const auto &data : values) { - // Qt::UserRole - 1 is used internally to store the flags - if (data.role != Qt::UserRole - 1) + if (data.role != DataFlagsRole) result.insert(data.role, data.value); } return result; @@ -868,9 +872,15 @@ QStandardItem *QStandardItem::parent() const Sets the item's data for the given \a role to the specified \a value. If you subclass QStandardItem and reimplement this function, your - reimplementation should call emitDataChanged() if you do not call - the base implementation of setData(). This will ensure that e.g. - views using the model are notified of the changes. + reimplementation should: + \list + \li call emitDataChanged() if you do not call the base implementation of + setData(). This will ensure that e.g. views using the model are notified + of the changes + \li call the base implementation for roles you don't handle, otherwise + setting flags, e.g. by calling setFlags(), setCheckable(), setEditable() + etc., will not work. + \endlist \note The default implementation treats Qt::EditRole and Qt::DisplayRole as referring to the same data. @@ -924,6 +934,11 @@ void QStandardItem::clearData() Returns the item's data for the given \a role, or an invalid QVariant if there is no data for the role. + If you reimplement this function, your reimplementation should call + the base implementation for roles you don't handle, otherwise getting + flags, e.g. by calling flags(), isCheckable(), isEditable() etc., + will not work. + \note The default implementation treats Qt::EditRole and Qt::DisplayRole as referring to the same data. */ @@ -983,7 +998,7 @@ void QStandardItem::emitDataChanged() */ void QStandardItem::setFlags(Qt::ItemFlags flags) { - setData((int)flags, Qt::UserRole - 1); + setData((int)flags, DataFlagsRole); } /*! @@ -998,7 +1013,7 @@ void QStandardItem::setFlags(Qt::ItemFlags flags) */ Qt::ItemFlags QStandardItem::flags() const { - QVariant v = data(Qt::UserRole - 1); + QVariant v = data(DataFlagsRole); if (!v.isValid()) return (Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable |Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled); @@ -1858,28 +1873,30 @@ QStandardItem *QStandardItem::takeChild(int row, int column) if (index != -1) { QModelIndex changedIdx; item = d->children.at(index); - if (item && d->model) { + if (item) { QStandardItemPrivate *const item_d = item->d_func(); - const int savedRows = item_d->rows; - const int savedCols = item_d->columns; - const QVector<QStandardItem*> savedChildren = item_d->children; - if (savedRows > 0) { - d->model->d_func()->rowsAboutToBeRemoved(item, 0, savedRows - 1); - item_d->rows = 0; - item_d->children = QVector<QStandardItem*>(); //slightly faster than clear - d->model->d_func()->rowsRemoved(item, 0, savedRows); - } - if (savedCols > 0) { - d->model->d_func()->columnsAboutToBeRemoved(item, 0, savedCols - 1); - item_d->columns = 0; - if (!item_d->children.isEmpty()) + if (d->model) { + QStandardItemModelPrivate *const model_d = d->model->d_func(); + const int savedRows = item_d->rows; + const int savedCols = item_d->columns; + const QVector<QStandardItem*> savedChildren = item_d->children; + if (savedRows > 0) { + model_d->rowsAboutToBeRemoved(item, 0, savedRows - 1); + item_d->rows = 0; + item_d->children = QVector<QStandardItem*>(); //slightly faster than clear + model_d->rowsRemoved(item, 0, savedRows); + } + if (savedCols > 0) { + model_d->columnsAboutToBeRemoved(item, 0, savedCols - 1); + item_d->columns = 0; item_d->children = QVector<QStandardItem*>(); //slightly faster than clear - d->model->d_func()->columnsRemoved(item, 0, savedCols); + model_d->columnsRemoved(item, 0, savedCols); + } + item_d->rows = savedRows; + item_d->columns = savedCols; + item_d->children = savedChildren; + changedIdx = d->model->indexFromItem(item); } - item_d->rows = savedRows; - item_d->columns = savedCols; - item_d->children = savedChildren; - changedIdx = d->model->indexFromItem(item); item_d->setParentAndModel(nullptr, nullptr); } d->children.replace(index, nullptr); @@ -3099,13 +3116,13 @@ QStringList QStandardItemModel::mimeTypes() const */ QMimeData *QStandardItemModel::mimeData(const QModelIndexList &indexes) const { - QMimeData *data = QAbstractItemModel::mimeData(indexes); + std::unique_ptr<QMimeData> data(QAbstractItemModel::mimeData(indexes)); if (!data) return nullptr; const QString format = qStandardItemModelDataListMimeType(); if (!mimeTypes().contains(format)) - return data; + return data.release(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); @@ -3157,7 +3174,7 @@ QMimeData *QStandardItemModel::mimeData(const QModelIndexList &indexes) const } data->setData(format, encoded); - return data; + return data.release(); } diff --git a/src/gui/itemmodels/qstandarditemmodel_p.h b/src/gui/itemmodels/qstandarditemmodel_p.h index def6c20727..a0c3f8a161 100644 --- a/src/gui/itemmodels/qstandarditemmodel_p.h +++ b/src/gui/itemmodels/qstandarditemmodel_p.h @@ -15,6 +15,8 @@ // We mean it. // +#include <QtGui/qstandarditemmodel.h> + #include <QtGui/private/qtguiglobal_p.h> #include "private/qabstractitemmodel_p.h" @@ -32,8 +34,10 @@ class QStandardItemData { public: inline QStandardItemData() : role(-1) {} - inline QStandardItemData(int r, const QVariant &v) : role(r), value(v) {} - inline QStandardItemData(const std::pair<const int&, const QVariant&> &p) : role(p.first), value(p.second) {} + inline QStandardItemData(int r, const QVariant &v) : + role(r == Qt::EditRole ? Qt::DisplayRole : r), value(v) {} + inline QStandardItemData(const std::pair<const int&, const QVariant&> &p) : + role(p.first == Qt::EditRole ? Qt::DisplayRole : p.first), value(p.second) {} int role; QVariant value; inline bool operator==(const QStandardItemData &other) const { return role == other.role && value == other.value; } |