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 <thomas.hartmann@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
Miikka Heikkinen
2021-02-08 15:56:26 +02:00
parent 7e0f0e470e
commit 359fa8e877
3 changed files with 78 additions and 11 deletions

View File

@@ -82,6 +82,8 @@
#include <QPushButton> #include <QPushButton>
#include <QGridLayout> #include <QGridLayout>
#include <QPointer> #include <QPointer>
#include <QMessageBox>
#include <QPair>
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
@@ -980,9 +982,30 @@ static bool addFilesToProject(const QStringList &fileNames, const QString &defau
return true; return true;
bool allSuccessful = true; bool allSuccessful = true;
QList<QPair<QString, QString>> copyList;
QStringList removeList;
for (const QString &fileName : fileNames) { for (const QString &fileName : fileNames) {
const QString targetFile = directory + "/" + QFileInfo(fileName).fileName(); 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<QWidget *>(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(); auto document = QmlDesignerPlugin::instance()->currentDesignDocument();
@@ -993,7 +1016,7 @@ static bool addFilesToProject(const QStringList &fileNames, const QString &defau
if (node) { if (node) {
ProjectExplorer::FolderNode *containingFolder = node->parentFolderNode(); ProjectExplorer::FolderNode *containingFolder = node->parentFolderNode();
if (containingFolder) if (containingFolder)
containingFolder->addFiles(QStringList(targetFile)); containingFolder->addFiles(QStringList(filePair.second));
} }
} else { } else {
allSuccessful = false; allSuccessful = false;

View File

@@ -39,7 +39,6 @@
#include <QImageReader> #include <QImageReader>
#include <QPainter> #include <QPainter>
#include <QRawFont> #include <QRawFont>
#include <QPair>
#include <qmath.h> #include <qmath.h>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
@@ -111,17 +110,23 @@ QString fontFamily(const QFileInfo &info)
class ItemLibraryFileIconProvider : public QFileIconProvider class ItemLibraryFileIconProvider : public QFileIconProvider
{ {
public: public:
ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache) ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache,
QHash<QString, QPair<QDateTime, QIcon>> &iconCache)
: QFileIconProvider() : QFileIconProvider()
, m_fontImageCache(fontImageCache) , m_fontImageCache(fontImageCache)
, m_iconCache(iconCache)
{ {
} }
QIcon icon( const QFileInfo & info ) const override QIcon icon( const QFileInfo & info ) const override
{ {
const QString filePath = info.absoluteFilePath();
QPair<QDateTime, QIcon> &cachedIcon = m_iconCache[filePath];
if (!cachedIcon.second.isNull() && cachedIcon.first == info.lastModified())
return cachedIcon.second;
QIcon icon; QIcon icon;
const QString suffix = info.suffix().toLower(); const QString suffix = info.suffix().toLower();
const QString filePath = info.absoluteFilePath();
// Provide icon depending on suffix // Provide icon depending on suffix
QPixmap origPixmap; QPixmap origPixmap;
@@ -150,6 +155,8 @@ public:
icon.addPixmap(pixmap); icon.addPixmap(pixmap);
} }
cachedIcon.first = info.lastModified();
cachedIcon.second = icon;
return icon; return icon;
} }
@@ -163,6 +170,7 @@ public:
"Abc"}); "Abc"});
} }
private:
// Generated icon sizes should contain all ItemLibraryResourceView needed icon sizes, and their // Generated icon sizes should contain all ItemLibraryResourceView needed icon sizes, and their
// x2 versions for HDPI sceens // x2 versions for HDPI sceens
std::vector<QSize> iconSizes = {{384, 384}, std::vector<QSize> iconSizes = {{384, 384},
@@ -175,18 +183,29 @@ public:
{32, 32}}; // List {32, 32}}; // List
SynchronousImageCache &m_fontImageCache; SynchronousImageCache &m_fontImageCache;
QHash<QString, QPair<QDateTime, QIcon>> &m_iconCache;
}; };
CustomFileSystemModel::CustomFileSystemModel(SynchronousImageCache &fontImageCache, QObject *parent) CustomFileSystemModel::CustomFileSystemModel(SynchronousImageCache &fontImageCache, QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_fileSystemModel(new QFileSystemModel(this)) , m_fileSystemModel(new QFileSystemModel(this))
, m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) , m_fileSystemWatcher(new Utils::FileSystemWatcher(this))
, m_fontImageCache(fontImageCache)
{ {
m_fileSystemModel->setIconProvider(new ItemLibraryFileIconProvider(fontImageCache)); m_updatePathTimer.setInterval(200);
m_updatePathTimer.setSingleShot(true);
connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, [this] { m_updatePathTimer.callOnTimeout([this]() {
updatePath(m_fileSystemModel->rootPath()); 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) void CustomFileSystemModel::setFilter(QDir::Filters)
@@ -351,9 +370,17 @@ void CustomFileSystemModel::appendIfNotFiltered(const QString &file)
QModelIndex CustomFileSystemModel::updatePath(const QString &newPath) QModelIndex CustomFileSystemModel::updatePath(const QString &newPath)
{ {
beginResetModel(); 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_fileSystemModel->setRootPath(newPath);
m_fileSystemWatcher->removeDirectories(m_fileSystemWatcher->directories()); m_fileSystemWatcher->removeDirectories(m_fileSystemWatcher->directories());
m_fileSystemWatcher->removeFiles(m_fileSystemWatcher->files());
m_fileSystemWatcher->addDirectory(newPath, Utils::FileSystemWatcher::WatchAllChanges); m_fileSystemWatcher->addDirectory(newPath, Utils::FileSystemWatcher::WatchAllChanges);
@@ -385,11 +412,19 @@ QModelIndex CustomFileSystemModel::updatePath(const QString &newPath)
while (fileIterator.hasNext()) while (fileIterator.hasNext())
appendIfNotFiltered(fileIterator.next()); appendIfNotFiltered(fileIterator.next());
QDirIterator dirIterator(newPath, {}, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); QDirIterator dirIterator(newPath, {}, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
while (dirIterator.hasNext()) QDirIterator::Subdirectories);
m_fileSystemWatcher->addDirectory(dirIterator.next(), Utils::FileSystemWatcher::WatchAllChanges); 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(); endResetModel();
return QAbstractListModel::index(0, 0); return QAbstractListModel::index(0, 0);
} }

View File

@@ -26,9 +26,13 @@
#pragma once #pragma once
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QDateTime>
#include <QDir> #include <QDir>
#include <QHash>
#include <QIcon>
#include <QPair> #include <QPair>
#include <QSet> #include <QSet>
#include <QTimer>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QFileIconProvider; class QFileIconProvider;
@@ -40,6 +44,7 @@ namespace Utils { class FileSystemWatcher; }
namespace QmlDesigner { namespace QmlDesigner {
class SynchronousImageCache; class SynchronousImageCache;
class ItemLibraryFileIconProvider;
class CustomFileSystemModel : public QAbstractListModel class CustomFileSystemModel : public QAbstractListModel
{ {
@@ -78,6 +83,10 @@ private:
QStringList m_files; QStringList m_files;
QString m_searchFilter; QString m_searchFilter;
Utils::FileSystemWatcher *m_fileSystemWatcher; Utils::FileSystemWatcher *m_fileSystemWatcher;
SynchronousImageCache &m_fontImageCache;
ItemLibraryFileIconProvider *m_fileIconProvider = nullptr;
QHash<QString, QPair<QDateTime, QIcon>> m_iconCache;
QTimer m_updatePathTimer;
}; };
} //QmlDesigner } //QmlDesigner