summaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2020-08-19 13:40:34 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2020-08-25 06:33:44 +0200
commitfb9ec8ad44decba7b2878370f3711b61614f035a (patch)
tree6904b3936a8ee7c61a770ef92a4e033bb18b0fc3 /src/gui
parente24bbc4871e6a3711ffaa0e01397a40954465a3a (diff)
Move QFileSystemModel into QtGui
This requires a QAbstractFileIconProvider in QtGui, as the standard QFileIconProvider depends on QStyle, and cannot be moved out of QtWidgets. QAbstractFileIconProvider returns strings for file types, but returns no icons yet. Support for a default icon set might be added in a follow-up commit. Change-Id: Ib9d095cd612fdcf04db62f2e40709fcffe3dc2b7 Fixes: QTBUG-66177 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/.prev_CMakeLists.txt8
-rw-r--r--src/gui/CMakeLists.txt8
-rw-r--r--src/gui/configure.cmake7
-rw-r--r--src/gui/configure.json7
-rw-r--r--src/gui/gui.pro5
-rw-r--r--src/gui/image/image.pri7
-rw-r--r--src/gui/image/qabstractfileiconprovider.cpp197
-rw-r--r--src/gui/image/qabstractfileiconprovider.h84
-rw-r--r--src/gui/image/qabstractfileiconprovider_p.h73
-rw-r--r--src/gui/itemmodels/itemmodels.pri23
-rw-r--r--src/gui/itemmodels/qfileinfogatherer.cpp443
-rw-r--r--src/gui/itemmodels/qfileinfogatherer_p.h230
-rw-r--r--src/gui/itemmodels/qfilesystemmodel.cpp2165
-rw-r--r--src/gui/itemmodels/qfilesystemmodel.h190
-rw-r--r--src/gui/itemmodels/qfilesystemmodel_p.h323
15 files changed, 3761 insertions, 9 deletions
diff --git a/src/gui/.prev_CMakeLists.txt b/src/gui/.prev_CMakeLists.txt
index 8a93a8752a..3dbae0069d 100644
--- a/src/gui/.prev_CMakeLists.txt
+++ b/src/gui/.prev_CMakeLists.txt
@@ -9,6 +9,7 @@ qt_add_module(Gui
PLUGIN_TYPES accessiblebridge platforms platforms/darwin xcbglintegrations platformthemes platforminputcontexts generic iconengines imageformats egldeviceintegrations
SOURCES
image/qabstractfileiconengine.cpp image/qabstractfileiconengine_p.h
+ image/qabstractfileiconprovider.cpp image/qabstractfileiconprovider.h image/qabstractfileiconprovider_p.h
image/qbitmap.cpp image/qbitmap.h
image/qbmphandler.cpp image/qbmphandler_p.h
image/qicon.cpp image/qicon.h image/qicon_p.h
@@ -323,6 +324,7 @@ qt_extend_target(Gui CONDITION WIN32
gdi32
ole32
user32
+ shell32
PUBLIC_LIBRARIES
d3d11
dxgi
@@ -439,6 +441,12 @@ qt_extend_target(Gui CONDITION QT_FEATURE_draganddrop
kernel/qsimpledrag.cpp kernel/qsimpledrag_p.h
)
+qt_extend_target(Gui CONDITION QT_FEATURE_filesystemmodel
+ SOURCES
+ itemmodels/qfileinfogatherer.cpp itemmodels/qfileinfogatherer_p.h
+ itemmodels/qfilesystemmodel.cpp itemmodels/qfilesystemmodel.h itemmodels/qfilesystemmodel_p.h
+)
+
qt_extend_target(Gui CONDITION QT_FEATURE_shortcut
SOURCES
kernel/qkeysequence.cpp kernel/qkeysequence.h kernel/qkeysequence_p.h
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 79f3bafcd5..3396adf23c 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -60,6 +60,7 @@ qt_add_module(Gui
QMAKE_MODULE_CONFIG "${qmake_module_config}" # special case
SOURCES
image/qabstractfileiconengine.cpp image/qabstractfileiconengine_p.h
+ image/qabstractfileiconprovider.cpp image/qabstractfileiconprovider.h image/qabstractfileiconprovider_p.h
image/qbitmap.cpp image/qbitmap.h
image/qbmphandler.cpp image/qbmphandler_p.h
image/qicon.cpp image/qicon.h image/qicon_p.h
@@ -403,6 +404,7 @@ qt_extend_target(Gui CONDITION WIN32
gdi32
ole32
user32
+ shell32
PUBLIC_LIBRARIES
d3d11
dxgi
@@ -537,6 +539,12 @@ qt_extend_target(Gui CONDITION QT_FEATURE_draganddrop
kernel/qsimpledrag.cpp kernel/qsimpledrag_p.h
)
+qt_extend_target(Gui CONDITION QT_FEATURE_filesystemmodel
+ SOURCES
+ itemmodels/qfileinfogatherer.cpp itemmodels/qfileinfogatherer_p.h
+ itemmodels/qfilesystemmodel.cpp itemmodels/qfilesystemmodel.h itemmodels/qfilesystemmodel_p.h
+)
+
qt_extend_target(Gui CONDITION QT_FEATURE_shortcut
SOURCES
kernel/qkeysequence.cpp kernel/qkeysequence.h kernel/qkeysequence_p.h
diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake
index f0b5d9b1fe..a6d265183f 100644
--- a/src/gui/configure.cmake
+++ b/src/gui/configure.cmake
@@ -830,6 +830,13 @@ qt_feature("eglfs_x11" PRIVATE
LABEL "EGLFS X11"
CONDITION QT_FEATURE_eglfs AND QT_FEATURE_xcb_xlib AND QT_FEATURE_egl_x11
)
+qt_feature("filesystemmodel" PUBLIC
+ SECTION "File I/O"
+ LABEL "QFileSystemModel"
+ PURPOSE "Provides a data model for the local filesystem."
+ CONDITION QT_FEATURE_itemmodel
+)
+qt_feature_definition("filesystemmodel" "QT_NO_FILESYSTEMMODEL" NEGATE VALUE "1")
qt_feature("gif" PRIVATE
LABEL "GIF"
CONDITION QT_FEATURE_imageformatplugin
diff --git a/src/gui/configure.json b/src/gui/configure.json
index a88ce54360..a29f870f05 100644
--- a/src/gui/configure.json
+++ b/src/gui/configure.json
@@ -1548,6 +1548,13 @@
"condition": "features.itemmodel",
"output": [ "publicFeature", "feature" ]
},
+ "filesystemmodel": {
+ "label": "QFileSystemModel",
+ "purpose": "Provides a data model for the local filesystem.",
+ "section": "File I/O",
+ "condition": "features.itemmodel",
+ "output": [ "publicFeature", "feature" ]
+ },
"imageformatplugin": {
"label": "QImageIOPlugin",
"purpose": "Provides a base for writing a image format plugins.",
diff --git a/src/gui/gui.pro b/src/gui/gui.pro
index 4e79e31e6e..13631aba67 100644
--- a/src/gui/gui.pro
+++ b/src/gui/gui.pro
@@ -51,7 +51,10 @@ QMAKE_LIBS += $$QMAKE_LIBS_GUI
load(qt_module)
load(cmake_functions)
-win32: CMAKE_WINDOWS_BUILD = True
+win32: {
+ CMAKE_WINDOWS_BUILD = True
+ QMAKE_USE_PRIVATE += shell32
+}
qtConfig(egl) {
CMAKE_EGL_LIBS = $$cmakeProcessLibs($$QMAKE_LIBS_EGL)
diff --git a/src/gui/image/image.pri b/src/gui/image/image.pri
index bf0aa519a8..f503c97b82 100644
--- a/src/gui/image/image.pri
+++ b/src/gui/image/image.pri
@@ -26,7 +26,9 @@ HEADERS += \
image/qiconloader_p.h \
image/qiconengine.h \
image/qiconengineplugin.h \
- image/qabstractfileiconengine_p.h
+ image/qabstractfileiconengine_p.h \
+ image/qabstractfileiconprovider.h \
+ image/qabstractfileiconprovider_p.h
SOURCES += \
image/qbitmap.cpp \
@@ -48,7 +50,8 @@ SOURCES += \
image/qiconloader.cpp \
image/qiconengine.cpp \
image/qiconengineplugin.cpp \
- image/qabstractfileiconengine.cpp
+ image/qabstractfileiconengine.cpp \
+ image/qabstractfileiconprovider.cpp
qtConfig(movie) {
HEADERS += image/qmovie.h
diff --git a/src/gui/image/qabstractfileiconprovider.cpp b/src/gui/image/qabstractfileiconprovider.cpp
new file mode 100644
index 0000000000..2c2a694b60
--- /dev/null
+++ b/src/gui/image/qabstractfileiconprovider.cpp
@@ -0,0 +1,197 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui 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$
+**
+****************************************************************************/
+
+#include "qabstractfileiconprovider.h"
+
+#include <qguiapplication.h>
+#include <private/qabstractfileiconprovider_p.h>
+#include <private/qfilesystementry_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QAbstractFileIconProviderPrivate::QAbstractFileIconProviderPrivate(QAbstractFileIconProvider *q)
+ : q_ptr(q)
+{}
+
+QAbstractFileIconProviderPrivate::~QAbstractFileIconProviderPrivate() = default;
+
+/*!
+ \class QAbstractFileIconProvider
+
+ \inmodule QtGui
+ \since 6.0
+
+ \brief The QAbstractFileIconProvider class provides file icons for the QFileSystemModel class.
+*/
+
+/*!
+ \enum QAbstractFileIconProvider::IconType
+
+ \value Computer
+ \value Desktop
+ \value Trashcan
+ \value Network
+ \value Drive
+ \value Folder
+ \value File
+*/
+
+/*!
+ \enum QAbstractFileIconProvider::Option
+
+ \value DontUseCustomDirectoryIcons Always use the default directory icon.
+ Some platforms allow the user to set a different icon. Custom icon lookup
+ cause a big performance impact over network or removable drives.
+*/
+
+/*!
+ Constructs a file icon provider.
+*/
+QAbstractFileIconProvider::QAbstractFileIconProvider()
+ : d_ptr(new QAbstractFileIconProviderPrivate(this))
+{
+}
+
+/*!
+ \internal
+*/
+QAbstractFileIconProvider::QAbstractFileIconProvider(QAbstractFileIconProviderPrivate &dd)
+ : d_ptr(&dd)
+{}
+
+/*!
+ Destroys the file icon provider.
+*/
+
+QAbstractFileIconProvider::~QAbstractFileIconProvider() = default;
+
+
+/*!
+ Sets \a options that affect the icon provider.
+ \sa options()
+*/
+
+void QAbstractFileIconProvider::setOptions(QAbstractFileIconProvider::Options options)
+{
+ Q_D(QAbstractFileIconProvider);
+ d->options = options;
+}
+
+/*!
+ Returns all the options that affect the icon provider.
+ By default, all options are disabled.
+ \sa setOptions()
+*/
+
+QAbstractFileIconProvider::Options QAbstractFileIconProvider::options() const
+{
+ Q_D(const QAbstractFileIconProvider);
+ return d->options;
+}
+
+/*!
+ Returns an icon set for the given \a type.
+*/
+
+QIcon QAbstractFileIconProvider::icon(IconType type) const
+{
+ Q_UNUSED(type);
+ return {};
+}
+
+/*!
+ Returns an icon for the file described by \a info.
+*/
+
+QIcon QAbstractFileIconProvider::icon(const QFileInfo &info) const
+{
+ Q_UNUSED(info);
+ return {};
+}
+
+/*!
+ Returns the type of the file described by \a info.
+*/
+
+QString QAbstractFileIconProvider::type(const QFileInfo &info) const
+{
+ /* ### Qt 6 These string translations being in the QFileDialog context is not ideal,
+ but translating them in QFileDialog context only in the QFileIconProvider subclass
+ isn't either (it basically requires a duplication of the entire function).
+ Either we change it to QAbstractFileIconProvider context (invalidates existing
+ translations, but that's probably ok), or make a runtime lookup and use QFileDIalog
+ context only if QGuiApplication is a QApplication.
+ */
+ if (QFileSystemEntry::isRootPath(info.absoluteFilePath()))
+ return QGuiApplication::translate("QFileDialog", "Drive");
+ if (info.isFile()) {
+ // ### could use QMimeDatabase::mimeTypeForFile(const QFileInfo&) here
+ if (!info.suffix().isEmpty()) {
+ //: %1 is a file name suffix, for example txt
+ return QGuiApplication::translate("QFileDialog", "%1 File").arg(info.suffix());
+ }
+ return QGuiApplication::translate("QFileDialog", "File");
+ }
+
+ if (info.isDir())
+#ifdef Q_OS_WIN
+ return QGuiApplication::translate("QFileDialog", "File Folder", "Match Windows Explorer");
+#else
+ return QGuiApplication::translate("QFileDialog", "Folder", "All other platforms");
+#endif
+ // Windows - "File Folder"
+ // macOS - "Folder"
+ // Konqueror - "Folder"
+ // Nautilus - "folder"
+
+ if (info.isSymLink())
+#ifdef Q_OS_MACOS
+ return QGuiApplication::translate("QFileDialog", "Alias", "macOS Finder");
+#else
+ return QGuiApplication::translate("QFileDialog", "Shortcut", "All other platforms");
+#endif
+ // macOS - "Alias"
+ // Windows - "Shortcut"
+ // Konqueror - "Folder" or "TXT File" i.e. what it is pointing to
+ // Nautilus - "link to folder" or "link to object file", same as Konqueror
+
+ return QGuiApplication::translate("QFileDialog", "Unknown");
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/image/qabstractfileiconprovider.h b/src/gui/image/qabstractfileiconprovider.h
new file mode 100644
index 0000000000..fc107e0ca3
--- /dev/null
+++ b/src/gui/image/qabstractfileiconprovider.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui 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$
+**
+****************************************************************************/
+
+#ifndef QABSTRACTFILEICONPROVIDER_H
+#define QABSTRACTFILEICONPROVIDER_H
+
+#include <QtGui/qtguiglobal.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtGui/qicon.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractFileIconProviderPrivate;
+
+class Q_GUI_EXPORT QAbstractFileIconProvider
+{
+public:
+ enum IconType { Computer, Desktop, Trashcan, Network, Drive, Folder, File };
+ enum Option {
+ DontUseCustomDirectoryIcons = 0x00000001
+ };
+ Q_DECLARE_FLAGS(Options, Option)
+
+ QAbstractFileIconProvider();
+ virtual ~QAbstractFileIconProvider();
+
+ virtual QIcon icon(IconType) const;
+ virtual QIcon icon(const QFileInfo &) const;
+ virtual QString type(const QFileInfo &) const;
+
+ virtual void setOptions(Options);
+ virtual Options options() const;
+
+protected:
+ QAbstractFileIconProvider(QAbstractFileIconProviderPrivate &dd);
+ QScopedPointer<QAbstractFileIconProviderPrivate> d_ptr;
+
+private:
+ Q_DECLARE_PRIVATE(QAbstractFileIconProvider)
+ Q_DISABLE_COPY(QAbstractFileIconProvider)
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFileIconProvider::Options)
+
+QT_END_NAMESPACE
+
+#endif // QABSTRACTFILEICONPROVIDER_H
diff --git a/src/gui/image/qabstractfileiconprovider_p.h b/src/gui/image/qabstractfileiconprovider_p.h
new file mode 100644
index 0000000000..db7ac6db1b
--- /dev/null
+++ b/src/gui/image/qabstractfileiconprovider_p.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui 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$
+**
+****************************************************************************/
+
+#ifndef QABSTRACTFILEICONPROVIDER_P_H
+#define QABSTRACTFILEICONPROVIDER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+#include "qabstractfileiconprovider.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QAbstractFileIconProviderPrivate
+{
+ Q_DECLARE_PUBLIC(QAbstractFileIconProvider)
+
+public:
+ QAbstractFileIconProviderPrivate(QAbstractFileIconProvider *q);
+ virtual ~QAbstractFileIconProviderPrivate();
+
+ QAbstractFileIconProvider *q_ptr = nullptr;
+ QAbstractFileIconProvider::Options options = {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QABSTRACTFILEICONPROVIDER_P_H
diff --git a/src/gui/itemmodels/itemmodels.pri b/src/gui/itemmodels/itemmodels.pri
index cab0594174..4de6cc29e2 100644
--- a/src/gui/itemmodels/itemmodels.pri
+++ b/src/gui/itemmodels/itemmodels.pri
@@ -1,8 +1,19 @@
-!qtConfig(standarditemmodel): return()
+qtConfig(standarditemmodel) {
+ HEADERS += \
+ itemmodels/qstandarditemmodel.h \
+ itemmodels/qstandarditemmodel_p.h \
-HEADERS += \
- itemmodels/qstandarditemmodel.h \
- itemmodels/qstandarditemmodel_p.h \
+ SOURCES += \
+ itemmodels/qstandarditemmodel.cpp \
+}
-SOURCES += \
- itemmodels/qstandarditemmodel.cpp \
+qtConfig(filesystemmodel) {
+ HEADERS += \
+ itemmodels/qfilesystemmodel.h \
+ itemmodels/qfilesystemmodel_p.h \
+ itemmodels/qfileinfogatherer_p.h
+
+ SOURCES += \
+ itemmodels/qfilesystemmodel.cpp \
+ itemmodels/qfileinfogatherer.cpp
+}
diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp
new file mode 100644
index 0000000000..417c3d7e42
--- /dev/null
+++ b/src/gui/itemmodels/qfileinfogatherer.cpp
@@ -0,0 +1,443 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "qfileinfogatherer_p.h"
+#include <qdebug.h>
+#include <qdiriterator.h>
+#include <private/qfileinfo_p.h>
+#ifndef Q_OS_WIN
+# include <unistd.h>
+# include <sys/types.h>
+#endif
+#if defined(Q_OS_VXWORKS)
+# include "qplatformdefs.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+#ifdef QT_BUILD_INTERNAL
+static QBasicAtomicInt fetchedRoot = Q_BASIC_ATOMIC_INITIALIZER(false);
+Q_AUTOTEST_EXPORT void qt_test_resetFetchedRoot()
+{
+ fetchedRoot.storeRelaxed(false);
+}
+
+Q_AUTOTEST_EXPORT bool qt_test_isFetchedRoot()
+{
+ return fetchedRoot.loadRelaxed();
+}
+#endif
+
+static QString translateDriveName(const QFileInfo &drive)
+{
+ QString driveName = drive.absoluteFilePath();
+#ifdef Q_OS_WIN
+ if (driveName.startsWith(QLatin1Char('/'))) // UNC host
+ return drive.fileName();
+ if (driveName.endsWith(QLatin1Char('/')))
+ driveName.chop(1);
+#endif // Q_OS_WIN
+ return driveName;
+}
+
+/*!
+ Creates thread
+*/
+QFileInfoGatherer::QFileInfoGatherer(QObject *parent)
+ : QThread(parent)
+ , m_iconProvider(&defaultProvider)
+{
+ start(LowPriority);
+}
+
+/*!
+ Destroys thread
+*/
+QFileInfoGatherer::~QFileInfoGatherer()
+{
+ abort.storeRelaxed(true);
+ QMutexLocker locker(&mutex);
+ condition.wakeAll();
+ locker.unlock();
+ wait();
+}
+
+void QFileInfoGatherer::setResolveSymlinks(bool enable)
+{
+ Q_UNUSED(enable);
+#ifdef Q_OS_WIN
+ m_resolveSymlinks = enable;
+#endif
+}
+
+void QFileInfoGatherer::driveAdded()
+{
+ fetchExtendedInformation(QString(), QStringList());
+}
+
+void QFileInfoGatherer::driveRemoved()
+{
+ QStringList drives;
+ const QFileInfoList driveInfoList = QDir::drives();
+ for (const QFileInfo &fi : driveInfoList)
+ drives.append(translateDriveName(fi));
+ newListOfFiles(QString(), drives);
+}
+
+bool QFileInfoGatherer::resolveSymlinks() const
+{
+#ifdef Q_OS_WIN
+ return m_resolveSymlinks;
+#else
+ return false;
+#endif
+}
+
+void QFileInfoGatherer::setIconProvider(QAbstractFileIconProvider *provider)
+{
+ m_iconProvider = provider;
+}
+
+QAbstractFileIconProvider *QFileInfoGatherer::iconProvider() const
+{
+ return m_iconProvider;
+}
+
+/*!
+ Fetch extended information for all \a files in \a path
+
+ \sa updateFile(), update(), resolvedName()
+*/
+void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStringList &files)
+{
+ 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) {
+ return;
+ }
+ loc = this->path.lastIndexOf(path, loc - 1);
+ }
+ this->path.push(path);
+ this->files.push(files);
+ condition.wakeAll();
+
+#if QT_CONFIG(filesystemwatcher)
+ if (files.isEmpty()
+ && !path.isEmpty()
+ && !path.startsWith(QLatin1String("//")) /*don't watch UNC path*/) {
+ if (!watchedDirectories().contains(path))
+ watchPaths(QStringList(path));
+ }
+#endif
+}
+
+/*!
+ Fetch extended information for all \a filePath
+
+ \sa fetchExtendedInformation()
+*/
+void QFileInfoGatherer::updateFile(const QString &filePath)
+{
+ QString dir = filePath.mid(0, filePath.lastIndexOf(QLatin1Char('/')));
+ QString fileName = filePath.mid(dir.length() + 1);
+ fetchExtendedInformation(dir, QStringList(fileName));
+}
+
+QStringList QFileInfoGatherer::watchedFiles() const
+{
+#if QT_CONFIG(filesystemwatcher)
+ if (m_watcher)
+ return m_watcher->files();
+#endif
+ return {};
+}
+
+QStringList QFileInfoGatherer::watchedDirectories() const
+{
+#if QT_CONFIG(filesystemwatcher)
+ if (m_watcher)
+ return m_watcher->directories();
+#endif
+ return {};
+}
+
+void QFileInfoGatherer::createWatcher()
+{
+#if QT_CONFIG(filesystemwatcher)
+ m_watcher = new QFileSystemWatcher(this);
+ connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &QFileInfoGatherer::list);
+ connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &QFileInfoGatherer::updateFile);
+# if defined(Q_OS_WIN)
+ const QVariant listener = m_watcher->property("_q_driveListener");
+ if (listener.canConvert<QObject *>()) {
+ if (QObject *driveListener = listener.value<QObject *>()) {
+ connect(driveListener, SIGNAL(driveAdded()), this, SLOT(driveAdded()));
+ connect(driveListener, SIGNAL(driveRemoved()), this, SLOT(driveRemoved()));
+ }
+ }
+# endif // Q_OS_WIN
+#endif
+}
+
+void QFileInfoGatherer::watchPaths(const QStringList &paths)
+{
+#if QT_CONFIG(filesystemwatcher)
+ if (m_watching) {
+ if (m_watcher == nullptr)
+ createWatcher();
+ m_watcher->addPaths(paths);
+ }
+#else
+ Q_UNUSED(paths);
+#endif
+}
+
+void QFileInfoGatherer::unwatchPaths(const QStringList &paths)
+{
+#if QT_CONFIG(filesystemwatcher)
+ if (m_watcher && !paths.isEmpty())
+ m_watcher->removePaths(paths);
+#else
+ Q_UNUSED(paths);
+#endif
+}
+
+bool QFileInfoGatherer::isWatching() const
+{
+ bool result = false;
+#if QT_CONFIG(filesystemwatcher)
+ QMutexLocker locker(&mutex);
+ result = m_watching;
+#endif
+ return result;
+}
+
+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;
+ }
+#else
+ Q_UNUSED(v);
+#endif
+}
+
+/*
+ List all files in \a directoryPath
+
+ \sa listed()
+*/
+void QFileInfoGatherer::clear()
+{
+#if QT_CONFIG(filesystemwatcher)
+ QMutexLocker locker(&mutex);
+ unwatchPaths(watchedFiles());
+ unwatchPaths(watchedDirectories());
+#endif
+}
+
+/*
+ Remove a \a path from the watcher
+
+ \sa listed()
+*/
+void QFileInfoGatherer::removePath(const QString &path)
+{
+#if QT_CONFIG(filesystemwatcher)
+ QMutexLocker locker(&mutex);
+ unwatchPaths(QStringList(path));
+#else
+ Q_UNUSED(path);
+#endif
+}
+
+/*
+ List all files in \a directoryPath
+
+ \sa listed()
+*/
+void QFileInfoGatherer::list(const QString &directoryPath)
+{
+ fetchExtendedInformation(directoryPath, QStringList());
+}
+
+/*
+ Until aborted wait to fetch a directory or files
+*/
+void QFileInfoGatherer::run()
+{
+ forever {
+ QMutexLocker locker(&mutex);
+ while (!abort.loadRelaxed() && path.isEmpty())
+ condition.wait(&mutex);
+ if (abort.loadRelaxed())
+ return;
+ const QString thisPath = qAsConst(path).front();
+ path.pop_front();
+ const QStringList thisList = qAsConst(files).front();
+ files.pop_front();
+ locker.unlock();
+
+ getFileInfos(thisPath, thisList);
+ }
+}
+
+QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const
+{
+ QExtendedInformation info(fileInfo);
+ info.icon = m_iconProvider->icon(fileInfo);
+ info.displayType = m_iconProvider->type(fileInfo);
+#if QT_CONFIG(filesystemwatcher)
+ // ### Not ready to listen all modifications by default
+ static const bool watchFiles = qEnvironmentVariableIsSet("QT_FILESYSTEMMODEL_WATCH_FILES");
+ if (watchFiles) {
+ if (!fileInfo.exists() && !fileInfo.isSymLink()) {
+ const_cast<QFileInfoGatherer *>(this)->
+ unwatchPaths(QStringList(fileInfo.absoluteFilePath()));
+ } else {
+ const QString path = fileInfo.absoluteFilePath();
+ if (!path.isEmpty() && fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable()
+ && !watchedFiles().contains(path)) {
+ const_cast<QFileInfoGatherer *>(this)->watchPaths(QStringList(path));
+ }
+ }
+ }
+#endif // filesystemwatcher
+
+#ifdef Q_OS_WIN
+ if (m_resolveSymlinks && info.isSymLink(/* ignoreNtfsSymLinks = */ true)) {
+ QFileInfo resolvedInfo(fileInfo.symLinkTarget());
+ resolvedInfo = resolvedInfo.canonicalFilePath();
+ if (resolvedInfo.exists()) {
+ emit nameResolved(fileInfo.filePath(), resolvedInfo.fileName());
+ }
+ }
+#endif
+ return info;
+}
+
+/*
+ Get specific file info's, batch the files so update when we have 100
+ items and every 200ms after that
+ */
+void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &files)
+{
+ // List drives
+ if (path.isEmpty()) {
+#ifdef QT_BUILD_INTERNAL
+ fetchedRoot.storeRelaxed(true);
+#endif
+ QFileInfoList infoList;
+ if (files.isEmpty()) {
+ infoList = QDir::drives();
+ } else {
+ infoList.reserve(files.count());
+ for (const auto &file : files)
+ infoList << QFileInfo(file);
+ }
+ QList<QPair<QString, QFileInfo>> updatedFiles;
+ updatedFiles.reserve(infoList.count());
+ for (int i = infoList.count() - 1; i >= 0; --i) {
+ QFileInfo driveInfo = infoList.at(i);
+ driveInfo.stat();
+ QString driveName = translateDriveName(driveInfo);
+ updatedFiles.append(QPair<QString,QFileInfo>(driveName, driveInfo));
+ }
+ emit updates(path, updatedFiles);
+ return;
+ }
+
+ QElapsedTimer base;
+ base.start();
+ QFileInfo fileInfo;
+ bool firstTime = true;
+ QList<QPair<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()) {
+ dirIt.next();
+ fileInfo = dirIt.fileInfo();
+ fileInfo.stat();
+ allFiles.append(fileInfo.fileName());
+ fetch(fileInfo, base, firstTime, updatedFiles, path);
+ }
+ }
+ if (!allFiles.isEmpty())
+ emit newListOfFiles(path, allFiles);
+
+ QStringList::const_iterator filesIt = filesToCheck.constBegin();
+ while (!abort.loadRelaxed() && filesIt != filesToCheck.constEnd()) {
+ fileInfo.setFile(path + QDir::separator() + *filesIt);
+ ++filesIt;
+ fileInfo.stat();
+ fetch(fileInfo, base, firstTime, updatedFiles, path);
+ }
+ if (!updatedFiles.isEmpty())
+ emit updates(path, updatedFiles);
+ emit directoryLoaded(path);
+}
+
+void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime,
+ QList<QPair<QString, QFileInfo>> &updatedFiles, const QString &path)
+{
+ updatedFiles.append(QPair<QString, QFileInfo>(fileInfo.fileName(), fileInfo));
+ QElapsedTimer current;
+ current.start();
+ if ((firstTime && updatedFiles.count() > 100) || base.msecsTo(current) > 1000) {
+ emit updates(path, updatedFiles);
+ updatedFiles.clear();
+ base = current;
+ firstTime = false;
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qfileinfogatherer_p.cpp"
diff --git a/src/gui/itemmodels/qfileinfogatherer_p.h b/src/gui/itemmodels/qfileinfogatherer_p.h
new file mode 100644
index 0000000000..374331a588
--- /dev/null
+++ b/src/gui/itemmodels/qfileinfogatherer_p.h
@@ -0,0 +1,230 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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$
+**
+****************************************************************************/
+
+#ifndef QFILEINFOGATHERER_H
+#define QFILEINFOGATHERER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+
+#include <qthread.h>
+#include <qmutex.h>
+#include <qwaitcondition.h>
+#if QT_CONFIG(filesystemwatcher)
+#include <qfilesystemwatcher.h>
+#endif
+#include <qabstractfileiconprovider.h>
+#include <qpair.h>
+#include <qstack.h>
+#include <qdatetime.h>
+#include <qdir.h>
+#include <qelapsedtimer.h>
+
+#include <private/qfilesystemengine_p.h>
+
+QT_REQUIRE_CONFIG(filesystemmodel);
+
+QT_BEGIN_NAMESPACE
+
+class QExtendedInformation {
+public:
+ enum Type { Dir, File, System };
+
+ QExtendedInformation() {}
+ QExtendedInformation(const QFileInfo &info) : mFileInfo(info) {}
+
+ inline bool isDir() { return type() == Dir; }
+ inline bool isFile() { return type() == File; }
+ inline bool isSystem() { return type() == System; }
+
+ bool operator ==(const QExtendedInformation &fileInfo) const {
+ return mFileInfo == fileInfo.mFileInfo
+ && displayType == fileInfo.displayType
+ && permissions() == fileInfo.permissions()
+ && lastModified() == fileInfo.lastModified();
+ }
+
+#ifndef QT_NO_FSFILEENGINE
+ bool isCaseSensitive() const {
+ return QFileSystemEngine::isCaseSensitive();
+ }
+#endif
+
+ QFile::Permissions permissions() const {
+ return mFileInfo.permissions();
+ }
+
+ Type type() const {
+ if (mFileInfo.isDir()) {
+ return QExtendedInformation::Dir;
+ }
+ if (mFileInfo.isFile()) {
+ return QExtendedInformation::File;
+ }
+ if (!mFileInfo.exists() && mFileInfo.isSymLink()) {
+ return QExtendedInformation::System;
+ }
+ return QExtendedInformation::System;
+ }
+
+ bool isSymLink(bool ignoreNtfsSymLinks = false) const
+ {
+ if (ignoreNtfsSymLinks) {
+#ifdef Q_OS_WIN
+ return !mFileInfo.suffix().compare(QLatin1String("lnk"), Qt::CaseInsensitive);
+#endif
+ }
+ return mFileInfo.isSymLink();
+ }
+
+ bool isHidden() const {
+ return mFileInfo.isHidden();
+ }
+
+ QFileInfo fileInfo() const {
+ return mFileInfo;
+ }
+
+ QDateTime lastModified() const {
+ return mFileInfo.lastModified();
+ }
+
+ qint64 size() const {
+ qint64 size = -1;
+ if (type() == QExtendedInformation::Dir)
+ size = 0;
+ if (type() == QExtendedInformation::File)
+ size = mFileInfo.size();
+ if (!mFileInfo.exists() && !mFileInfo.isSymLink())
+ size = -1;
+ return size;
+ }
+
+ QString displayType;
+ QIcon icon;
+
+private :
+ QFileInfo mFileInfo;
+};
+
+class QFileIconProvider;
+
+class Q_GUI_EXPORT QFileInfoGatherer : public QThread
+{
+Q_OBJECT
+
+Q_SIGNALS:
+ void updates(const QString &directory, const QList<QPair<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);
+
+public:
+ explicit QFileInfoGatherer(QObject *parent = nullptr);
+ ~QFileInfoGatherer();
+
+ QStringList watchedFiles() const;
+ QStringList watchedDirectories() const;
+ void watchPaths(const QStringList &paths);
+ void unwatchPaths(const QStringList &paths);
+
+ bool isWatching() const;
+ void setWatching(bool v);
+
+ // only callable from this->thread():
+ void clear();
+ void removePath(const QString &path);
+ QExtendedInformation getInfo(const QFileInfo &info) const;
+ QAbstractFileIconProvider *iconProvider() const;
+ bool resolveSymlinks() const;
+
+public Q_SLOTS:
+ void list(const QString &directoryPath);
+ void fetchExtendedInformation(const QString &path, const QStringList &files);
+ void updateFile(const QString &path);
+ void setResolveSymlinks(bool enable);
+ void setIconProvider(QAbstractFileIconProvider *provider);
+
+private Q_SLOTS:
+ void driveAdded();
+ void driveRemoved();
+
+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);
+
+private:
+ void createWatcher();
+
+ mutable QMutex mutex;
+ // begin protected by mutex
+ QWaitCondition condition;
+ QStack<QString> path;
+ QStack<QStringList> files;
+ // end protected by mutex
+ QAtomicInt abort;
+
+#if QT_CONFIG(filesystemwatcher)
+ QFileSystemWatcher *m_watcher = nullptr;
+#endif
+ QAbstractFileIconProvider *m_iconProvider; // not accessed by run()
+ QAbstractFileIconProvider defaultProvider;
+#ifdef Q_OS_WIN
+ bool m_resolveSymlinks = true; // not accessed by run()
+#endif
+#if QT_CONFIG(filesystemwatcher)
+ bool m_watching = true;
+#endif
+};
+
+QT_END_NAMESPACE
+#endif // QFILEINFOGATHERER_H
diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp
new file mode 100644
index 0000000000..481f11162a
--- /dev/null
+++ b/src/gui/itemmodels/qfilesystemmodel.cpp
@@ -0,0 +1,2165 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "qfilesystemmodel_p.h"
+#include "qfilesystemmodel.h"
+#include <qabstractfileiconprovider.h>
+#include <qlocale.h>
+#include <qmimedata.h>
+#include <qurl.h>
+#include <qdebug.h>
+#include <QtCore/qcollator.h>
+#if QT_CONFIG(regularexpression)
+# include <QtCore/qregularexpression.h>
+#endif
+
+#include <algorithm>
+
+#ifdef Q_OS_WIN
+# include <QtCore/QVarLengthArray>
+# include <qt_windows.h>
+# include <shlobj.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \enum QFileSystemModel::Roles
+ \value FileIconRole
+ \value FilePathRole
+ \value FileNameRole
+ \value FilePermissions
+*/
+
+/*!
+ \class QFileSystemModel
+ \since 4.4
+
+ \brief The QFileSystemModel class provides a data model for the local filesystem.
+
+ \ingroup model-view
+ \inmodule QtWidgets
+
+ 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 filter.
+
+ QFileSystemModel 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(), isDir(), fileName() and filePath() functions provide information
+ about the underlying files and directories related to items in the model.
+ Directories can be created and removed using mkdir(), rmdir().
+
+ \note QFileSystemModel requires an instance of \l QApplication.
+
+ \section1 Example Usage
+
+ A directory model that displays the contents of a default directory
+ is usually constructed with a parent object:
+
+ \snippet shareddirmodel/main.cpp 2
+
+ A tree view can be used to display the contents of the model
+
+ \snippet shareddirmodel/main.cpp 4
+
+ and the contents of a particular directory can be displayed by
+ setting the tree view's root index:
+
+ \snippet shareddirmodel/main.cpp 7
+
+ The view's root index can be used to control how much of a
+ hierarchical model is displayed. QFileSystemModel provides a convenience
+ function that returns a suitable model index for a path to a
+ directory within the model.
+
+ \section1 Caching and Performance
+
+ QFileSystemModel will not fetch any files or directories until setRootPath()
+ is called. This will prevent any unnecessary querying on the file system
+ until that point such as listing the drives on Windows.
+
+ QFileSystemModel uses a separate thread to populate itself so it will not
+ cause the main thread to hang as the file system is being queried.
+ Calls to rowCount() will return 0 until the model populates a directory.
+
+ QFileSystemModel keeps a cache with file information. The cache is
+ automatically kept up to date using the QFileSystemWatcher.
+
+ \sa {Model Classes}
+*/
+
+/*!
+ \fn bool QFileSystemModel::rmdir(const QModelIndex &index)
+
+ Removes the directory corresponding to the model item \a index in the
+ file system model and \b{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
+ \b{not} move them to a location where they can be recovered.
+
+ \sa remove()
+*/
+
+/*!
+ \fn QIcon QFileSystemModel::fileName(const QModelIndex &index) const
+
+ Returns the file name for the item stored in the model under the given
+ \a index.
+*/
+
+/*!
+ \fn QIcon QFileSystemModel::fileIcon(const QModelIndex &index) const
+
+ Returns the icon for the item stored in the model under the given
+ \a index.
+*/
+
+/*!
+ \fn QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const
+
+ Returns the QFileInfo for the item stored in the model under the given
+ \a index.
+*/
+QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ return d->node(index)->fileInfo();
+}
+
+/*!
+ \fn void QFileSystemModel::rootPathChanged(const QString &newPath);
+
+ This signal is emitted whenever the root path has been changed to a \a newPath.
+*/
+
+/*!
+ \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.
+*/
+
+/*!
+ \since 4.7
+ \fn void QFileSystemModel::directoryLoaded(const QString &path)
+
+ This signal is emitted when the gatherer thread has finished to load the \a path.
+
+*/
+
+/*!
+ \fn bool QFileSystemModel::remove(const QModelIndex &index)
+
+ Removes the model item \a index from the file system model and \b{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 \b{not}
+ move them to a location where they can be recovered.
+
+ \sa rmdir()
+*/
+
+bool QFileSystemModel::remove(const QModelIndex &aindex)
+{
+ Q_D(QFileSystemModel);
+
+ const QString path = d->filePath(aindex);
+ const QFileInfo fileInfo(path);
+#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
+ // QTBUG-65683: Remove file system watchers prior to deletion to prevent
+ // failure due to locked files on Windows.
+ const QStringList watchedPaths = d->unwatchPathsAt(aindex);
+#endif // filesystemwatcher && Q_OS_WIN
+ const bool success = (fileInfo.isFile() || fileInfo.isSymLink())
+ ? QFile::remove(path) : QDir(path).removeRecursively();
+#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
+ if (!success)
+ d->watchPaths(watchedPaths);
+#endif // filesystemwatcher && Q_OS_WIN
+ return success;
+}
+
+/*!
+ Constructs a file system model with the given \a parent.
+*/
+QFileSystemModel::QFileSystemModel(QObject *parent) :
+ QFileSystemModel(*new QFileSystemModelPrivate, parent)
+{
+}
+
+/*!
+ \internal
+*/
+QFileSystemModel::QFileSystemModel(QFileSystemModelPrivate &dd, QObject *parent)
+ : QAbstractItemModel(dd, parent)
+{
+ Q_D(QFileSystemModel);
+ d->init();
+}
+
+/*!
+ Destroys this file system model.
+*/
+QFileSystemModel::~QFileSystemModel() = default;
+
+/*!
+ \reimp
+*/
+QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent) const
+{
+ Q_D(const QFileSystemModel);
+ if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
+ return QModelIndex();
+
+ // get the parent node
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = (d->indexValid(parent) ? d->node(parent) :
+ const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&d->root));
+ Q_ASSERT(parentNode);
+
+ // now get the internal pointer for the index
+ const int i = d->translateVisibleLocation(parentNode, row);
+ if (i >= parentNode->visibleChildren.size())
+ return QModelIndex();
+ const QString &childName = parentNode->visibleChildren.at(i);
+ const QFileSystemModelPrivate::QFileSystemNode *indexNode = parentNode->children.value(childName);
+ Q_ASSERT(indexNode);
+
+ return createIndex(row, column, const_cast<QFileSystemModelPrivate::QFileSystemNode*>(indexNode));
+}
+
+/*!
+ \reimp
+*/
+QModelIndex QFileSystemModel::sibling(int row, int column, const QModelIndex &idx) const
+{
+ if (row == idx.row() && column < QFileSystemModelPrivate::NumColumns) {
+ // cheap sibling operation: just adjust the column:
+ return createIndex(row, column, idx.internalPointer());
+ } else {
+ // for anything else: call the default implementation
+ // (this could probably be optimized, too):
+ return QAbstractItemModel::sibling(row, column, idx);
+ }
+}
+
+/*!
+ \overload
+
+ Returns the model item index for the given \a path and \a column.
+*/
+QModelIndex QFileSystemModel::index(const QString &path, int column) const
+{
+ Q_D(const QFileSystemModel);
+ QFileSystemModelPrivate::QFileSystemNode *node = d->node(path, false);
+ return d->index(node, column);
+}
+
+/*!
+ \internal
+
+ Return the QFileSystemNode that goes to index.
+ */
+QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return const_cast<QFileSystemNode*>(&root);
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = static_cast<QFileSystemModelPrivate::QFileSystemNode*>(index.internalPointer());
+ Q_ASSERT(indexNode);
+ return indexNode;
+}
+
+#ifdef Q_OS_WIN32
+static QString qt_GetLongPathName(const QString &strShortPath)
+{
+ if (strShortPath.isEmpty()
+ || strShortPath == QLatin1String(".") || strShortPath == QLatin1String(".."))
+ return strShortPath;
+ if (strShortPath.length() == 2 && strShortPath.endsWith(QLatin1Char(':')))
+ return strShortPath.toUpper();
+ const QString absPath = QDir(strShortPath).absolutePath();
+ if (absPath.startsWith(QLatin1String("//"))
+ || absPath.startsWith(QLatin1String("\\\\"))) // unc
+ return QDir::fromNativeSeparators(absPath);
+ if (absPath.startsWith(QLatin1Char('/')))
+ return QString();
+ const QString inputString = QLatin1String("\\\\?\\") + QDir::toNativeSeparators(absPath);
+ QVarLengthArray<TCHAR, MAX_PATH> buffer(MAX_PATH);
+ DWORD result = ::GetLongPathName((wchar_t*)inputString.utf16(),
+ buffer.data(),
+ buffer.size());
+ if (result > DWORD(buffer.size())) {
+ buffer.resize(result);
+ result = ::GetLongPathName((wchar_t*)inputString.utf16(),
+ buffer.data(),
+ buffer.size());
+ }
+ if (result > 4) {
+ QString longPath = QString::fromWCharArray(buffer.data() + 4); // ignoring prefix
+ longPath[0] = longPath.at(0).toUpper(); // capital drive letters
+ return QDir::fromNativeSeparators(longPath);
+ } else {
+ return QDir::fromNativeSeparators(strShortPath);
+ }
+}
+#endif
+
+/*!
+ \internal
+
+ Given a path return the matching QFileSystemNode or &root if invalid
+*/
+QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const
+{
+ Q_Q(const QFileSystemModel);
+ Q_UNUSED(q);
+ if (path.isEmpty() || path == myComputer() || path.startsWith(QLatin1Char(':')))
+ return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
+
+ // Construct the nodes up to the new root path if they need to be built
+ QString absolutePath;
+#ifdef Q_OS_WIN32
+ QString longPath = qt_GetLongPathName(path);
+#else
+ QString longPath = path;
+#endif
+ if (longPath == rootDir.path())
+ absolutePath = rootDir.absolutePath();
+ else
+ absolutePath = QDir(longPath).absolutePath();
+
+ // ### TODO can we use bool QAbstractFileEngine::caseSensitive() const?
+ QStringList pathElements = absolutePath.split(QLatin1Char('/'), Qt::SkipEmptyParts);
+ if ((pathElements.isEmpty())
+#if !defined(Q_OS_WIN)
+ && QDir::fromNativeSeparators(longPath) != QLatin1String("/")
+#endif
+ )
+ return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
+ QModelIndex index = QModelIndex(); // start with "My Computer"
+ QString elementPath;
+ QChar separator = QLatin1Char('/');
+ QString trailingSeparator;
+#if defined(Q_OS_WIN)
+ if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path
+ QString host = QLatin1String("\\\\") + 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("\\");
+ int r = 0;
+ QFileSystemModelPrivate::QFileSystemNode *rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
+ if (!root.children.contains(host.toLower())) {
+ if (pathElements.count() == 1 && !absolutePath.endsWith(QLatin1Char('/')))
+ return rootNode;
+ QFileInfo info(host);
+ if (!info.exists())
+ return rootNode;
+ QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
+ p->addNode(rootNode, host,info);
+ p->addVisibleFiles(rootNode, QStringList(host));
+ }
+ r = rootNode->visibleLocation(host);
+ r = translateVisibleLocation(rootNode, r);
+ index = q->index(r, 0, QModelIndex());
+ pathElements.pop_front();
+ separator = QLatin1Char('\\');
+ elementPath = host;
+ elementPath.append(separator);
+ } else {
+ if (!pathElements.at(0).contains(QLatin1Char(':'))) {
+ QString rootPath = QDir(longPath).rootPath();
+ pathElements.prepend(rootPath);
+ }
+ if (pathElements.at(0).endsWith(QLatin1Char('/')))
+ pathElements[0].chop(1);
+ }
+#else
+ // add the "/" item, since it is a valid path element on Unix
+ if (absolutePath[0] == QLatin1Char('/'))
+ pathElements.prepend(QLatin1String("/"));
+#endif
+
+ QFileSystemModelPrivate::QFileSystemNode *parent = node(index);
+
+ for (int i = 0; i < pathElements.count(); ++i) {
+ QString element = pathElements.at(i);
+ if (i != 0)
+ elementPath.append(separator);
+ elementPath.append(element);
+ if (i == pathElements.count() - 1)
+ elementPath.append(trailingSeparator);
+#ifdef Q_OS_WIN
+ // On Windows, "filename " and "filename" are equivalent and
+ // "filename . " and "filename" are equivalent
+ // "filename......." and "filename" are equivalent Task #133928
+ // whereas "filename .txt" is still "filename .txt"
+ // 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(' ')))
+ element.chop(1);
+ // Only filenames that can't possibly exist will be end up being empty
+ if (element.isEmpty())
+ return parent;
+#endif
+ bool alreadyExisted = parent->children.contains(element);
+
+ // 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)
+ || (parent->caseSensitive()
+ && parent->children.value(element)->fileName != element)
+ || (!parent->caseSensitive()
+ && parent->children.value(element)->fileName.toLower() != element.toLower()))
+ alreadyExisted = false;
+ }
+
+ QFileSystemModelPrivate::QFileSystemNode *node;
+ if (!alreadyExisted) {
+ // Someone might call ::index("file://cookie/monster/doesn't/like/veggies"),
+ // a path that doesn't exists, I.E. don't blindly create directories.
+ QFileInfo info(elementPath);
+ if (!info.exists())
+ return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
+ QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
+ node = p->addNode(parent, element,info);
+#if QT_CONFIG(filesystemwatcher)
+ node->populate(fileInfoGatherer.getInfo(info));
+#endif
+ } else {
+ node = parent->children.value(element);
+ }
+
+ Q_ASSERT(node);
+ if (!node->isVisible) {
+ // It has been filtered out
+ if (alreadyExisted && node->hasInformation() && !fetch)
+ return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root);
+
+ QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
+ p->addVisibleFiles(parent, QStringList(element));
+ if (!p->bypassFilters.contains(node))
+ p->bypassFilters[node] = 1;
+ QString dir = q->filePath(this->index(parent));
+ if (!node->hasInformation() && fetch) {
+ Fetching f = { std::move(dir), std::move(element), node };
+ p->toFetch.append(std::move(f));
+ p->fetchingTimer.start(0, const_cast<QFileSystemModel*>(q));
+ }
+ }
+ parent = node;
+ }
+
+ return parent;
+}
+
+/*!
+ \reimp
+*/
+void QFileSystemModel::timerEvent(QTimerEvent *event)
+{
+ Q_D(QFileSystemModel);
+ if (event->timerId() == d->fetchingTimer.timerId()) {
+ d->fetchingTimer.stop();
+#if QT_CONFIG(filesystemwatcher)
+ for (int i = 0; i < d->toFetch.count(); ++i) {
+ const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node;
+ if (!node->hasInformation()) {
+ d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir,
+ QStringList(d->toFetch.at(i).file));
+ } else {
+ // qDebug("yah!, you saved a little gerbil soul");
+ }
+ }
+#endif
+ d->toFetch.clear();
+ }
+}
+
+/*!
+ Returns \c true if the model item \a index represents a directory;
+ otherwise returns \c false.
+*/
+bool QFileSystemModel::isDir(const QModelIndex &index) const
+{
+ // This function is for public usage only because it could create a file info
+ Q_D(const QFileSystemModel);
+ if (!index.isValid())
+ return true;
+ QFileSystemModelPrivate::QFileSystemNode *n = d->node(index);
+ if (n->hasInformation())
+ return n->isDir();
+ return fileInfo(index).isDir();
+}
+
+/*!
+ Returns the size in bytes of \a index. If the file does not exist, 0 is returned.
+ */
+qint64 QFileSystemModel::size(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ if (!index.isValid())
+ return 0;
+ return d->node(index)->size();
+}
+
+/*!
+ Returns the type of file \a index such as "Directory" or "JPEG file".
+ */
+QString QFileSystemModel::type(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ if (!index.isValid())
+ return QString();
+ return d->node(index)->type();
+}
+
+/*!
+ Returns the date and time when \a index was last modified.
+ */
+QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ if (!index.isValid())
+ return QDateTime();
+ return d->node(index)->lastModified();
+}
+
+/*!
+ \reimp
+*/
+QModelIndex QFileSystemModel::parent(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ if (!d->indexValid(index))
+ return QModelIndex();
+
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
+ Q_ASSERT(indexNode != nullptr);
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent;
+ if (parentNode == nullptr || parentNode == &d->root)
+ return QModelIndex();
+
+ // get the parent's row
+ QFileSystemModelPrivate::QFileSystemNode *grandParentNode = parentNode->parent;
+ Q_ASSERT(grandParentNode->children.contains(parentNode->fileName));
+ int visualRow = d->translateVisibleLocation(grandParentNode, grandParentNode->visibleLocation(grandParentNode->children.value(parentNode->fileName)->fileName));
+ if (visualRow == -1)
+ return QModelIndex();
+ return createIndex(visualRow, 0, parentNode);
+}
+
+/*
+ \internal
+
+ return the index for node
+*/
+QModelIndex QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode *node, int column) const
+{
+ Q_Q(const QFileSystemModel);
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = (node ? node->parent : nullptr);
+ if (node == &root || !parentNode)
+ return QModelIndex();
+
+ // get the parent's row
+ Q_ASSERT(node);
+ if (!node->isVisible)
+ return QModelIndex();
+
+ int visualRow = translateVisibleLocation(parentNode, parentNode->visibleLocation(node->fileName));
+ return q->createIndex(visualRow, column, const_cast<QFileSystemNode*>(node));
+}
+
+/*!
+ \reimp
+*/
+bool QFileSystemModel::hasChildren(const QModelIndex &parent) const
+{
+ Q_D(const QFileSystemModel);
+ if (parent.column() > 0)
+ return false;
+
+ if (!parent.isValid()) // drives
+ return true;
+
+ const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
+ Q_ASSERT(indexNode);
+ return (indexNode->isDir());
+}
+
+/*!
+ \reimp
+ */
+bool QFileSystemModel::canFetchMore(const QModelIndex &parent) const
+{
+ Q_D(const QFileSystemModel);
+ const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
+ return (!indexNode->populatedChildren);
+}
+
+/*!
+ \reimp
+ */
+void QFileSystemModel::fetchMore(const QModelIndex &parent)
+{
+ Q_D(QFileSystemModel);
+ if (!d->setRootPath)
+ return;
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent);
+ if (indexNode->populatedChildren)
+ return;
+ indexNode->populatedChildren = true;
+#if QT_CONFIG(filesystemwatcher)
+ d->fileInfoGatherer.list(filePath(parent));
+#endif
+}
+
+/*!
+ \reimp
+*/
+int QFileSystemModel::rowCount(const QModelIndex &parent) const
+{
+ Q_D(const QFileSystemModel);
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ return d->root.visibleChildren.count();
+
+ const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
+ return parentNode->visibleChildren.count();
+}
+
+/*!
+ \reimp
+*/
+int QFileSystemModel::columnCount(const QModelIndex &parent) const
+{
+ return (parent.column() > 0) ? 0 : QFileSystemModelPrivate::NumColumns;
+}
+
+/*!
+ Returns the data stored under the given \a role for the item "My Computer".
+
+ \sa Qt::ItemDataRole
+ */
+QVariant QFileSystemModel::myComputer(int role) const
+{
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(const QFileSystemModel);
+#endif
+ switch (role) {
+ case Qt::DisplayRole:
+ return QFileSystemModelPrivate::myComputer();
+#if QT_CONFIG(filesystemwatcher)
+ case Qt::DecorationRole:
+ return d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Computer);
+#endif
+ }
+ return QVariant();
+}
+
+/*!
+ \reimp
+*/
+QVariant QFileSystemModel::data(const QModelIndex &index, int role) const
+{
+ Q_D(const QFileSystemModel);
+ if (!index.isValid() || index.model() != this)
+ return QVariant();
+
+ switch (role) {
+ case Qt::EditRole:
+ 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);
+ default:
+ qWarning("data: invalid display value column %d", index.column());
+ break;
+ }
+ break;
+ case FilePathRole:
+ return filePath(index);
+ case FileNameRole:
+ return d->name(index);
+ case Qt::DecorationRole:
+ if (index.column() == 0) {
+ 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);
+ }
+#endif // filesystemwatcher
+ return icon;
+ }
+ break;
+ case Qt::TextAlignmentRole:
+ if (index.column() == 1)
+ return QVariant(Qt::AlignTrailing | Qt::AlignVCenter);
+ break;
+ case FilePermissions:
+ int p = permissions(index);
+ return p;
+ }
+
+ return QVariant();
+}
+
+/*!
+ \internal
+*/
+QString QFileSystemModelPrivate::size(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QString();
+ const QFileSystemNode *n = node(index);
+ if (n->isDir()) {
+#ifdef Q_OS_MAC
+ return QLatin1String("--");
+#else
+ return QLatin1String("");
+#endif
+ // Windows - ""
+ // OS X - "--"
+ // Konqueror - "4 KB"
+ // Nautilus - "9 items" (the number of children)
+ }
+ return size(n->size());
+}
+
+QString QFileSystemModelPrivate::size(qint64 bytes)
+{
+ return QLocale::system().formattedDataSize(bytes);
+}
+
+/*!
+ \internal
+*/
+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);
+#else
+ Q_UNUSED(index);
+ return QString();
+#endif
+}
+
+/*
+ \internal
+*/
+QString QFileSystemModelPrivate::type(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QString();
+ return node(index)->type();
+}
+
+/*!
+ \internal
+*/
+QString QFileSystemModelPrivate::name(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QString();
+ QFileSystemNode *dirNode = node(index);
+ if (
+#if QT_CONFIG(filesystemwatcher)
+ fileInfoGatherer.resolveSymlinks() &&
+#endif
+ !resolvedSymLinks.isEmpty() && dirNode->isSymLink(/* ignoreNtfsSymLinks = */ true)) {
+ QString fullPath = QDir::fromNativeSeparators(filePath(index));
+ return resolvedSymLinks.value(fullPath, dirNode->fileName);
+ }
+ return dirNode->fileName;
+}
+
+/*!
+ \internal
+*/
+QString QFileSystemModelPrivate::displayName(const QModelIndex &index) const
+{
+#if defined(Q_OS_WIN)
+ QFileSystemNode *dirNode = node(index);
+ if (!dirNode->volumeName.isEmpty())
+ return dirNode->volumeName;
+#endif
+ return name(index);
+}
+
+/*!
+ \internal
+*/
+QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QIcon();
+ return node(index)->icon();
+}
+
+/*!
+ \reimp
+*/
+bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, int role)
+{
+ Q_D(QFileSystemModel);
+ if (!idx.isValid()
+ || idx.column() != 0
+ || role != Qt::EditRole
+ || (flags(idx) & Qt::ItemIsEditable) == 0) {
+ return false;
+ }
+
+ QString newName = value.toString();
+ QString oldName = idx.data().toString();
+ if (newName == oldName)
+ return true;
+
+ const QString parentPath = filePath(parent(idx));
+
+ if (newName.isEmpty() || QDir::toNativeSeparators(newName).contains(QDir::separator()))
+ return false;
+
+#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
+ // QTBUG-65683: Remove file system watchers prior to renaming to prevent
+ // failure due to locked files on Windows.
+ const QStringList watchedPaths = d->unwatchPathsAt(idx);
+#endif // filesystemwatcher && Q_OS_WIN
+ if (!QDir(parentPath).rename(oldName, newName)) {
+#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
+ d->watchPaths(watchedPaths);
+#endif
+ return false;
+ } else {
+ /*
+ *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
+
+ - if this get any more complicated think of changing it to just
+ use layoutChanged
+ */
+
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(idx);
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent;
+ int visibleLocation = parentNode->visibleLocation(parentNode->children.value(indexNode->fileName)->fileName);
+
+ parentNode->visibleChildren.removeAt(visibleLocation);
+ QScopedPointer<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)));
+#endif
+ nodeToRename->isVisible = true;
+ parentNode->children[newName] = nodeToRename.take();
+ parentNode->visibleChildren.insert(visibleLocation, newName);
+
+ d->delayedSort();
+ emit fileRenamed(parentPath, oldName, newName);
+ }
+ return true;
+}
+
+/*!
+ \reimp
+*/
+QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ switch (role) {
+ case Qt::DecorationRole:
+ if (section == 0) {
+ // ### TODO oh man this is ugly and doesn't even work all the way!
+ // it is still 2 pixels off
+ QImage pixmap(16, 1, QImage::Format_ARGB32_Premultiplied);
+ pixmap.fill(Qt::transparent);
+ return pixmap;
+ }
+ break;
+ case Qt::TextAlignmentRole:
+ return Qt::AlignLeft;
+ }
+
+ if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
+ return QAbstractItemModel::headerData(section, orientation, role);
+
+ QString returnValue;
+ switch (section) {
+ case 0: returnValue = tr("Name");
+ break;
+ case 1: returnValue = tr("Size");
+ break;
+ case 2: returnValue =
+#ifdef Q_OS_MAC
+ tr("Kind", "Match OS X Finder");
+#else
+ tr("Type", "All other platforms");
+#endif
+ break;
+ // Windows - Type
+ // OS X - Kind
+ // Konqueror - File Type
+ // Nautilus - Type
+ case 3: returnValue = tr("Date Modified");
+ break;
+ default: return QVariant();
+ }
+ return returnValue;
+}
+
+/*!
+ \reimp
+*/
+Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+ if (!index.isValid())
+ return flags;
+
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index);
+ if (d->nameFilterDisables && !d->passNameFilters(indexNode)) {
+ flags &= ~Qt::ItemIsEnabled;
+ // ### TODO you shouldn't be able to set this as the current item, task 119433
+ return flags;
+ }
+
+ flags |= Qt::ItemIsDragEnabled;
+ 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;
+}
+
+/*!
+ \internal
+*/
+void QFileSystemModelPrivate::_q_performDelayedSort()
+{
+ Q_Q(QFileSystemModel);
+ q->sort(sortColumn, sortOrder);
+}
+
+
+/*
+ \internal
+ Helper functor used by sort()
+*/
+class QFileSystemModelSorter
+{
+public:
+ inline QFileSystemModelSorter(int column) : sortColumn(column)
+ {
+ naturalCompare.setNumericMode(true);
+ naturalCompare.setCaseSensitivity(Qt::CaseInsensitive);
+ }
+
+ bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l,
+ const QFileSystemModelPrivate::QFileSystemNode *r) const
+ {
+ switch (sortColumn) {
+ case 0: {
+#ifndef Q_OS_MAC
+ // place directories before files
+ bool left = l->isDir();
+ bool right = r->isDir();
+ if (left ^ right)
+ return left;
+#endif
+ return naturalCompare.compare(l->fileName, r->fileName) < 0;
+ }
+ case 1:
+ {
+ // Directories go first
+ bool left = l->isDir();
+ bool right = r->isDir();
+ if (left ^ right)
+ return left;
+
+ qint64 sizeDifference = l->size() - r->size();
+ if (sizeDifference == 0)
+ return naturalCompare.compare(l->fileName, r->fileName) < 0;
+
+ return sizeDifference < 0;
+ }
+ case 2:
+ {
+ int compare = naturalCompare.compare(l->type(), r->type());
+ if (compare == 0)
+ return naturalCompare.compare(l->fileName, r->fileName) < 0;
+
+ return compare < 0;
+ }
+ case 3:
+ {
+ if (l->lastModified() == r->lastModified())
+ return naturalCompare.compare(l->fileName, r->fileName) < 0;
+
+ return l->lastModified() < r->lastModified();
+ }
+ }
+ Q_ASSERT(false);
+ return false;
+ }
+
+ bool operator()(const QFileSystemModelPrivate::QFileSystemNode *l,
+ const QFileSystemModelPrivate::QFileSystemNode *r) const
+ {
+ return compareNodes(l, r);
+ }
+
+
+private:
+ QCollator naturalCompare;
+ int sortColumn;
+};
+
+/*
+ \internal
+
+ Sort all of the children of parent
+*/
+void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent)
+{
+ Q_Q(QFileSystemModel);
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent);
+ if (indexNode->children.count() == 0)
+ return;
+
+ QList<QFileSystemModelPrivate::QFileSystemNode *> values;
+
+ for (auto iterator = indexNode->children.constBegin(), cend = indexNode->children.constEnd(); iterator != cend; ++iterator) {
+ if (filtersAcceptsNode(iterator.value())) {
+ values.append(iterator.value());
+ } else {
+ iterator.value()->isVisible = false;
+ }
+ }
+ QFileSystemModelSorter ms(column);
+ std::sort(values.begin(), values.end(), ms);
+ // First update the new visible list
+ 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;
+ }
+
+ if (!disableRecursiveSort) {
+ for (int i = 0; i < q->rowCount(parent); ++i) {
+ const QModelIndex childIndex = q->index(i, 0, parent);
+ QFileSystemModelPrivate::QFileSystemNode *indexNode = node(childIndex);
+ //Only do a recursive sort on visible nodes
+ if (indexNode->isVisible)
+ sortChildren(column, childIndex);
+ }
+ }
+}
+
+/*!
+ \reimp
+*/
+void QFileSystemModel::sort(int column, Qt::SortOrder order)
+{
+ Q_D(QFileSystemModel);
+ if (d->sortOrder == order && d->sortColumn == column && !d->forceSort)
+ return;
+
+ 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);
+ QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(oldNode), oldNode.column());
+ oldNodes.append(pair);
+ }
+
+ if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) {
+ //we sort only from where we are, don't need to sort all the model
+ d->sortChildren(column, index(rootPath()));
+ d->sortColumn = column;
+ d->forceSort = false;
+ }
+ 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));
+ }
+ changePersistentIndexList(oldList, newList);
+ emit layoutChanged();
+}
+
+/*!
+ Returns a list of MIME types that can be used to describe a list of items
+ in the model.
+*/
+QStringList QFileSystemModel::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, \nullptr is returned rather than a
+ serialized empty list.
+*/
+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)
+ 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. Returns true if the operation was
+ successful.
+
+ \sa supportedDropActions()
+*/
+bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
+ int row, int column, const QModelIndex &parent)
+{
+ Q_UNUSED(row);
+ Q_UNUSED(column);
+ if (!parent.isValid() || isReadOnly())
+ return false;
+
+ bool success = true;
+ QString to = filePath(parent) + QDir::separator();
+
+ 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();
+ success = QFile::rename(path, to + QFileInfo(path).fileName()) && success;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ return success;
+}
+
+/*!
+ \reimp
+*/
+Qt::DropActions QFileSystemModel::supportedDropActions() const
+{
+ return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
+}
+
+/*!
+ \reimp
+*/
+QHash<int, QByteArray> QFileSystemModel::roleNames() const
+{
+ auto ret = QAbstractItemModel::roleNames();
+ ret.insert(QFileSystemModel::FileIconRole,
+ QByteArrayLiteral("fileIcon")); // == Qt::decoration
+ ret.insert(QFileSystemModel::FilePathRole, QByteArrayLiteral("filePath"));
+ ret.insert(QFileSystemModel::FileNameRole, QByteArrayLiteral("fileName"));
+ ret.insert(QFileSystemModel::FilePermissions, QByteArrayLiteral("filePermissions"));
+ return ret;
+}
+
+/*!
+ \enum QFileSystemModel::Option
+ \since 5.14
+
+ \value DontWatchForChanges Do not add file watchers to the paths.
+ This reduces overhead when using the model for simple tasks
+ like line edit completion.
+
+ \value DontResolveSymlinks Don't resolve symlinks in the file
+ system model. By default, symlinks are resolved.
+
+ \value DontUseCustomDirectoryIcons Always use the default directory icon.
+ Some platforms allow the user to set a different icon. Custom icon lookup
+ causes a big performance impact over network or removable drives.
+ This sets the QFileIconProvider::DontUseCustomDirectoryIcons
+ option in the icon provider accordingly.
+
+ \sa resolveSymlinks
+*/
+
+/*!
+ \since 5.14
+ Sets the given \a option to be enabled if \a on is true; otherwise,
+ clears the given \a option.
+
+ Options should be set before changing properties.
+
+ \sa options, testOption()
+*/
+void QFileSystemModel::setOption(Option option, bool on)
+{
+ QFileSystemModel::Options previousOptions = options();
+ setOptions(previousOptions.setFlag(option, on));
+}
+
+/*!
+ \since 5.14
+
+ Returns \c true if the given \a option is enabled; otherwise, returns
+ false.
+
+ \sa options, setOption()
+*/
+bool QFileSystemModel::testOption(Option option) const
+{
+ return options().testFlag(option);
+}
+
+/*!
+ \property QFileSystemModel::options
+ \brief the various options that affect the model
+ \since 5.14
+
+ By default, all options are disabled.
+
+ Options should be set before changing properties.
+
+ \sa setOption(), testOption()
+*/
+void QFileSystemModel::setOptions(Options options)
+{
+ const Options changed = (options ^ QFileSystemModel::options());
+
+ if (changed.testFlag(DontResolveSymlinks))
+ setResolveSymlinks(!options.testFlag(DontResolveSymlinks));
+
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(QFileSystemModel);
+ if (changed.testFlag(DontWatchForChanges))
+ d->fileInfoGatherer.setWatching(!options.testFlag(DontWatchForChanges));
+#endif
+
+ if (changed.testFlag(DontUseCustomDirectoryIcons)) {
+ if (auto provider = iconProvider()) {
+ QAbstractFileIconProvider::Options providerOptions = provider->options();
+ providerOptions.setFlag(QAbstractFileIconProvider::DontUseCustomDirectoryIcons,
+ options.testFlag(QFileSystemModel::DontUseCustomDirectoryIcons));
+ provider->setOptions(providerOptions);
+ } else {
+ qWarning("Setting QFileSystemModel::DontUseCustomDirectoryIcons has no effect when no provider is used");
+ }
+ }
+}
+
+QFileSystemModel::Options QFileSystemModel::options() const
+{
+ QFileSystemModel::Options result;
+ result.setFlag(DontResolveSymlinks, !resolveSymlinks());
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(const QFileSystemModel);
+ result.setFlag(DontWatchForChanges, !d->fileInfoGatherer.isWatching());
+#else
+ result.setFlag(DontWatchForChanges);
+#endif
+ if (auto provider = iconProvider()) {
+ result.setFlag(DontUseCustomDirectoryIcons,
+ provider->options().testFlag(QAbstractFileIconProvider::DontUseCustomDirectoryIcons));
+ }
+ return result;
+}
+
+/*!
+ Returns the path of the item stored in the model under the
+ \a index given.
+*/
+QString QFileSystemModel::filePath(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ QString fullPath = d->filePath(index);
+ QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
+ if (dirNode->isSymLink()
+#if QT_CONFIG(filesystemwatcher)
+ && d->fileInfoGatherer.resolveSymlinks()
+#endif
+ && d->resolvedSymLinks.contains(fullPath)
+ && dirNode->isDir()) {
+ QFileInfo fullPathInfo(dirNode->fileInfo());
+ if (!dirNode->hasInformation())
+ fullPathInfo = QFileInfo(fullPath);
+ QString canonicalPath = fullPathInfo.canonicalFilePath();
+ auto *canonicalNode = d->node(fullPathInfo.canonicalFilePath(), false);
+ QFileInfo resolvedInfo = canonicalNode->fileInfo();
+ if (!canonicalNode->hasInformation())
+ resolvedInfo = QFileInfo(canonicalPath);
+ if (resolvedInfo.exists())
+ return resolvedInfo.filePath();
+ }
+ return fullPath;
+}
+
+QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const
+{
+ Q_Q(const QFileSystemModel);
+ Q_UNUSED(q);
+ if (!index.isValid())
+ return QString();
+ Q_ASSERT(index.model() == q);
+
+ QStringList path;
+ QModelIndex idx = index;
+ while (idx.isValid()) {
+ QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx);
+ if (dirNode)
+ path.prepend(dirNode->fileName);
+ idx = idx.parent();
+ }
+ QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator()));
+#if !defined(Q_OS_WIN)
+ if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/'))
+ fullPath = fullPath.mid(1);
+#else
+ if (fullPath.length() == 2 && fullPath.endsWith(QLatin1Char(':')))
+ fullPath.append(QLatin1Char('/'));
+#endif
+ return fullPath;
+}
+
+/*!
+ Create a directory with the \a name in the \a parent model index.
+*/
+QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name)
+{
+ Q_D(QFileSystemModel);
+ if (!parent.isValid())
+ return parent;
+
+ QDir dir(filePath(parent));
+ if (!dir.mkdir(name))
+ return QModelIndex();
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent);
+ d->addNode(parentNode, name, QFileInfo());
+ 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)));
+#endif
+ d->addVisibleFiles(parentNode, QStringList(name));
+ return d->index(node);
+}
+
+/*!
+ Returns the complete OR-ed together combination of QFile::Permission for the \a index.
+ */
+QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const
+{
+ Q_D(const QFileSystemModel);
+ return d->node(index)->permissions();
+}
+
+/*!
+ Sets the directory that is being watched by the model to \a newPath by
+ installing a \l{QFileSystemWatcher}{file system watcher} on it. Any
+ changes to files and directories within this directory will be
+ reflected in the model.
+
+ If the path is changed, the rootPathChanged() signal will be emitted.
+
+ \note This function does not change the structure of the model or
+ modify the data available to views. In other words, the "root" of
+ the model is \e not changed to include only files and directories
+ within the directory specified by \a newPath in the file system.
+ */
+QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
+{
+ Q_D(QFileSystemModel);
+#ifdef Q_OS_WIN
+#ifdef Q_OS_WIN32
+ QString longNewPath = qt_GetLongPathName(newPath);
+#else
+ QString longNewPath = QDir::fromNativeSeparators(newPath);
+#endif
+#else
+ QString longNewPath = newPath;
+#endif
+ //we remove .. and . from the given path if exist
+ if (!newPath.isEmpty())
+ longNewPath = QDir::cleanPath(longNewPath);
+
+ d->setRootPath = true;
+
+ //user don't ask for the root path ("") but the conversion failed
+ if (!newPath.isEmpty() && longNewPath.isEmpty())
+ return d->index(rootPath());
+
+ if (d->rootDir.path() == longNewPath)
+ return d->index(rootPath());
+
+ auto node = d->node(longNewPath);
+ QFileInfo newPathInfo;
+ if (node && node->hasInformation())
+ newPathInfo = node->fileInfo();
+ else
+ newPathInfo = QFileInfo(longNewPath);
+
+ bool showDrives = (longNewPath.isEmpty() || longNewPath == QFileSystemModelPrivate::myComputer());
+ if (!showDrives && !newPathInfo.exists())
+ return d->index(rootPath());
+
+ //We remove the watcher on the previous path
+ if (!rootPath().isEmpty() && rootPath() != QLatin1String(".")) {
+ //This remove the watcher for the old rootPath
+#if QT_CONFIG(filesystemwatcher)
+ 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
+ //But it doesn't re-fetch everything
+ d->node(rootPath())->populatedChildren = false;
+ }
+
+ // We have a new valid root path
+ d->rootDir = QDir(longNewPath);
+ QModelIndex newRootIndex;
+ if (showDrives) {
+ // otherwise dir will become '.'
+ d->rootDir.setPath(QLatin1String(""));
+ } else {
+ newRootIndex = d->index(d->rootDir.path());
+ }
+ fetchMore(newRootIndex);
+ emit rootPathChanged(longNewPath);
+ d->forceSort = true;
+ d->delayedSort();
+ return newRootIndex;
+}
+
+/*!
+ The currently set root path
+
+ \sa rootDirectory()
+*/
+QString QFileSystemModel::rootPath() const
+{
+ Q_D(const QFileSystemModel);
+ return d->rootDir.path();
+}
+
+/*!
+ The currently set directory
+
+ \sa rootPath()
+*/
+QDir QFileSystemModel::rootDirectory() const
+{
+ Q_D(const QFileSystemModel);
+ QDir dir(d->rootDir);
+ dir.setNameFilters(nameFilters());
+ dir.setFilter(filter());
+ return dir;
+}
+
+/*!
+ Sets the \a provider of file icons for the directory model.
+*/
+void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider)
+{
+ Q_D(QFileSystemModel);
+#if QT_CONFIG(filesystemwatcher)
+ d->fileInfoGatherer.setIconProvider(provider);
+#endif
+ d->root.updateIcon(provider, QString());
+}
+
+/*!
+ Returns the file icon provider for this directory model.
+*/
+QAbstractFileIconProvider *QFileSystemModel::iconProvider() const
+{
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(const QFileSystemModel);
+ return d->fileInfoGatherer.iconProvider();
+#else
+ return 0;
+#endif
+}
+
+/*!
+ 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 QFileSystemModel won't be able to read the directory structure.
+
+ \sa QDir::Filters
+*/
+void QFileSystemModel::setFilter(QDir::Filters filters)
+{
+ Q_D(QFileSystemModel);
+ if (d->filters == filters)
+ return;
+ d->filters = filters;
+ // CaseSensitivity might have changed
+ setNameFilters(nameFilters());
+ d->forceSort = true;
+ d->delayedSort();
+}
+
+/*!
+ Returns the filter specified for the directory model.
+
+ If a filter has not been set, the default filter is QDir::AllEntries |
+ QDir::NoDotAndDotDot | QDir::AllDirs.
+
+ \sa QDir::Filters
+*/
+QDir::Filters QFileSystemModel::filter() const
+{
+ Q_D(const QFileSystemModel);
+ return d->filters;
+}
+
+/*!
+ \property QFileSystemModel::resolveSymlinks
+ \brief Whether the directory model should resolve symbolic links
+
+ This is only relevant on Windows.
+
+ By default, this property is \c true.
+
+ \sa QFileSystemModel::Options
+*/
+void QFileSystemModel::setResolveSymlinks(bool enable)
+{
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(QFileSystemModel);
+ d->fileInfoGatherer.setResolveSymlinks(enable);
+#else
+ Q_UNUSED(enable);
+#endif
+}
+
+bool QFileSystemModel::resolveSymlinks() const
+{
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(const QFileSystemModel);
+ return d->fileInfoGatherer.resolveSymlinks();
+#else
+ return false;
+#endif
+}
+
+/*!
+ \property QFileSystemModel::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 \c true by default
+*/
+void QFileSystemModel::setReadOnly(bool enable)
+{
+ Q_D(QFileSystemModel);
+ d->readOnly = enable;
+}
+
+bool QFileSystemModel::isReadOnly() const
+{
+ Q_D(const QFileSystemModel);
+ return d->readOnly;
+}
+
+/*!
+ \property QFileSystemModel::nameFilterDisables
+ \brief Whether files that don't pass the name filter are hidden or disabled
+
+ This property is \c true by default
+*/
+void QFileSystemModel::setNameFilterDisables(bool enable)
+{
+ Q_D(QFileSystemModel);
+ if (d->nameFilterDisables == enable)
+ return;
+ d->nameFilterDisables = enable;
+ d->forceSort = true;
+ d->delayedSort();
+}
+
+bool QFileSystemModel::nameFilterDisables() const
+{
+ Q_D(const QFileSystemModel);
+ return d->nameFilterDisables;
+}
+
+/*!
+ Sets the name \a filters to apply against the existing files.
+*/
+void QFileSystemModel::setNameFilters(const QStringList &filters)
+{
+ // Prep the regexp's ahead of time
+#if QT_CONFIG(regularexpression)
+ Q_D(QFileSystemModel);
+
+ if (!d->bypassFilters.isEmpty()) {
+ // update the bypass filter to only bypass the stuff that must be kept around
+ d->bypassFilters.clear();
+ // We guarantee that rootPath will stick around
+ QPersistentModelIndex root(index(rootPath()));
+ const QModelIndexList persistentList = persistentIndexList();
+ for (const auto &persistentIndex : persistentList) {
+ QFileSystemModelPrivate::QFileSystemNode *node = d->node(persistentIndex);
+ while (node) {
+ if (d->bypassFilters.contains(node))
+ break;
+ if (node->isDir())
+ d->bypassFilters[node] = true;
+ node = node->parent;
+ }
+ }
+ }
+
+ d->nameFilters = filters;
+ d->forceSort = true;
+ d->delayedSort();
+#else
+ Q_UNUSED(filters);
+#endif
+}
+
+/*!
+ Returns a list of filters applied to the names in the model.
+*/
+QStringList QFileSystemModel::nameFilters() const
+{
+#if QT_CONFIG(regularexpression)
+ Q_D(const QFileSystemModel);
+ return d->nameFilters;
+#else
+ return QStringList();
+#endif
+}
+
+/*!
+ \reimp
+*/
+bool QFileSystemModel::event(QEvent *event)
+{
+#if QT_CONFIG(filesystemwatcher)
+ Q_D(QFileSystemModel);
+ if (event->type() == QEvent::LanguageChange) {
+ d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString());
+ return true;
+ }
+#endif
+ return QAbstractItemModel::event(event);
+}
+
+bool QFileSystemModel::rmdir(const QModelIndex &aindex)
+{
+ QString path = filePath(aindex);
+ const bool success = QDir().rmdir(path);
+#if QT_CONFIG(filesystemwatcher)
+ if (success) {
+ QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func());
+ d->fileInfoGatherer.removePath(path);
+ }
+#endif
+ return success;
+}
+
+/*!
+ \internal
+
+ 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)
+{
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
+ if (parentNode->children.count() == 0)
+ return;
+ QStringList toRemove;
+ QStringList newFiles = files;
+ std::sort(newFiles.begin(), newFiles.end());
+ for (auto i = parentNode->children.constBegin(), cend = parentNode->children.constEnd(); i != cend; ++i) {
+ QStringList::iterator iterator = std::lower_bound(newFiles.begin(), newFiles.end(), i.value()->fileName);
+ if ((iterator == newFiles.end()) || (i.value()->fileName < *iterator))
+ toRemove.append(i.value()->fileName);
+ }
+ for (int i = 0 ; i < toRemove.count() ; ++i )
+ removeNode(parentNode, toRemove[i]);
+}
+
+#if defined(Q_OS_WIN)
+static QString volumeName(const QString &path)
+{
+ IShellItem *item = nullptr;
+ const QString native = QDir::toNativeSeparators(path);
+ HRESULT hr = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
+ nullptr, IID_IShellItem,
+ reinterpret_cast<void **>(&item));
+ if (FAILED(hr))
+ return QString();
+ LPWSTR name = nullptr;
+ hr = item->GetDisplayName(SIGDN_NORMALDISPLAY, &name);
+ if (FAILED(hr))
+ return QString();
+ QString result = QString::fromWCharArray(name);
+ CoTaskMemFree(name);
+ item->Release();
+ return result;
+}
+#endif // Q_OS_WIN
+
+/*!
+ \internal
+
+ Adds a new file to the children of parentNode
+
+ *WARNING* this will change the count of children
+*/
+QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
+{
+ // In the common case, itemLocation == count() so check there first
+ QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
+#if QT_CONFIG(filesystemwatcher)
+ node->populate(info);
+#else
+ Q_UNUSED(info);
+#endif
+#if defined(Q_OS_WIN)
+ //The parentNode is "" so we are listing the drives
+ if (parentNode->fileName.isEmpty())
+ node->volumeName = volumeName(fileName);
+#endif
+ Q_ASSERT(!parentNode->children.contains(fileName));
+ parentNode->children.insert(fileName, node);
+ return node;
+}
+
+/*!
+ \internal
+
+ File at parentNode->children(itemLocation) has been removed, remove from the lists
+ and emit signals if necessary
+
+ *WARNING* this will change the count of children and could change visibleChildren
+ */
+void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name)
+{
+ Q_Q(QFileSystemModel);
+ QModelIndex parent = index(parentNode);
+ bool indexHidden = isHiddenByFilter(parentNode, parent);
+
+ int vLocation = parentNode->visibleLocation(name);
+ if (vLocation >= 0 && !indexHidden)
+ q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
+ translateVisibleLocation(parentNode, vLocation));
+ QFileSystemNode * node = parentNode->children.take(name);
+ delete node;
+ // cleanup sort files after removing rather then re-sorting which is O(n)
+ if (vLocation >= 0)
+ parentNode->visibleChildren.removeAt(vLocation);
+ if (vLocation >= 0 && !indexHidden)
+ q->endRemoveRows();
+}
+
+/*!
+ \internal
+
+ File at parentNode->children(itemLocation) was not visible before, but now should be
+ and emit signals if necessary.
+
+ *WARNING* this will change the visible count
+ */
+void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles)
+{
+ Q_Q(QFileSystemModel);
+ QModelIndex parent = index(parentNode);
+ bool indexHidden = isHiddenByFilter(parentNode, parent);
+ if (!indexHidden) {
+ q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1);
+ }
+
+ if (parentNode->dirtyChildrenIndex == -1)
+ parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count();
+
+ for (const auto &newFile : newFiles) {
+ parentNode->visibleChildren.append(newFile);
+ parentNode->children.value(newFile)->isVisible = true;
+ }
+ if (!indexHidden)
+ q->endInsertRows();
+}
+
+/*!
+ \internal
+
+ File was visible before, but now should NOT be
+
+ *WARNING* this will change the visible count
+ */
+void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation)
+{
+ Q_Q(QFileSystemModel);
+ if (vLocation == -1)
+ return;
+ QModelIndex parent = index(parentNode);
+ bool indexHidden = isHiddenByFilter(parentNode, parent);
+ if (!indexHidden)
+ q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation),
+ translateVisibleLocation(parentNode, vLocation));
+ parentNode->children.value(parentNode->visibleChildren.at(vLocation))->isVisible = false;
+ parentNode->visibleChildren.removeAt(vLocation);
+ if (!indexHidden)
+ q->endRemoveRows();
+}
+
+/*!
+ \internal
+
+ 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)
+{
+#if QT_CONFIG(filesystemwatcher)
+ Q_Q(QFileSystemModel);
+ QList<QString> rowsToUpdate;
+ QStringList newFiles;
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false);
+ QModelIndex parentIndex = index(parentNode);
+ for (const auto &update : updates) {
+ QString fileName = update.first;
+ Q_ASSERT(!fileName.isEmpty());
+ QExtendedInformation info = fileInfoGatherer.getInfo(update.second);
+ bool previouslyHere = parentNode->children.contains(fileName);
+ if (!previouslyHere) {
+ addNode(parentNode, fileName, info.fileInfo());
+ }
+ QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName);
+ bool isCaseSensitive = parentNode->caseSensitive();
+ if (isCaseSensitive) {
+ if (node->fileName != fileName)
+ continue;
+ } else {
+ if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0)
+ continue;
+ }
+ if (isCaseSensitive) {
+ Q_ASSERT(node->fileName == fileName);
+ } else {
+ node->fileName = fileName;
+ }
+
+ if (*node != info ) {
+ node->populate(info);
+ bypassFilters.remove(node);
+ // brand new information.
+ if (filtersAcceptsNode(node)) {
+ if (!node->isVisible) {
+ newFiles.append(fileName);
+ } else {
+ rowsToUpdate.append(fileName);
+ }
+ } else {
+ if (node->isVisible) {
+ int visibleLocation = parentNode->visibleLocation(fileName);
+ removeVisibleFile(parentNode, visibleLocation);
+ } else {
+ // The file is not visible, don't do anything
+ }
+ }
+ }
+ }
+
+ // bundle up all of the changed signals into as few as possible.
+ std::sort(rowsToUpdate.begin(), rowsToUpdate.end());
+ QString min;
+ QString max;
+ for (const QString &value : qAsConst(rowsToUpdate)) {
+ //##TODO is there a way to bundle signals with QString as the content of the list?
+ /*if (min.isEmpty()) {
+ min = value;
+ if (i != rowsToUpdate.count() - 1)
+ continue;
+ }
+ if (i != rowsToUpdate.count() - 1) {
+ if ((value == min + 1 && max.isEmpty()) || value == max + 1) {
+ max = value;
+ continue;
+ }
+ }*/
+ max = value;
+ min = value;
+ int visibleMin = parentNode->visibleLocation(min);
+ int visibleMax = parentNode->visibleLocation(max);
+ if (visibleMin >= 0
+ && visibleMin < parentNode->visibleChildren.count()
+ && 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);
+ }
+
+ /*min = QString();
+ max = QString();*/
+ }
+
+ if (newFiles.count() > 0) {
+ addVisibleFiles(parentNode, newFiles);
+ }
+
+ if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) {
+ forceSort = true;
+ delayedSort();
+ }
+#else
+ Q_UNUSED(path);
+ Q_UNUSED(updates);
+#endif // filesystemwatcher
+}
+
+/*!
+ \internal
+*/
+void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
+{
+ resolvedSymLinks[fileName] = resolvedName;
+}
+
+#if QT_CONFIG(filesystemwatcher) && defined(Q_OS_WIN)
+// Remove file system watchers at/below the index and return a list of previously
+// watched files. This should be called prior to operations like rename/remove
+// which might fail due to watchers on platforms like Windows. The watchers
+// should be restored on failure.
+QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index)
+{
+ const QFileSystemModelPrivate::QFileSystemNode *indexNode = node(index);
+ if (indexNode == nullptr)
+ return QStringList();
+ const Qt::CaseSensitivity caseSensitivity = indexNode->caseSensitive()
+ ? Qt::CaseSensitive : Qt::CaseInsensitive;
+ const QString path = indexNode->fileInfo().absoluteFilePath();
+
+ QStringList result;
+ const auto filter = [path, caseSensitivity] (const QString &watchedPath)
+ {
+ const int pathSize = path.size();
+ if (pathSize == watchedPath.size()) {
+ return path.compare(watchedPath, caseSensitivity) == 0;
+ } else if (watchedPath.size() > pathSize) {
+ return watchedPath.at(pathSize) == QLatin1Char('/')
+ && watchedPath.startsWith(path, caseSensitivity);
+ }
+ return false;
+ };
+
+ const QStringList &watchedFiles = fileInfoGatherer.watchedFiles();
+ std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(),
+ std::back_inserter(result), filter);
+
+ const QStringList &watchedDirectories = fileInfoGatherer.watchedDirectories();
+ std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(),
+ std::back_inserter(result), filter);
+
+ fileInfoGatherer.unwatchPaths(result);
+ return result;
+}
+#endif // filesystemwatcher && Q_OS_WIN
+
+/*!
+ \internal
+*/
+void QFileSystemModelPrivate::init()
+{
+ Q_Q(QFileSystemModel);
+
+ delayedSortTimer.setSingleShot(true);
+
+ qRegisterMetaType<QList<QPair<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)));
+#endif // filesystemwatcher
+ q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
+}
+
+/*!
+ \internal
+
+ Returns \c false if node doesn't pass the filters otherwise true
+
+ QDir::Modified is not supported
+ QDir::Drives is not supported
+*/
+bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
+{
+ // always accept drives
+ if (node->parent == &root || bypassFilters.contains(node))
+ return true;
+
+ // If we don't know anything yet don't accept it
+ if (!node->hasInformation())
+ return false;
+
+ 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));
+ const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable));
+ const bool hideHidden = !(filters & QDir::Hidden);
+ const bool hideSystem = !(filters & QDir::System);
+ const bool hideSymlinks = (filters & QDir::NoSymLinks);
+ const bool hideDot = (filters & QDir::NoDot);
+ 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(".."));
+ if ( (hideHidden && !(isDot || isDotDot) && node->isHidden())
+ || (hideSystem && node->isSystem())
+ || (hideDirs && node->isDir())
+ || (hideFiles && node->isFile())
+ || (hideSymlinks && node->isSymLink())
+ || (hideReadable && node->isReadable())
+ || (hideWritable && node->isWritable())
+ || (hideExecutable && node->isExecutable())
+ || (hideDot && isDot)
+ || (hideDotDot && isDotDot))
+ return false;
+
+ return nameFilterDisables || passNameFilters(node);
+}
+
+/*
+ \internal
+
+ Returns \c true if node passes the name filters and should be visible.
+ */
+bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const
+{
+#if QT_CONFIG(regularexpression)
+ if (nameFilters.isEmpty())
+ return true;
+
+ // 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;
+ }
+#else
+ Q_UNUSED(node);
+#endif
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qfilesystemmodel.cpp"
diff --git a/src/gui/itemmodels/qfilesystemmodel.h b/src/gui/itemmodels/qfilesystemmodel.h
new file mode 100644
index 0000000000..a79c4c14df
--- /dev/null
+++ b/src/gui/itemmodels/qfilesystemmodel.h
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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$
+**
+****************************************************************************/
+
+#ifndef QFILESYSTEMMODEL_H
+#define QFILESYSTEMMODEL_H
+
+#include <QtGui/qtguiglobal.h>
+#include <QtCore/qabstractitemmodel.h>
+#include <QtCore/qpair.h>
+#include <QtCore/qdir.h>
+#include <QtGui/qicon.h>
+#include <QtCore/qdiriterator.h>
+
+QT_REQUIRE_CONFIG(filesystemmodel);
+
+QT_BEGIN_NAMESPACE
+
+class ExtendedInformation;
+class QFileSystemModelPrivate;
+class QAbstractFileIconProvider;
+
+class Q_GUI_EXPORT QFileSystemModel : public QAbstractItemModel
+{
+ Q_OBJECT
+ Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks)
+ Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly)
+ Q_PROPERTY(bool nameFilterDisables READ nameFilterDisables WRITE setNameFilterDisables)
+ Q_PROPERTY(Options options READ options WRITE setOptions)
+
+Q_SIGNALS:
+ void rootPathChanged(const QString &newPath);
+ void fileRenamed(const QString &path, const QString &oldName, const QString &newName);
+ void directoryLoaded(const QString &path);
+
+public:
+ enum Roles {
+ FileIconRole = Qt::DecorationRole,
+ FilePathRole = Qt::UserRole + 1,
+ FileNameRole = Qt::UserRole + 2,
+ FilePermissions = Qt::UserRole + 3
+ };
+
+ enum Option
+ {
+ DontWatchForChanges = 0x00000001,
+ DontResolveSymlinks = 0x00000002,
+ DontUseCustomDirectoryIcons = 0x00000004
+ };
+ Q_ENUM(Option)
+ Q_DECLARE_FLAGS(Options, Option)
+
+ explicit QFileSystemModel(QObject *parent = nullptr);
+ ~QFileSystemModel();
+
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex index(const QString &path, int column = 0) const;
+ QModelIndex parent(const QModelIndex &child) const override;
+ using QObject::parent;
+ QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
+ bool canFetchMore(const QModelIndex &parent) const override;
+ void fetchMore(const QModelIndex &parent) override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ QVariant myComputer(int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
+
+ QStringList mimeTypes() const override;
+ QMimeData *mimeData(const QModelIndexList &indexes) const override;
+ bool dropMimeData(const QMimeData *data, Qt::DropAction action,
+ int row, int column, const QModelIndex &parent) override;
+ Qt::DropActions supportedDropActions() const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ // QFileSystemModel specific API
+ QModelIndex setRootPath(const QString &path);
+ QString rootPath() const;
+ QDir rootDirectory() const;
+
+ void setIconProvider(QAbstractFileIconProvider *provider);
+ QAbstractFileIconProvider *iconProvider() const;
+
+ void setFilter(QDir::Filters filters);
+ QDir::Filters filter() const;
+
+ void setResolveSymlinks(bool enable);
+ bool resolveSymlinks() const;
+
+ void setReadOnly(bool enable);
+ bool isReadOnly() const;
+
+ void setNameFilterDisables(bool enable);
+ bool nameFilterDisables() const;
+
+ void setNameFilters(const QStringList &filters);
+ QStringList nameFilters() const;
+
+ void setOption(Option option, bool on = true);
+ bool testOption(Option option) const;
+ void setOptions(Options options);
+ Options options() const;
+
+ QString filePath(const QModelIndex &index) const;
+ bool isDir(const QModelIndex &index) const;
+ qint64 size(const QModelIndex &index) const;
+ QString type(const QModelIndex &index) const;
+ QDateTime lastModified(const QModelIndex &index) const;
+
+ QModelIndex mkdir(const QModelIndex &parent, const QString &name);
+ bool rmdir(const QModelIndex &index);
+ inline QString fileName(const QModelIndex &index) const;
+ inline QIcon fileIcon(const QModelIndex &index) const;
+ QFile::Permissions permissions(const QModelIndex &index) const;
+ QFileInfo fileInfo(const QModelIndex &index) const;
+ bool remove(const QModelIndex &index);
+
+protected:
+ QFileSystemModel(QFileSystemModelPrivate &, QObject *parent = nullptr);
+ void timerEvent(QTimerEvent *event) override;
+ bool event(QEvent *event) override;
+
+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;
+};
+
+inline QString QFileSystemModel::fileName(const QModelIndex &aindex) const
+{ return aindex.data(Qt::DisplayRole).toString(); }
+inline QIcon QFileSystemModel::fileIcon(const QModelIndex &aindex) const
+{ return qvariant_cast<QIcon>(aindex.data(Qt::DecorationRole)); }
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QFileSystemModel::Options)
+
+QT_END_NAMESPACE
+
+#endif // QFILESYSTEMMODEL_H
diff --git a/src/gui/itemmodels/qfilesystemmodel_p.h b/src/gui/itemmodels/qfilesystemmodel_p.h
new file mode 100644
index 0000000000..092eae3b2e
--- /dev/null
+++ b/src/gui/itemmodels/qfilesystemmodel_p.h
@@ -0,0 +1,323 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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$
+**
+****************************************************************************/
+
+#ifndef QFILESYSTEMMODEL_P_H
+#define QFILESYSTEMMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+#include "qfilesystemmodel.h"
+
+#include <private/qabstractitemmodel_p.h>
+#include <qabstractitemmodel.h>
+#include "qfileinfogatherer_p.h"
+#include <qpair.h>
+#include <qdir.h>
+#include <qicon.h>
+#include <qfileinfo.h>
+#include <qtimer.h>
+#include <qhash.h>
+
+QT_REQUIRE_CONFIG(filesystemmodel);
+
+QT_BEGIN_NAMESPACE
+
+class ExtendedInformation;
+class QFileSystemModelPrivate;
+class QFileIconProvider;
+
+#if defined(Q_OS_WIN)
+class QFileSystemModelNodePathKey : public QString
+{
+public:
+ QFileSystemModelNodePathKey() {}
+ QFileSystemModelNodePathKey(const QString &other) : QString(other) {}
+ QFileSystemModelNodePathKey(const QFileSystemModelNodePathKey &other) : QString(other) {}
+ bool operator==(const QFileSystemModelNodePathKey &other) const { return !compare(other, Qt::CaseInsensitive); }
+};
+
+Q_DECLARE_TYPEINFO(QFileSystemModelNodePathKey, Q_MOVABLE_TYPE);
+
+inline size_t qHash(const QFileSystemModelNodePathKey &key) { return qHash(key.toCaseFolded()); }
+#else // Q_OS_WIN
+typedef QString QFileSystemModelNodePathKey;
+#endif
+
+class Q_GUI_EXPORT QFileSystemModelPrivate : public QAbstractItemModelPrivate
+{
+ Q_DECLARE_PUBLIC(QFileSystemModel)
+
+public:
+ enum { NumColumns = 4 };
+
+ class QFileSystemNode
+ {
+ public:
+ Q_DISABLE_COPY_MOVE(QFileSystemNode)
+
+ explicit QFileSystemNode(const QString &filename = QString(), QFileSystemNode *p = nullptr)
+ : fileName(filename), parent(p) {}
+ ~QFileSystemNode() {
+ qDeleteAll(children);
+ delete info;
+ }
+
+ QString fileName;
+#if defined(Q_OS_WIN)
+ QString volumeName;
+#endif
+
+ inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; }
+ inline QString type() const { if (info) return info->displayType; return QLatin1String(""); }
+ inline QDateTime lastModified() const { if (info) return info->lastModified(); return QDateTime(); }
+ inline QFile::Permissions permissions() const { if (info) return info->permissions(); return { }; }
+ inline bool isReadable() const { return ((permissions() & QFile::ReadUser) != 0); }
+ inline bool isWritable() const { return ((permissions() & QFile::WriteUser) != 0); }
+ inline bool isExecutable() const { return ((permissions() & QFile::ExeUser) != 0); }
+ inline bool isDir() const {
+ if (info)
+ return info->isDir();
+ if (children.count() > 0)
+ return true;
+ return false;
+ }
+ inline QFileInfo fileInfo() const { if (info) return info->fileInfo(); return QFileInfo(); }
+ inline bool isFile() const { if (info) return info->isFile(); return true; }
+ inline bool isSystem() const { if (info) return info->isSystem(); return true; }
+ inline bool isHidden() const { if (info) return info->isHidden(); return false; }
+ inline bool isSymLink(bool ignoreNtfsSymLinks = false) const { return info && info->isSymLink(ignoreNtfsSymLinks); }
+ inline bool caseSensitive() const { if (info) return info->isCaseSensitive(); return false; }
+ inline QIcon icon() const { if (info) return info->icon; return QIcon(); }
+
+ inline bool operator <(const QFileSystemNode &node) const {
+ if (caseSensitive() || node.caseSensitive())
+ return fileName < node.fileName;
+ return QString::compare(fileName, node.fileName, Qt::CaseInsensitive) < 0;
+ }
+ inline bool operator >(const QString &name) const {
+ if (caseSensitive())
+ return fileName > name;
+ return QString::compare(fileName, name, Qt::CaseInsensitive) > 0;
+ }
+ inline bool operator <(const QString &name) const {
+ if (caseSensitive())
+ return fileName < name;
+ return QString::compare(fileName, name, Qt::CaseInsensitive) < 0;
+ }
+ inline bool operator !=(const QExtendedInformation &fileInfo) const {
+ return !operator==(fileInfo);
+ }
+ bool operator ==(const QString &name) const {
+ if (caseSensitive())
+ return fileName == name;
+ return QString::compare(fileName, name, Qt::CaseInsensitive) == 0;
+ }
+ bool operator ==(const QExtendedInformation &fileInfo) const {
+ return info && (*info == fileInfo);
+ }
+
+ inline bool hasInformation() const { return info != nullptr; }
+
+ void populate(const QExtendedInformation &fileInfo) {
+ if (!info)
+ info = new QExtendedInformation(fileInfo.fileInfo());
+ (*info) = fileInfo;
+ }
+
+ // children shouldn't normally be accessed directly, use node()
+ inline int visibleLocation(const QString &childName) {
+ return visibleChildren.indexOf(childName);
+ }
+ void updateIcon(QAbstractFileIconProvider *iconProvider, const QString &path) {
+ if (info)
+ info->icon = iconProvider->icon(QFileInfo(path));
+ for (QFileSystemNode *child : qAsConst(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()) {
+ if (path.endsWith(QLatin1Char('/')))
+ child->updateIcon(iconProvider, path + child->fileName);
+ else
+ child->updateIcon(iconProvider, path + QLatin1Char('/') + child->fileName);
+ } else
+ child->updateIcon(iconProvider, child->fileName);
+ }
+ }
+
+ void retranslateStrings(QAbstractFileIconProvider *iconProvider, const QString &path) {
+ if (info)
+ info->displayType = iconProvider->type(QFileInfo(path));
+ for (QFileSystemNode *child : qAsConst(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()) {
+ if (path.endsWith(QLatin1Char('/')))
+ child->retranslateStrings(iconProvider, path + child->fileName);
+ else
+ child->retranslateStrings(iconProvider, path + QLatin1Char('/') + child->fileName);
+ } else
+ child->retranslateStrings(iconProvider, child->fileName);
+ }
+ }
+
+ QHash<QFileSystemModelNodePathKey, QFileSystemNode *> children;
+ QList<QString> visibleChildren;
+ QExtendedInformation *info = nullptr;
+ QFileSystemNode *parent;
+ int dirtyChildrenIndex = -1;
+ bool populatedChildren = false;
+ bool isVisible = false;
+ };
+
+ QFileSystemModelPrivate() = default;
+ void init();
+ /*
+ \internal
+
+ Return true if index which is owned by node is hidden by the filter.
+ */
+ inline bool isHiddenByFilter(QFileSystemNode *indexNode, const QModelIndex &index) const
+ {
+ return (indexNode != &root && !index.isValid());
+ }
+ QFileSystemNode *node(const QModelIndex &index) const;
+ QFileSystemNode *node(const QString &path, bool fetch = true) const;
+ inline QModelIndex index(const QString &path, int column = 0) { return index(node(path), column); }
+ QModelIndex index(const QFileSystemNode *node, int column = 0) const;
+ bool filtersAcceptsNode(const QFileSystemNode *node) const;
+ bool passNameFilters(const QFileSystemNode *node) const;
+ void removeNode(QFileSystemNode *parentNode, const QString &name);
+ QFileSystemNode* addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo &info);
+ void addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles);
+ void removeVisibleFile(QFileSystemNode *parentNode, int visibleLocation);
+ void sortChildren(int column, const QModelIndex &parent);
+
+ inline int translateVisibleLocation(QFileSystemNode *parent, int row) const {
+ if (sortOrder != Qt::AscendingOrder) {
+ if (parent->dirtyChildrenIndex == -1)
+ return parent->visibleChildren.count() - row - 1;
+
+ if (row < parent->dirtyChildrenIndex)
+ return parent->dirtyChildrenIndex - row - 1;
+ }
+
+ return row;
+ }
+
+ inline static QString myComputer() {
+ // ### TODO We should query the system to find out what the string should be
+ // XP == "My Computer",
+ // Vista == "Computer",
+ // OS X == "Computer" (sometime user generated) "Benjamin's PowerBook G4"
+#ifdef Q_OS_WIN
+ return QFileSystemModel::tr("My Computer");
+#else
+ return QFileSystemModel::tr("Computer");
+#endif
+ }
+
+ inline void delayedSort() {
+ if (!delayedSortTimer.isActive())
+ delayedSortTimer.start(0);
+ }
+
+ QIcon icon(const QModelIndex &index) const;
+ QString name(const QModelIndex &index) const;
+ QString displayName(const QModelIndex &index) const;
+ QString filePath(const QModelIndex &index) const;
+ QString size(const QModelIndex &index) const;
+ static QString size(qint64 bytes);
+ 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);
+
+ QDir rootDir;
+#if QT_CONFIG(filesystemwatcher)
+# ifdef Q_OS_WIN
+ QStringList unwatchPathsAt(const QModelIndex &);
+ void watchPaths(const QStringList &paths) { fileInfoGatherer.watchPaths(paths); }
+# endif // Q_OS_WIN
+ QFileInfoGatherer fileInfoGatherer;
+#endif // filesystemwatcher
+ QTimer delayedSortTimer;
+ QHash<const QFileSystemNode*, bool> bypassFilters;
+#if QT_CONFIG(regularexpression)
+ QStringList nameFilters;
+#endif
+ QHash<QString, QString> resolvedSymLinks;
+
+ QFileSystemNode root;
+
+ struct Fetching {
+ QString dir;
+ QString file;
+ const QFileSystemNode *node;
+ };
+ QList<Fetching> toFetch;
+
+ QBasicTimer fetchingTimer;
+
+ QDir::Filters filters = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs;
+ int sortColumn = 0;
+ Qt::SortOrder sortOrder = Qt::AscendingOrder;
+ bool forceSort = true;
+ bool readOnly = true;
+ bool setRootPath = false;
+ bool nameFilterDisables = true; // false on windows, true on mac and unix
+ // This flag is an optimization for QFileDialog. It enables a sort which is
+ // not recursive, meaning we sort only what we see.
+ bool disableRecursiveSort = false;
+};
+Q_DECLARE_TYPEINFO(QFileSystemModelPrivate::Fetching, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif