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