From 359fa8e877ea256b78b2f5e1d911f3c5d55846c2 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 8 Feb 2021 15:56:26 +0200 Subject: [PATCH] QmlDesigner: Support updating imported assets Added support for overwriting file-based assets such as images when an asset with the same name is imported to the same location. Assets tab will now also refresh its icons if underlying files are modified. Added local caching of icons to avoid regenerating unchanged icons. Also added bit of a delay before responding to a file system change, which should improve performance when multiple assets are imported at once. Fixes: QDS-3644 Change-Id: Id452d3b1de8f4651a36401878c5067655865b675 Reviewed-by: Thomas Hartmann Reviewed-by: Mahmoud Badri Reviewed-by: Leena Miettinen --- .../componentcore/modelnodeoperations.cpp | 27 +++++++++- .../itemlibrary/customfilesystemmodel.cpp | 53 +++++++++++++++---- .../itemlibrary/customfilesystemmodel.h | 9 ++++ 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 1c07e0ea2f0..c13080c8f1f 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -82,6 +82,8 @@ #include #include #include +#include +#include #include #include @@ -980,9 +982,30 @@ static bool addFilesToProject(const QStringList &fileNames, const QString &defau return true; bool allSuccessful = true; + QList> copyList; + QStringList removeList; for (const QString &fileName : fileNames) { const QString targetFile = directory + "/" + QFileInfo(fileName).fileName(); - const bool success = QFile::copy(fileName, targetFile); + if (QFileInfo::exists(targetFile)) { + const QString title = QCoreApplication::translate( + "ModelNodeOperations", "Overwrite Existing File?"); + const QString question = QCoreApplication::translate( + "ModelNodeOperations", "File already exists. Overwrite?\n\"%1\"").arg(targetFile); + if (QMessageBox::question(qobject_cast(Core::ICore::dialogParent()), + title, question, QMessageBox::Yes | QMessageBox::No) + != QMessageBox::Yes) { + continue; + } + removeList.append(targetFile); + } + copyList.append({fileName, targetFile}); + } + // Defer actual file operations after we have dealt with possible popup dialogs to avoid + // unnecessarily refreshing file models multiple times during the operation + for (const auto &file : qAsConst(removeList)) + QFile::remove(file); + for (const auto &filePair : qAsConst(copyList)) { + const bool success = QFile::copy(filePair.first, filePair.second); auto document = QmlDesignerPlugin::instance()->currentDesignDocument(); @@ -993,7 +1016,7 @@ static bool addFilesToProject(const QStringList &fileNames, const QString &defau if (node) { ProjectExplorer::FolderNode *containingFolder = node->parentFolderNode(); if (containingFolder) - containingFolder->addFiles(QStringList(targetFile)); + containingFolder->addFiles(QStringList(filePair.second)); } } else { allSuccessful = false; diff --git a/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp index db16051072a..9873963411c 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -111,17 +110,23 @@ QString fontFamily(const QFileInfo &info) class ItemLibraryFileIconProvider : public QFileIconProvider { public: - ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache) + ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache, + QHash> &iconCache) : QFileIconProvider() , m_fontImageCache(fontImageCache) + , m_iconCache(iconCache) { } QIcon icon( const QFileInfo & info ) const override { + const QString filePath = info.absoluteFilePath(); + QPair &cachedIcon = m_iconCache[filePath]; + if (!cachedIcon.second.isNull() && cachedIcon.first == info.lastModified()) + return cachedIcon.second; + QIcon icon; const QString suffix = info.suffix().toLower(); - const QString filePath = info.absoluteFilePath(); // Provide icon depending on suffix QPixmap origPixmap; @@ -150,6 +155,8 @@ public: icon.addPixmap(pixmap); } + cachedIcon.first = info.lastModified(); + cachedIcon.second = icon; return icon; } @@ -163,6 +170,7 @@ public: "Abc"}); } +private: // Generated icon sizes should contain all ItemLibraryResourceView needed icon sizes, and their // x2 versions for HDPI sceens std::vector iconSizes = {{384, 384}, @@ -175,18 +183,29 @@ public: {32, 32}}; // List SynchronousImageCache &m_fontImageCache; + QHash> &m_iconCache; }; CustomFileSystemModel::CustomFileSystemModel(SynchronousImageCache &fontImageCache, QObject *parent) : QAbstractListModel(parent) , m_fileSystemModel(new QFileSystemModel(this)) , m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) + , m_fontImageCache(fontImageCache) { - m_fileSystemModel->setIconProvider(new ItemLibraryFileIconProvider(fontImageCache)); - - connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, [this] { + m_updatePathTimer.setInterval(200); + m_updatePathTimer.setSingleShot(true); + m_updatePathTimer.callOnTimeout([this]() { updatePath(m_fileSystemModel->rootPath()); }); + + // If project directory contents change, or one of the asset files is modified, we must + // reconstruct the model to update the icons + connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, [this] { + m_updatePathTimer.start(); + }); + connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, [this] { + m_updatePathTimer.start(); + }); } void CustomFileSystemModel::setFilter(QDir::Filters) @@ -351,9 +370,17 @@ void CustomFileSystemModel::appendIfNotFiltered(const QString &file) QModelIndex CustomFileSystemModel::updatePath(const QString &newPath) { beginResetModel(); + + // We must recreate icon provider to ensure modified icons are recreated + auto newProvider = new ItemLibraryFileIconProvider(m_fontImageCache, m_iconCache); + m_fileSystemModel->setIconProvider(newProvider); + delete m_fileIconProvider; + m_fileIconProvider = newProvider; + m_fileSystemModel->setRootPath(newPath); m_fileSystemWatcher->removeDirectories(m_fileSystemWatcher->directories()); + m_fileSystemWatcher->removeFiles(m_fileSystemWatcher->files()); m_fileSystemWatcher->addDirectory(newPath, Utils::FileSystemWatcher::WatchAllChanges); @@ -385,11 +412,19 @@ QModelIndex CustomFileSystemModel::updatePath(const QString &newPath) while (fileIterator.hasNext()) appendIfNotFiltered(fileIterator.next()); - QDirIterator dirIterator(newPath, {}, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) - m_fileSystemWatcher->addDirectory(dirIterator.next(), Utils::FileSystemWatcher::WatchAllChanges); + QDirIterator dirIterator(newPath, {}, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + const QString entry = dirIterator.next(); + QFileInfo fi{entry}; + if (fi.isDir()) + m_fileSystemWatcher->addDirectory(entry, Utils::FileSystemWatcher::WatchAllChanges); + else if (supportedSuffixes().contains(fi.suffix())) + m_fileSystemWatcher->addFile(entry, Utils::FileSystemWatcher::WatchAllChanges); + } endResetModel(); + return QAbstractListModel::index(0, 0); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.h b/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.h index 1051dec1ce1..eabd6c1e23b 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.h @@ -26,9 +26,13 @@ #pragma once #include +#include #include +#include +#include #include #include +#include QT_BEGIN_NAMESPACE class QFileIconProvider; @@ -40,6 +44,7 @@ namespace Utils { class FileSystemWatcher; } namespace QmlDesigner { class SynchronousImageCache; +class ItemLibraryFileIconProvider; class CustomFileSystemModel : public QAbstractListModel { @@ -78,6 +83,10 @@ private: QStringList m_files; QString m_searchFilter; Utils::FileSystemWatcher *m_fileSystemWatcher; + SynchronousImageCache &m_fontImageCache; + ItemLibraryFileIconProvider *m_fileIconProvider = nullptr; + QHash> m_iconCache; + QTimer m_updatePathTimer; }; } //QmlDesigner