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 <QGridLayout>
#include <QPointer>
#include <QMessageBox>
#include <QPair>
#include <algorithm>
#include <functional>
@@ -980,9 +982,30 @@ static bool addFilesToProject(const QStringList &fileNames, const QString &defau
return true;
bool allSuccessful = true;
QList<QPair<QString, QString>> 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<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();
@@ -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;

View File

@@ -39,7 +39,6 @@
#include <QImageReader>
#include <QPainter>
#include <QRawFont>
#include <QPair>
#include <qmath.h>
#include <condition_variable>
#include <mutex>
@@ -111,17 +110,23 @@ QString fontFamily(const QFileInfo &info)
class ItemLibraryFileIconProvider : public QFileIconProvider
{
public:
ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache)
ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache,
QHash<QString, QPair<QDateTime, QIcon>> &iconCache)
: QFileIconProvider()
, m_fontImageCache(fontImageCache)
, m_iconCache(iconCache)
{
}
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;
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<QSize> iconSizes = {{384, 384},
@@ -175,18 +183,29 @@ public:
{32, 32}}; // List
SynchronousImageCache &m_fontImageCache;
QHash<QString, QPair<QDateTime, QIcon>> &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);
}

View File

@@ -26,9 +26,13 @@
#pragma once
#include <QAbstractTableModel>
#include <QDateTime>
#include <QDir>
#include <QHash>
#include <QIcon>
#include <QPair>
#include <QSet>
#include <QTimer>
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<QString, QPair<QDateTime, QIcon>> m_iconCache;
QTimer m_updatePathTimer;
};
} //QmlDesigner