QmlDesigner: Improve imagecache

Instead of coding some arguments to extraId(state) we provide now a
std::variant there extra arguments can be saved. Because it's a
std::variant it can be easlily extended by new structs. There is a new
synchronous interface too. It has an extra method for QIcon which saves
icons in an extra table. It would be even nicer if we would have a
mipmap image too. So we could do it asynchonously too but so far it works
only in the main thread.

Task-number: QDS-3579
Fixes: QDS-3584
Change-Id: If368d84d82308a91a5f4f037021e749c9ef868ed
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Marco Bubke
2021-01-13 13:23:46 +01:00
parent 3ffc7271e5
commit 7dc72c533e
55 changed files with 2063 additions and 904 deletions

View File

@@ -249,11 +249,18 @@ void BaseStatement::bind(int index, Utils::SmallStringView text)
void BaseStatement::bind(int index, BlobView blobView)
{
int resultCode = sqlite3_bind_blob64(m_compiledStatement.get(),
int resultCode = SQLITE_OK;
if (blobView.empty()) {
sqlite3_bind_null(m_compiledStatement.get(), index);
} else {
resultCode = sqlite3_bind_blob64(m_compiledStatement.get(),
index,
blobView.data(),
blobView.size(),
SQLITE_STATIC);
}
if (resultCode != SQLITE_OK)
checkForBindingError(resultCode);
}
@@ -713,6 +720,26 @@ StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column)
}
} // namespace
Type BaseStatement::fetchType(int column) const
{
auto dataType = sqlite3_column_type(m_compiledStatement.get(), column);
switch (dataType) {
case SQLITE_INTEGER:
return Type::Integer;
case SQLITE_FLOAT:
return Type::Float;
case SQLITE3_TEXT:
return Type::Text;
case SQLITE_BLOB:
return Type::Blob;
case SQLITE_NULL:
return Type::Null;
}
return Type::Invalid;
}
int BaseStatement::fetchIntValue(int column) const
{
return sqlite3_column_int(m_compiledStatement.get(), column);

View File

@@ -51,6 +51,8 @@ namespace Sqlite {
class Database;
class DatabaseBackend;
enum class Type : char { Invalid, Integer, Float, Text, Blob, Null };
class SQLITE_EXPORT BaseStatement
{
public:
@@ -65,6 +67,7 @@ public:
void step() const;
void reset() const;
Type fetchType(int column) const;
int fetchIntValue(int column) const;
long fetchLongValue(int column) const;
long long fetchLongLongValue(int column) const;

View File

@@ -509,8 +509,10 @@ extend_qtc_plugin(QmlDesigner
include/textmodifier.h
include/variantproperty.h
include/viewmanager.h
include/imagecache.h
include/imagecacheinterface.h
include/asynchronousimagecache.h
include/synchronousimagecache.h
include/imagecacheauxiliarydata.h
include/asynchronousimagecacheinterface.h
)
extend_qtc_plugin(QmlDesigner
@@ -601,7 +603,8 @@ extend_qtc_plugin(QmlDesigner
imagecache/imagecachecollector.cpp
imagecache/imagecachefontcollector.h
imagecache/imagecachefontcollector.cpp
imagecache/imagecache.cpp
imagecache/asynchronousimagecache.cpp
imagecache/synchronousimagecache.cpp
imagecache/imagecachecollectorinterface.h
imagecache/imagecacheconnectionmanager.cpp
imagecache/imagecacheconnectionmanager.h

View File

@@ -25,8 +25,8 @@
#include "customfilesystemmodel.h"
#include <synchronousimagecache.h>
#include <theme.h>
#include <imagecache.h>
#include <utils/filesystemwatcher.h>
@@ -98,7 +98,7 @@ QString fontFamily(const QFileInfo &info)
class ItemLibraryFileIconProvider : public QFileIconProvider
{
public:
ItemLibraryFileIconProvider(ImageCache &fontImageCache)
ItemLibraryFileIconProvider(SynchronousImageCache &fontImageCache)
: QFileIconProvider()
, m_fontImageCache(fontImageCache)
{
@@ -138,71 +138,29 @@ public:
QIcon generateFontIcons(const QString &filePath) const
{
QIcon icon;
QString colorName = Theme::getColor(Theme::DStextColor).name();
std::condition_variable condition;
int count = iconSizes.size();
std::mutex mutex;
QList<QPair<QSize, QImage>> images;
for (auto iconSize : iconSizes) {
m_fontImageCache.requestImage(
filePath,
[&images, &condition, &count, &mutex, iconSize](const QImage &image) {
int currentCount;
{
std::unique_lock lock{mutex};
currentCount = --count;
images.append({iconSize, image});
}
if (currentCount <= 0)
condition.notify_all();
},
[&images, &condition, &count, &mutex, iconSize] {
int currentCount;
{
std::unique_lock lock{mutex};
currentCount = --count;
images.append({iconSize, {}});
}
if (currentCount <= 0)
condition.notify_all();
},
QStringLiteral("%1@%2@Abc").arg(QString::number(iconSize.width()),
colorName)
);
}
{
// Block main thread until icons are generated, as it has to be done synchronously
std::unique_lock lock{mutex};
if (count > 0)
condition.wait(lock, [&]{ return count <= 0; });
}
for (const auto &pair : qAsConst(images)) {
QImage image = pair.second;
if (image.isNull())
icon.addPixmap(defaultPixmapForType("font", pair.first));
else
icon.addPixmap(QPixmap::fromImage(image));
}
return icon;
return m_fontImageCache.icon(
filePath,
{},
ImageCache::FontCollectorSizesAuxiliaryData{Utils::span{iconSizes},
Theme::getColor(Theme::DStextColor).name(),
"Abc"});
}
// Generated icon sizes should contain all ItemLibraryResourceView needed icon sizes, and their
// x2 versions for HDPI sceens
QList<QSize> iconSizes = {{384, 384}, {192, 192}, // Large
{256, 256}, {128, 128}, // Drag
{96, 96}, // Medium
{48, 48}, // Small
{64, 64}, {32, 32}}; // List
std::vector<QSize> iconSizes = {{384, 384},
{192, 192}, // Large
{256, 256},
{128, 128}, // Drag
{96, 96}, // Medium
{48, 48}, // Small
{64, 64},
{32, 32}}; // List
ImageCache &m_fontImageCache;
SynchronousImageCache &m_fontImageCache;
};
CustomFileSystemModel::CustomFileSystemModel(ImageCache &fontImageCache, QObject *parent)
CustomFileSystemModel::CustomFileSystemModel(SynchronousImageCache &fontImageCache, QObject *parent)
: QAbstractListModel(parent)
, m_fileSystemModel(new QFileSystemModel(this))
, m_fileSystemWatcher(new Utils::FileSystemWatcher(this))

View File

@@ -39,13 +39,14 @@ namespace Utils { class FileSystemWatcher; }
namespace QmlDesigner {
class ImageCache;
class SynchronousImageCache;
class CustomFileSystemModel : public QAbstractListModel
{
Q_OBJECT
public:
CustomFileSystemModel(ImageCache &fontImageCache, QObject *parent = nullptr);
CustomFileSystemModel(QmlDesigner::SynchronousImageCache &fontImageCache,
QObject *parent = nullptr);
void setFilter(QDir::Filters filters);
QString rootPath() const;

View File

@@ -66,7 +66,7 @@ QQuickImageResponse *ItemLibraryIconImageProvider::requestImageResponse(const QS
{
auto response = std::make_unique<ImageRespose>();
m_cache.requestIcon(
m_cache.requestSmallImage(
id,
[response = QPointer<ImageRespose>(response.get())](const QImage &image) {
QMetaObject::invokeMethod(

View File

@@ -29,7 +29,7 @@
#include <rewriterview.h>
#include <coreplugin/icore.h>
#include <imagecache.h>
#include <asynchronousimagecache.h>
#include <imagecache/imagecachecollector.h>
#include <imagecache/imagecacheconnectionmanager.h>
#include <imagecache/imagecachegenerator.h>
@@ -45,14 +45,14 @@ namespace QmlDesigner {
class ItemLibraryIconImageProvider : public QQuickAsyncImageProvider
{
public:
ItemLibraryIconImageProvider(ImageCache &imageCache)
ItemLibraryIconImageProvider(AsynchronousImageCache &imageCache)
: m_cache{imageCache}
{}
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
private:
ImageCache &m_cache;
AsynchronousImageCache &m_cache;
};
} // namespace QmlDesigner

View File

@@ -28,7 +28,7 @@
#include "customfilesystemmodel.h"
#include <theme.h>
#include <imagecache.h>
#include <asynchronousimagecache.h>
#include <QAction>
#include <QActionGroup>
@@ -62,8 +62,9 @@ void ItemLibraryResourceView::addSizeAction(QActionGroup *group, const QString &
});
}
ItemLibraryResourceView::ItemLibraryResourceView(ImageCache &fontImageCache, QWidget *parent) :
QListView(parent)
ItemLibraryResourceView::ItemLibraryResourceView(AsynchronousImageCache &fontImageCache,
QWidget *parent)
: QListView(parent)
{
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -113,13 +114,12 @@ ItemLibraryResourceView::ItemLibraryResourceView(ImageCache &fontImageCache, QWi
// a commonly used sentence to preview the font glyphs in latin fonts.
// For fonts that do not have latin glyphs, the font family name will have to
// suffice for preview. Font family name is inserted into %1 at render time.
m_fontPreviewTooltipBackend->setState(QStringLiteral("%1@%2@%3")
.arg(QString::number(300),
Theme::getColor(Theme::DStextColor).name(),
QStringLiteral("%1\n\n"
"The quick brown fox jumps\n"
"over the lazy dog\n"
"1234567890")));
m_fontPreviewTooltipBackend->setAuxiliaryData(
ImageCache::FontCollectorSizeAuxiliaryData{QSize{300, 300},
Theme::getColor(Theme::DStextColor).name(),
QStringLiteral("The quick brown fox jumps\n"
"over the lazy dog\n"
"1234567890")});
}
void ItemLibraryResourceView::startDrag(Qt::DropActions /* supportedActions */)

View File

@@ -35,13 +35,14 @@ QT_END_NAMESPACE
namespace QmlDesigner {
class ImageCache;
class AsynchronousImageCache;
class ItemLibraryResourceView : public QListView {
Q_OBJECT
public:
explicit ItemLibraryResourceView(ImageCache &fontImageCache, QWidget *parent = nullptr);
explicit ItemLibraryResourceView(AsynchronousImageCache &fontImageCache,
QWidget *parent = nullptr);
void startDrag(Qt::DropActions supportedActions) override;
bool viewportEvent(QEvent *event) override;

View File

@@ -26,12 +26,12 @@
#include "itemlibraryview.h"
#include "itemlibrarywidget.h"
#include "metainfo.h"
#include <asynchronousimagecache.h>
#include <bindingproperty.h>
#include <coreplugin/icore.h>
#include <imagecache.h>
#include <imagecache/imagecachecollector.h>
#include <imagecache/imagecachefontcollector.h>
#include <imagecache/imagecacheconnectionmanager.h>
#include <imagecache/imagecachefontcollector.h>
#include <imagecache/imagecachegenerator.h>
#include <imagecache/imagecachestorage.h>
#include <imagecache/timestampprovider.h>
@@ -42,6 +42,7 @@
#include <projectexplorer/target.h>
#include <rewriterview.h>
#include <sqlitedatabase.h>
#include <synchronousimagecache.h>
#include <utils/algorithm.h>
#include <qmldesignerplugin.h>
#include <qmlitemnode.h>
@@ -52,7 +53,7 @@ class ImageCacheData
{
public:
Sqlite::Database database{
Utils::PathString{Core::ICore::cacheResourcePath() + "/imagecache-v1.db"}};
Utils::PathString{Core::ICore::cacheResourcePath() + "/imagecache-v2.db"}};
ImageCacheStorage<Sqlite::Database> storage{database};
ImageCacheConnectionManager connectionManager;
ImageCacheCollector collector{connectionManager};
@@ -60,8 +61,9 @@ public:
ImageCacheGenerator generator{collector, storage};
ImageCacheGenerator fontGenerator{fontCollector, storage};
TimeStampProvider timeStampProvider;
ImageCache cache{storage, generator, timeStampProvider};
ImageCache fontImageCache{storage, fontGenerator, timeStampProvider};
AsynchronousImageCache cache{storage, generator, timeStampProvider};
AsynchronousImageCache asynchronousFontImageCache{storage, fontGenerator, timeStampProvider};
SynchronousImageCache synchronousFontImageCache{storage, timeStampProvider, fontCollector};
};
ItemLibraryView::ItemLibraryView(QObject* parent)
@@ -82,7 +84,9 @@ bool ItemLibraryView::hasWidget() const
WidgetInfo ItemLibraryView::widgetInfo()
{
if (m_widget.isNull()) {
m_widget = new ItemLibraryWidget{m_imageCacheData->cache, m_imageCacheData->fontImageCache};
m_widget = new ItemLibraryWidget{m_imageCacheData->cache,
m_imageCacheData->asynchronousFontImageCache,
m_imageCacheData->synchronousFontImageCache};
m_widget->setImportsWidget(m_importManagerView->widgetInfo().widget);
}
@@ -159,12 +163,14 @@ void ItemLibraryView::importsChanged(const QList<Import> &addedImports, const QL
void ItemLibraryView::setResourcePath(const QString &resourcePath)
{
if (m_widget.isNull())
m_widget = new ItemLibraryWidget{m_imageCacheData->cache, m_imageCacheData->fontImageCache};
m_widget = new ItemLibraryWidget{m_imageCacheData->cache,
m_imageCacheData->asynchronousFontImageCache,
m_imageCacheData->synchronousFontImageCache};
m_widget->setResourcePath(resourcePath);
}
ImageCache &ItemLibraryView::imageCache()
AsynchronousImageCache &ItemLibraryView::imageCache()
{
return m_imageCacheData->cache;
}

View File

@@ -35,7 +35,7 @@ namespace QmlDesigner {
class ItemLibraryWidget;
class ImportManagerView;
class ImageCacheData;
class ImageCache;
class AsynchronousImageCache;
class ItemLibraryView : public AbstractView
{
@@ -56,7 +56,7 @@ public:
void setResourcePath(const QString &resourcePath);
ImageCache &imageCache();
AsynchronousImageCache &imageCache();
protected:
void updateImports();

View File

@@ -82,10 +82,12 @@ static QString propertyEditorResourcesPath() {
return Core::ICore::resourcePath() + QStringLiteral("/qmldesigner/propertyEditorQmlSources");
}
ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache, ImageCache &fontImageCache)
ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache,
AsynchronousImageCache &asynchronousFontImageCache,
SynchronousImageCache &synchronousFontImageCache)
: m_itemIconSize(24, 24)
, m_itemViewQuickWidget(new QQuickWidget(this))
, m_resourcesView(new ItemLibraryResourceView(fontImageCache, this))
, m_resourcesView(new ItemLibraryResourceView(asynchronousFontImageCache, this))
, m_importTagsWidget(new QWidget(this))
, m_addResourcesWidget(new QWidget(this))
, m_imageCache{imageCache}
@@ -118,7 +120,7 @@ ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache, ImageCache &fontIma
Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
/* create Resources view and its model */
m_resourcesFileSystemModel = new CustomFileSystemModel(fontImageCache, this);
m_resourcesFileSystemModel = new CustomFileSystemModel(synchronousFontImageCache, this);
m_resourcesView->setModel(m_resourcesFileSystemModel.data());
/* create image provider for loading item icons */

View File

@@ -56,7 +56,8 @@ class CustomFileSystemModel;
class ItemLibraryModel;
class ItemLibraryResourceView;
class ImageCache;
class SynchronousImageCache;
class AsynchronousImageCache;
class ImageCacheCollector;
class ItemLibraryWidget : public QFrame
@@ -69,7 +70,9 @@ class ItemLibraryWidget : public QFrame
};
public:
ItemLibraryWidget(ImageCache &imageCache, ImageCache &fontImageCache);
ItemLibraryWidget(AsynchronousImageCache &imageCache,
AsynchronousImageCache &asynchronousFontImageCache,
SynchronousImageCache &synchronousFontImageCache);
~ItemLibraryWidget();
void setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo);
@@ -126,7 +129,7 @@ private:
std::unique_ptr<PreviewTooltipBackend> m_previewTooltipBackend;
QShortcut *m_qmlSourceUpdateShortcut;
ImageCache &m_imageCache;
AsynchronousImageCache &m_imageCache;
QPointer<Model> m_model;
FilterChangeFlag m_filterFlag;
ItemLibraryEntry m_currentitemLibraryEntry;

View File

@@ -28,7 +28,7 @@
#include "previewimagetooltip.h"
#include <coreplugin/icore.h>
#include <imagecache.h>
#include <asynchronousimagecache.h>
#include <QApplication>
#include <QMetaObject>
@@ -36,7 +36,7 @@
namespace QmlDesigner {
PreviewTooltipBackend::PreviewTooltipBackend(ImageCache &cache)
PreviewTooltipBackend::PreviewTooltipBackend(AsynchronousImageCache &cache)
: m_cache{cache}
{}
@@ -64,8 +64,8 @@ void PreviewTooltipBackend::showTooltip()
});
},
[] {},
m_state
);
m_extraId,
m_auxiliaryData);
reposition();
}
@@ -155,17 +155,17 @@ void PreviewTooltipBackend::setInfo(const QString &info)
emit infoChanged();
}
QString PreviewTooltipBackend::state() const
QString PreviewTooltipBackend::extraId() const
{
return m_state;
return m_extraId;
}
// Sets the imageCache state hint. Valid content depends on image cache collector used.
void PreviewTooltipBackend::setState(const QString &state)
// Sets the imageCache extraId hint. Valid content depends on image cache collector used.
void PreviewTooltipBackend::setExtraId(const QString &extraId)
{
m_state = state;
if (m_state != state)
emit stateChanged();
m_extraId = extraId;
if (m_extraId != extraId)
emit extraIdChanged();
}
} // namespace QmlDesigner

View File

@@ -25,6 +25,8 @@
#pragma once
#include <imagecacheauxiliarydata.h>
#include <QObject>
#include <QQmlEngine>
@@ -33,7 +35,7 @@
namespace QmlDesigner {
class PreviewImageTooltip;
class ImageCache;
class AsynchronousImageCache;
class PreviewTooltipBackend : public QObject
{
@@ -42,10 +44,10 @@ class PreviewTooltipBackend : public QObject
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QString info READ info WRITE setInfo NOTIFY infoChanged)
Q_PROPERTY(QString state READ state WRITE setState NOTIFY stateChanged)
Q_PROPERTY(QString extraId READ extraId WRITE setExtraId NOTIFY extraIdChanged)
public:
PreviewTooltipBackend(ImageCache &cache);
PreviewTooltipBackend(AsynchronousImageCache &cache);
~PreviewTooltipBackend();
Q_INVOKABLE void showTooltip();
@@ -58,24 +60,30 @@ public:
void setPath(const QString &path);
QString info() const;
void setInfo(const QString &info);
QString state() const;
void setState(const QString &state);
QString extraId() const;
void setExtraId(const QString &extraId);
bool isVisible() const;
void setAuxiliaryData(ImageCache::AuxiliaryData auxiliaryData)
{
m_auxiliaryData = std::move(auxiliaryData);
}
signals:
void nameChanged();
void pathChanged();
void infoChanged();
void stateChanged();
void extraIdChanged();
private:
QString m_name;
QString m_path;
QString m_info;
QString m_state;
QString m_extraId;
std::unique_ptr<PreviewImageTooltip> m_tooltip;
ImageCache &m_cache;
ImageCache::AuxiliaryData m_auxiliaryData;
AsynchronousImageCache &m_cache;
};
}

View File

@@ -88,7 +88,8 @@ SOURCES += $$PWD/model/abstractview.cpp \
$$PWD/model/qmltimelinekeyframegroup.cpp \
$$PWD/model/annotation.cpp \
$$PWD/model/stylesheetmerger.cpp \
$$PWD/imagecache/imagecache.cpp \
$$PWD/imagecache/asynchronousimagecache.cpp \
$$PWD/imagecache/synchronousimagecache.cpp \
$$PWD/imagecache/imagecacheconnectionmanager.cpp \
$$PWD/imagecache/imagecachegenerator.cpp \
$$PWD/imagecache/timestampprovider.cpp
@@ -174,7 +175,9 @@ HEADERS += $$PWD/include/qmldesignercorelib_global.h \
$$PWD/include/qmltimelinekeyframegroup.h \
$$PWD/include/annotation.h \
$$PWD/include/stylesheetmerger.h \
$$PWD/include/imagecache.h \
$$PWD/include/asynchronousimagecache.h \
$$PWD/include/synchronousimagecache.h \
$$PWD/include/imagecacheauxiliarydata.h \
$$PWD/imagecache/imagecachecollectorinterface.h \
$$PWD/imagecache/imagecacheconnectionmanager.h \
$$PWD/imagecache/imagecachegeneratorinterface.h \

View File

@@ -23,7 +23,7 @@
**
****************************************************************************/
#include "imagecache.h"
#include "asynchronousimagecache.h"
#include "imagecachegenerator.h"
#include "imagecachestorage.h"
@@ -33,9 +33,9 @@
namespace QmlDesigner {
ImageCache::ImageCache(ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider)
AsynchronousImageCache::AsynchronousImageCache(ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider)
: m_storage(storage)
, m_generator(generator)
, m_timeStampProvider(timeStampProvider)
@@ -44,10 +44,11 @@ ImageCache::ImageCache(ImageCacheStorageInterface &storage,
while (isRunning()) {
if (auto [hasEntry, entry] = getEntry(); hasEntry) {
request(entry.name,
entry.state,
entry.extraId,
entry.requestType,
std::move(entry.captureCallback),
std::move(entry.abortCallback),
std::move(entry.auxiliaryData),
m_storage,
m_generator,
m_timeStampProvider);
@@ -58,26 +59,27 @@ ImageCache::ImageCache(ImageCacheStorageInterface &storage,
}};
}
ImageCache::~ImageCache()
AsynchronousImageCache::~AsynchronousImageCache()
{
clean();
wait();
}
void ImageCache::request(Utils::SmallStringView name,
Utils::SmallStringView state,
ImageCache::RequestType requestType,
ImageCache::CaptureCallback captureCallback,
ImageCache::AbortCallback abortCallback,
ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider)
void AsynchronousImageCache::request(Utils::SmallStringView name,
Utils::SmallStringView extraId,
AsynchronousImageCache::RequestType requestType,
AsynchronousImageCache::CaptureImageCallback captureCallback,
AsynchronousImageCache::AbortCallback abortCallback,
ImageCache::AuxiliaryData auxiliaryData,
ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider)
{
const auto id = state.empty() ? Utils::PathString{name} : Utils::PathString{name, "+", state};
const auto id = extraId.empty() ? Utils::PathString{name} : Utils::PathString{name, "+", extraId};
const auto timeStamp = timeStampProvider.timeStamp(name);
const auto entry = requestType == RequestType::Image ? storage.fetchImage(id, timeStamp)
: storage.fetchIcon(id, timeStamp);
: storage.fetchSmallImage(id, timeStamp);
if (entry.hasEntry) {
if (entry.image.isNull())
@@ -85,15 +87,20 @@ void ImageCache::request(Utils::SmallStringView name,
else
captureCallback(entry.image);
} else {
auto callback = [captureCallback = std::move(captureCallback),
requestType](const QImage &image, const QImage &smallImage) {
captureCallback(requestType == RequestType::Image ? image : smallImage);
};
generator.generateImage(name,
state,
extraId,
timeStamp,
std::move(captureCallback),
std::move(abortCallback));
std::move(callback),
std::move(abortCallback),
std::move(auxiliaryData));
}
}
void ImageCache::wait()
void AsynchronousImageCache::wait()
{
stopThread();
m_condition.notify_all();
@@ -101,46 +108,50 @@ void ImageCache::wait()
m_backgroundThread.join();
}
void ImageCache::requestImage(Utils::PathString name,
ImageCache::CaptureCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString state)
void AsynchronousImageCache::requestImage(Utils::PathString name,
AsynchronousImageCache::CaptureImageCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString extraId,
ImageCache::AuxiliaryData auxiliaryData)
{
addEntry(std::move(name),
std::move(state),
std::move(extraId),
std::move(captureCallback),
std::move(abortCallback),
std::move(auxiliaryData),
RequestType::Image);
m_condition.notify_all();
}
void ImageCache::requestIcon(Utils::PathString name,
ImageCache::CaptureCallback captureCallback,
ImageCache::AbortCallback abortCallback,
Utils::SmallString state)
void AsynchronousImageCache::requestSmallImage(Utils::PathString name,
AsynchronousImageCache::CaptureImageCallback captureCallback,
AsynchronousImageCache::AbortCallback abortCallback,
Utils::SmallString extraId,
ImageCache::AuxiliaryData auxiliaryData)
{
addEntry(std::move(name),
std::move(state),
std::move(extraId),
std::move(captureCallback),
std::move(abortCallback),
RequestType::Icon);
std::move(auxiliaryData),
RequestType::SmallImage);
m_condition.notify_all();
}
void ImageCache::clean()
void AsynchronousImageCache::clean()
{
clearEntries();
m_generator.clean();
}
void ImageCache::waitForFinished()
void AsynchronousImageCache::waitForFinished()
{
wait();
m_generator.waitForFinished();
}
std::tuple<bool, ImageCache::Entry> ImageCache::getEntry()
std::tuple<bool, AsynchronousImageCache::Entry> AsynchronousImageCache::getEntry()
{
std::unique_lock lock{m_mutex};
@@ -153,22 +164,24 @@ std::tuple<bool, ImageCache::Entry> ImageCache::getEntry()
return {true, entry};
}
void ImageCache::addEntry(Utils::PathString &&name,
Utils::SmallString &&state,
ImageCache::CaptureCallback &&captureCallback,
AbortCallback &&abortCallback,
RequestType requestType)
void AsynchronousImageCache::addEntry(Utils::PathString &&name,
Utils::SmallString &&extraId,
AsynchronousImageCache::CaptureImageCallback &&captureCallback,
AbortCallback &&abortCallback,
ImageCache::AuxiliaryData &&auxiliaryData,
RequestType requestType)
{
std::unique_lock lock{m_mutex};
m_entries.emplace_back(std::move(name),
std::move(state),
std::move(extraId),
std::move(captureCallback),
std::move(abortCallback),
std::move(auxiliaryData),
requestType);
}
void ImageCache::clearEntries()
void AsynchronousImageCache::clearEntries()
{
std::unique_lock lock{m_mutex};
for (Entry &entry : m_entries)
@@ -176,20 +189,20 @@ void ImageCache::clearEntries()
m_entries.clear();
}
void ImageCache::waitForEntries()
void AsynchronousImageCache::waitForEntries()
{
std::unique_lock lock{m_mutex};
if (m_entries.empty())
m_condition.wait(lock, [&] { return m_entries.size() || m_finishing; });
}
void ImageCache::stopThread()
void AsynchronousImageCache::stopThread()
{
std::unique_lock lock{m_mutex};
m_finishing = true;
}
bool ImageCache::isRunning()
bool AsynchronousImageCache::isRunning()
{
std::unique_lock lock{m_mutex};
return !m_finishing || m_entries.size();

View File

@@ -68,6 +68,7 @@ ImageCacheCollector::~ImageCacheCollector() = default;
void ImageCacheCollector::start(Utils::SmallStringView name,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback,
AbortCallback abortCallback)
{
@@ -97,7 +98,15 @@ void ImageCacheCollector::start(Utils::SmallStringView name,
if (stateNode.isValid())
rewriterView.setCurrentStateNode(stateNode);
m_connectionManager.setCallback(std::move(captureCallback));
auto callback = [captureCallback = std::move(captureCallback)](QImage &&image) {
QSize smallImageSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()),
Qt::KeepAspectRatio);
QImage smallImage = image.isNull() ? QImage{} : image.scaled(smallImageSize);
captureCallback(std::move(image), std::move(smallImage));
};
m_connectionManager.setCallback(std::move(callback));
nodeInstanceView.setTarget(m_target.data());
nodeInstanceView.setCrashCallback(abortCallback);
@@ -115,6 +124,20 @@ void ImageCacheCollector::start(Utils::SmallStringView name,
abortCallback();
}
std::pair<QImage, QImage> ImageCacheCollector::createImage(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData)
{
return {};
}
QIcon ImageCacheCollector::createIcon(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData)
{
return {};
}
void ImageCacheCollector::setTarget(ProjectExplorer::Target *target)
{
m_target = target;

View File

@@ -54,9 +54,18 @@ public:
void start(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback,
AbortCallback abortCallback) override;
std::pair<QImage, QImage> createImage(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData) override;
QIcon createIcon(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData) override;
void setTarget(ProjectExplorer::Target *target);
private:

View File

@@ -25,6 +25,7 @@
#pragma once
#include <imagecacheauxiliarydata.h>
#include <utils/smallstringview.h>
#include <QImage>
@@ -34,15 +35,26 @@ namespace QmlDesigner {
class ImageCacheCollectorInterface
{
public:
using CaptureCallback = std::function<void(QImage &&image)>;
using CaptureCallback = std::function<void(QImage &&image, QImage &&smallImage)>;
using AbortCallback = std::function<void()>;
using ImagePair = std::pair<QImage, QImage>;
virtual void start(Utils::SmallStringView filePath,
Utils::SmallStringView state,
Utils::SmallStringView extraId,
const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback,
AbortCallback abortCallback)
= 0;
virtual ImagePair createImage(Utils::SmallStringView filePath,
Utils::SmallStringView extraId,
const ImageCache::AuxiliaryData &auxiliaryData)
= 0;
virtual QIcon createIcon(Utils::SmallStringView filePath,
Utils::SmallStringView extraId,
const ImageCache::AuxiliaryData &auxiliaryData)
= 0;
protected:
~ImageCacheCollectorInterface() = default;
};

View File

@@ -42,6 +42,8 @@ ImageCacheFontCollector::ImageCacheFontCollector() = default;
ImageCacheFontCollector::~ImageCacheFontCollector() = default;
namespace {
QByteArray fileToByteArray(QString const &filename)
{
QFile file(filename);
@@ -53,29 +55,17 @@ QByteArray fileToByteArray(QString const &filename)
return {};
}
} // namespace
void ImageCacheFontCollector::start(Utils::SmallStringView name,
Utils::SmallStringView state,
Utils::SmallStringView,
const ImageCache::AuxiliaryData &auxiliaryDataValue,
CaptureCallback captureCallback,
AbortCallback abortCallback)
{
// State contains size, text color, and sample text
QStringList hints = QString(state).split('@');
int dim(300);
if (hints.size() >= 1) {
bool ok = false;
int newDim = QString(hints[0]).toInt(&ok);
if (ok)
dim = newDim;
}
#ifndef QMLDESIGNER_TEST
QColor textColor(Theme::getColor(Theme::DStextColor));
#else
QColor textColor;
#endif
if (hints.size() >= 2)
textColor.setNamedColor(hints[1]);
QString text = hints.size() >= 3 ? hints[2] : "Abc";
QSize size(dim, dim);
auto &&auxiliaryData = std::get<ImageCache::FontCollectorSizeAuxiliaryData>(auxiliaryDataValue);
QColor textColor = auxiliaryData.colorName;
QSize size = auxiliaryData.size;
QRect rect({0, 0}, size);
QByteArray fontData(fileToByteArray(QString(name)));
@@ -86,8 +76,7 @@ void ImageCacheFontCollector::start(Utils::SmallStringView name,
const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
if (!families.isEmpty()) {
QString fontFamily = families.first();
if (text.contains("%1"))
text = text.arg(fontFamily);
QString text = fontFamily + "\n\n" + auxiliaryData.text;
QFont font(fontFamily);
font.setStyle(rawFont.style());
font.setStyleName(rawFont.styleName());
@@ -120,7 +109,7 @@ void ImageCacheFontCollector::start(Utils::SmallStringView name,
painter.setFont(font);
painter.drawText(rect, flags, text);
captureCallback(std::move(image));
captureCallback(std::move(image), {});
return;
}
QFontDatabase::removeApplicationFont(fontId);
@@ -129,4 +118,133 @@ void ImageCacheFontCollector::start(Utils::SmallStringView name,
abortCallback();
}
std::pair<QImage, QImage> ImageCacheFontCollector::createImage(
Utils::SmallStringView name,
Utils::SmallStringView,
const ImageCache::AuxiliaryData &auxiliaryDataValue)
{
auto &&auxiliaryData = std::get<ImageCache::FontCollectorSizeAuxiliaryData>(auxiliaryDataValue);
QColor textColor = auxiliaryData.colorName;
QSize size = auxiliaryData.size;
QRect rect({0, 0}, size);
QByteArray fontData(fileToByteArray(QString(name)));
if (!fontData.isEmpty()) {
int fontId = QFontDatabase::addApplicationFontFromData(fontData);
if (fontId != -1) {
QRawFont rawFont(fontData, 10.); // Pixel size is irrelevant, we only need style/weight
const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
if (!families.isEmpty()) {
QString fontFamily = families.first();
QString text = fontFamily + "\n\n" + auxiliaryData.text;
QFont font(fontFamily);
font.setStyle(rawFont.style());
font.setStyleName(rawFont.styleName());
font.setWeight(rawFont.weight());
QImage image(size, QImage::Format_ARGB32);
image.fill(Qt::transparent);
int pixelSize(200);
int flags = Qt::AlignCenter;
while (pixelSize >= 2) {
font.setPixelSize(pixelSize);
QFontMetrics fm(font, &image);
QRect bounds = fm.boundingRect(rect, flags, text);
if (bounds.width() < rect.width() && bounds.height() < rect.height()) {
break;
} else {
int newPixelSize = pixelSize - 1;
if (bounds.width() >= rect.width())
newPixelSize = int(qreal(pixelSize) * qreal(rect.width())
/ qreal(bounds.width()));
else if (bounds.height() >= rect.height())
newPixelSize = int(qreal(pixelSize) * qreal(rect.height())
/ qreal(bounds.height()));
if (newPixelSize < pixelSize)
pixelSize = newPixelSize;
else
--pixelSize;
}
}
QPainter painter(&image);
painter.setPen(textColor);
painter.setFont(font);
painter.drawText(rect, flags, text);
return {image, {}};
}
QFontDatabase::removeApplicationFont(fontId);
}
}
return {};
}
QIcon ImageCacheFontCollector::createIcon(Utils::SmallStringView name,
Utils::SmallStringView,
const ImageCache::AuxiliaryData &auxiliaryDataValue)
{
auto &&auxiliaryData = std::get<ImageCache::FontCollectorSizesAuxiliaryData>(auxiliaryDataValue);
QColor textColor = auxiliaryData.colorName;
auto sizes = auxiliaryData.sizes;
QIcon icon;
QByteArray fontData(fileToByteArray(QString(name)));
if (!fontData.isEmpty()) {
int fontId = QFontDatabase::addApplicationFontFromData(fontData);
if (fontId != -1) {
QRawFont rawFont(fontData, 10.); // Pixel size is irrelevant, we only need style/weight
const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
if (!families.isEmpty()) {
QString fontFamily = families.first();
QString text = auxiliaryData.text;
QFont font(fontFamily);
font.setStyle(rawFont.style());
font.setStyleName(rawFont.styleName());
font.setWeight(rawFont.weight());
for (QSize size : sizes) {
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
int pixelSize(200);
int flags = Qt::AlignCenter;
QRect rect({0, 0}, size);
while (pixelSize >= 2) {
font.setPixelSize(pixelSize);
QFontMetrics fm(font, &pixmap);
QRect bounds = fm.boundingRect(rect, flags, text);
if (bounds.width() < rect.width() && bounds.height() < rect.height()) {
break;
} else {
int newPixelSize = pixelSize - 1;
if (bounds.width() >= rect.width())
newPixelSize = int(qreal(pixelSize) * qreal(rect.width())
/ qreal(bounds.width()));
else if (bounds.height() >= rect.height())
newPixelSize = int(qreal(pixelSize) * qreal(rect.height())
/ qreal(bounds.height()));
if (newPixelSize < pixelSize)
pixelSize = newPixelSize;
else
--pixelSize;
}
}
QPainter painter(&pixmap);
painter.setPen(textColor);
painter.setFont(font);
painter.drawText(rect, flags, text);
icon.addPixmap(pixmap);
}
} else {
QFontDatabase::removeApplicationFont(fontId);
}
}
}
return icon;
}
} // namespace QmlDesigner

View File

@@ -37,9 +37,18 @@ public:
~ImageCacheFontCollector();
void start(Utils::SmallStringView filePath,
Utils::SmallStringView state,
Utils::SmallStringView extraId,
const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback,
AbortCallback abortCallback) override;
std::pair<QImage, QImage> createImage(Utils::SmallStringView filePath,
Utils::SmallStringView extraId,
const ImageCache::AuxiliaryData &auxiliaryData) override;
QIcon createIcon(Utils::SmallStringView filePath,
Utils::SmallStringView extraId,
const ImageCache::AuxiliaryData &auxiliaryData) override;
};
} // namespace QmlDesigner

View File

@@ -47,17 +47,18 @@ ImageCacheGenerator::~ImageCacheGenerator()
waitForFinished();
}
void ImageCacheGenerator::generateImage(
Utils::SmallStringView name,
Utils::SmallStringView state,
Sqlite::TimeStamp timeStamp,
ImageCacheGeneratorInterface::CaptureCallback &&captureCallback,
AbortCallback &&abortCallback)
void ImageCacheGenerator::generateImage(Utils::SmallStringView name,
Utils::SmallStringView extraId,
Sqlite::TimeStamp timeStamp,
ImageCacheGeneratorInterface::CaptureCallback &&captureCallback,
AbortCallback &&abortCallback,
ImageCache::AuxiliaryData &&auxiliaryData)
{
{
std::lock_guard lock{m_mutex};
m_tasks.emplace_back(name,
state,
extraId,
std::move(auxiliaryData),
timeStamp,
std::move(captureCallback),
std::move(abortCallback));
@@ -82,6 +83,12 @@ void ImageCacheGenerator::waitForFinished()
if (m_backgroundThread)
m_backgroundThread->wait();
}
namespace {
Utils::PathString createId(Utils::SmallStringView name, Utils::SmallStringView extraId)
{
return extraId.empty() ? Utils::PathString{name} : Utils::PathString{name, "+", extraId};
}
} // namespace
void ImageCacheGenerator::startGeneration()
{
@@ -105,18 +112,22 @@ void ImageCacheGenerator::startGeneration()
m_collector.start(
task.filePath,
task.state,
[this, task](QImage &&image) {
task.extraId,
std::move(task.auxiliaryData),
[this, task](QImage &&image, QImage &&smallImage) {
if (image.isNull())
task.abortCallback();
else
task.captureCallback(image);
task.captureCallback(image, smallImage);
m_storage.storeImage(std::move(task.filePath), task.timeStamp, image);
m_storage.storeImage(createId(task.filePath, task.extraId),
task.timeStamp,
image,
smallImage);
},
[this, task] {
task.abortCallback();
m_storage.storeImage(std::move(task.filePath), task.timeStamp, {});
m_storage.storeImage(createId(task.filePath, task.extraId), task.timeStamp, {}, {});
});
std::lock_guard lock{m_mutex};

View File

@@ -27,6 +27,7 @@
#include "imagecachegeneratorinterface.h"
#include <imagecacheauxiliarydata.h>
#include <utils/smallstring.h>
#include <QThread>
@@ -51,10 +52,11 @@ public:
~ImageCacheGenerator();
void generateImage(Utils::SmallStringView filePath,
Utils::SmallStringView state,
Utils::SmallStringView extraId,
Sqlite::TimeStamp timeStamp,
CaptureCallback &&captureCallback,
AbortCallback &&abortCallback) override;
AbortCallback &&abortCallback,
ImageCache::AuxiliaryData &&auxiliaryData) override;
void clean() override;
void waitForFinished() override;
@@ -64,19 +66,22 @@ private:
{
Task() = default;
Task(Utils::SmallStringView filePath,
Utils::SmallStringView state,
Utils::SmallStringView extraId,
ImageCache::AuxiliaryData &&auxiliaryData,
Sqlite::TimeStamp timeStamp,
CaptureCallback &&captureCallback,
AbortCallback &&abortCallback)
: filePath(filePath)
, state(std::move(state))
, extraId(std::move(extraId))
, auxiliaryData(std::move(auxiliaryData))
, captureCallback(std::move(captureCallback))
, abortCallback(std::move(abortCallback))
, timeStamp(timeStamp)
{}
Utils::PathString filePath;
Utils::SmallString state;
Utils::SmallString extraId;
ImageCache::AuxiliaryData auxiliaryData;
CaptureCallback captureCallback;
AbortCallback abortCallback;
Sqlite::TimeStamp timeStamp;

View File

@@ -25,6 +25,7 @@
#pragma once
#include <imagecacheauxiliarydata.h>
#include <sqlitetimestamp.h>
#include <utils/smallstringview.h>
@@ -35,14 +36,15 @@ namespace QmlDesigner {
class ImageCacheGeneratorInterface
{
public:
using CaptureCallback = std::function<void(const QImage &image)>;
using CaptureCallback = std::function<void(const QImage &image, const QImage &smallImage)>;
using AbortCallback = std::function<void()>;
virtual void generateImage(Utils::SmallStringView name,
Utils::SmallStringView state,
Utils::SmallStringView extraId,
Sqlite::TimeStamp timeStamp,
CaptureCallback &&captureCallback,
AbortCallback &&abortCallback)
AbortCallback &&abortCallback,
ImageCache::AuxiliaryData &&auxiliaryData)
= 0;
virtual void clean() = 0;

View File

@@ -34,8 +34,8 @@
#include <sqlitewritestatement.h>
#include <QBuffer>
#include <QImageReader>
#include <QImageWriter>
#include <QIcon>
#include <QImage>
namespace QmlDesigner {
@@ -52,7 +52,7 @@ public:
transaction.commit();
}
Entry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override
ImageEntry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override
{
try {
Sqlite::DeferredTransaction transaction{database};
@@ -62,21 +62,37 @@ public:
transaction.commit();
if (optionalBlob) {
QBuffer buffer{&optionalBlob->byteArray};
QImageReader reader{&buffer, "PNG"};
return Entry{reader.read(), true};
}
if (optionalBlob)
return {readImage(optionalBlob->byteArray), true};
return {};
} catch (const Sqlite::StatementIsBusy &) {
return fetchImage(name, minimumTimeStamp);
}
}
Entry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override
ImageEntry fetchSmallImage(Utils::SmallStringView name,
Sqlite::TimeStamp minimumTimeStamp) const override
{
try {
Sqlite::DeferredTransaction transaction{database};
auto optionalBlob = selectSmallImageStatement.template value<Sqlite::ByteArrayBlob>(
name, minimumTimeStamp.value);
transaction.commit();
if (optionalBlob)
return ImageEntry{readImage(optionalBlob->byteArray), true};
return {};
} catch (const Sqlite::StatementIsBusy &) {
return fetchSmallImage(name, minimumTimeStamp);
}
}
IconEntry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override
{
try {
Sqlite::DeferredTransaction transaction{database};
@@ -86,12 +102,8 @@ public:
transaction.commit();
if (optionalBlob) {
QBuffer buffer{&optionalBlob->byteArray};
QImageReader reader{&buffer, "PNG"};
return Entry{reader.read(), true};
}
if (optionalBlob)
return {readIcon(optionalBlob->byteArray), true};
return {};
@@ -100,29 +112,40 @@ public:
}
}
void storeImage(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image) override
void storeImage(Utils::SmallStringView name,
Sqlite::TimeStamp newTimeStamp,
const QImage &image,
const QImage &smallImage) override
{
try {
Sqlite::ImmediateTransaction transaction{database};
if (image.isNull()) {
upsertImageStatement.write(name,
newTimeStamp.value,
Sqlite::NullValue{},
Sqlite::NullValue{});
} else {
QSize iconSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()),
Qt::KeepAspectRatio);
QImage icon = image.scaled(iconSize);
upsertImageStatement.write(name,
newTimeStamp.value,
Sqlite::BlobView{createImageBuffer(image)->data()},
Sqlite::BlobView{createImageBuffer(icon)->data()});
}
auto imageBuffer = createBuffer(image);
auto smallImageBuffer = createBuffer(smallImage);
upsertImageStatement.write(name,
newTimeStamp.value,
createBlobView(imageBuffer.get()),
createBlobView(smallImageBuffer.get()));
transaction.commit();
} catch (const Sqlite::StatementIsBusy &) {
return storeImage(name, newTimeStamp, image);
return storeImage(name, newTimeStamp, image, smallImage);
}
}
void storeIcon(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QIcon &icon)
{
try {
Sqlite::ImmediateTransaction transaction{database};
auto iconBuffer = createBuffer(icon);
upsertIconStatement.write(name, newTimeStamp.value, createBlobView(iconBuffer.get()));
transaction.commit();
} catch (const Sqlite::StatementIsBusy &) {
return storeIcon(name, newTimeStamp, icon);
}
}
@@ -156,41 +179,113 @@ private:
void createImagesTable(DatabaseType &database)
{
Sqlite::Table table;
table.setUseIfNotExists(true);
table.setName("images");
table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}});
table.addColumn("name", Sqlite::ColumnType::Text, {Sqlite::NotNull{}, Sqlite::Unique{}});
table.addColumn("mtime", Sqlite::ColumnType::Integer);
table.addColumn("image", Sqlite::ColumnType::Blob);
table.addColumn("icon", Sqlite::ColumnType::Blob);
Sqlite::Table imageTable;
imageTable.setUseIfNotExists(true);
imageTable.setName("images");
imageTable.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}});
imageTable.addColumn("name",
Sqlite::ColumnType::Text,
{Sqlite::NotNull{}, Sqlite::Unique{}});
imageTable.addColumn("mtime", Sqlite::ColumnType::Integer);
imageTable.addColumn("image", Sqlite::ColumnType::Blob);
imageTable.addColumn("smallImage", Sqlite::ColumnType::Blob);
table.initialize(database);
imageTable.initialize(database);
Sqlite::Table iconTable;
iconTable.setUseIfNotExists(true);
iconTable.setName("icons");
iconTable.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}});
iconTable.addColumn("name",
Sqlite::ColumnType::Text,
{Sqlite::NotNull{}, Sqlite::Unique{}});
iconTable.addColumn("mtime", Sqlite::ColumnType::Integer);
iconTable.addColumn("icon", Sqlite::ColumnType::Blob);
iconTable.initialize(database);
}
};
std::unique_ptr<QBuffer> createImageBuffer(const QImage &image)
Sqlite::BlobView createBlobView(QBuffer *buffer)
{
if (buffer)
return Sqlite::BlobView{buffer->data()};
return {};
}
static std::unique_ptr<QBuffer> createBuffer(const QImage &image)
{
if (image.isNull())
return {};
auto buffer = std::make_unique<QBuffer>();
buffer->open(QIODevice::WriteOnly);
QImageWriter writer{buffer.get(), "PNG"};
writer.write(image);
QDataStream out{buffer.get()};
out << image;
return buffer;
}
static std::unique_ptr<QBuffer> createBuffer(const QIcon &icon)
{
if (icon.isNull())
return {};
auto buffer = std::make_unique<QBuffer>();
buffer->open(QIODevice::WriteOnly);
QDataStream out{buffer.get()};
out << icon;
return buffer;
}
static QIcon readIcon(const QByteArray &byteArray)
{
QIcon icon;
QBuffer buffer;
buffer.setData(byteArray);
buffer.open(QIODevice::ReadOnly);
QDataStream in{&buffer};
in >> icon;
return icon;
}
static QImage readImage(const QByteArray &byteArray)
{
QImage image;
QBuffer buffer;
buffer.setData(byteArray);
buffer.open(QIODevice::ReadOnly);
QDataStream in{&buffer};
in >> image;
return image;
}
public:
DatabaseType &database;
Initializer initializer{database};
Sqlite::ImmediateNonThrowingDestructorTransaction transaction{database};
mutable ReadStatement selectImageStatement{
"SELECT image FROM images WHERE name=?1 AND mtime >= ?2", database};
mutable ReadStatement selectSmallImageStatement{
"SELECT smallImage FROM images WHERE name=?1 AND mtime >= ?2", database};
mutable ReadStatement selectIconStatement{
"SELECT icon FROM images WHERE name=?1 AND mtime >= ?2", database};
"SELECT icon FROM icons WHERE name=?1 AND mtime >= ?2", database};
WriteStatement upsertImageStatement{
"INSERT INTO images(name, mtime, image, icon) VALUES (?1, ?2, ?3, ?4) ON "
"INSERT INTO images(name, mtime, image, smallImage) VALUES (?1, ?2, ?3, ?4) ON "
"CONFLICT(name) DO UPDATE SET mtime=excluded.mtime, image=excluded.image, "
"icon=excluded.icon",
"smallImage=excluded.smallImage",
database};
WriteStatement upsertIconStatement{
"INSERT INTO icons(name, mtime, icon) VALUES (?1, ?2, ?3) ON "
"CONFLICT(name) DO UPDATE SET mtime=excluded.mtime, icon=excluded.icon",
database};
};

View File

@@ -25,6 +25,7 @@
#pragma once
#include <QIcon>
#include <QImage>
#include <sqlitetimestamp.h>
@@ -32,23 +33,40 @@
namespace QmlDesigner {
namespace Internal {
class ImageCacheStorageEntry
class ImageCacheStorageImageEntry
{
public:
public:
QImage image;
bool hasEntry = false;
};
class ImageCacheStorageIconEntry
{
public:
QIcon icon;
bool hasEntry = false;
};
} // namespace Internal
class ImageCacheStorageInterface
{
public:
using Entry = Internal::ImageCacheStorageEntry;
using ImageEntry = Internal::ImageCacheStorageImageEntry;
using IconEntry = Internal::ImageCacheStorageIconEntry;
virtual Entry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const = 0;
virtual Entry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const = 0;
virtual void storeImage(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image) = 0;
virtual ImageEntry fetchImage(Utils::SmallStringView name,
Sqlite::TimeStamp minimumTimeStamp) const = 0;
virtual ImageEntry fetchSmallImage(Utils::SmallStringView name,
Sqlite::TimeStamp minimumTimeStamp) const = 0;
virtual IconEntry fetchIcon(Utils::SmallStringView name,
Sqlite::TimeStamp minimumTimeStamp) const = 0;
virtual void storeImage(Utils::SmallStringView name,
Sqlite::TimeStamp newTimeStamp,
const QImage &image,
const QImage &smallImage)
= 0;
virtual void storeIcon(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QIcon &icon) = 0;
virtual void walCheckpointFull() = 0;
protected:

View File

@@ -0,0 +1,101 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/
#include "synchronousimagecache.h"
#include "imagecachecollectorinterface.h"
#include "imagecachestorage.h"
#include "timestampprovider.h"
#include <thread>
namespace QmlDesigner {
namespace {
Utils::PathString createId(Utils::PathString filePath, Utils::SmallString extraId)
{
return extraId.empty() ? Utils::PathString{filePath} : Utils::PathString{filePath, "+", extraId};
}
} // namespace
QImage SynchronousImageCache::image(Utils::PathString filePath,
Utils::SmallString extraId,
const ImageCache::AuxiliaryData &auxiliaryData)
{
const auto id = createId(filePath, extraId);
const auto timeStamp = m_timeStampProvider.timeStamp(filePath);
const auto entry = m_storage.fetchImage(id, timeStamp);
if (entry.hasEntry)
return entry.image;
const auto &[image, smallImage] = m_collector.createImage(filePath, extraId, auxiliaryData);
m_storage.storeImage(id, timeStamp, image, smallImage);
return image;
}
QImage SynchronousImageCache::smallImage(Utils::PathString filePath,
Utils::SmallString extraId,
const ImageCache::AuxiliaryData &auxiliaryData)
{
const auto id = createId(filePath, extraId);
const auto timeStamp = m_timeStampProvider.timeStamp(filePath);
const auto entry = m_storage.fetchSmallImage(id, timeStamp);
if (entry.hasEntry)
return entry.image;
const auto &[image, smallImage] = m_collector.createImage(filePath, extraId, auxiliaryData);
m_storage.storeImage(id, timeStamp, image, smallImage);
return smallImage;
}
QIcon SynchronousImageCache::icon(Utils::PathString filePath,
Utils::SmallString extraId,
const ImageCache::AuxiliaryData &auxiliaryData)
{
const auto id = createId(filePath, extraId);
const auto timeStamp = m_timeStampProvider.timeStamp(filePath);
const auto entry = m_storage.fetchIcon(id, timeStamp);
if (entry.hasEntry)
return entry.icon;
const auto icon = m_collector.createIcon(filePath, extraId, auxiliaryData);
m_storage.storeIcon(id, timeStamp, icon);
return icon;
}
} // namespace QmlDesigner

View File

@@ -25,7 +25,7 @@
#pragma once
#include "imagecacheinterface.h"
#include "asynchronousimagecacheinterface.h"
#include <condition_variable>
#include <functional>
@@ -37,70 +37,78 @@ namespace QmlDesigner {
class TimeStampProviderInterface;
class ImageCacheStorageInterface;
class ImageCacheGeneratorInterface;
class ImageCacheCollectorInterface;
class ImageCache final : public ImageCacheInterface
class AsynchronousImageCache final : public AsynchronousImageCacheInterface
{
public:
using CaptureCallback = std::function<void(const QImage &)>;
using CaptureImageCallback = std::function<void(const QImage &)>;
using AbortCallback = std::function<void()>;
~ImageCache();
~AsynchronousImageCache();
ImageCache(ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider);
AsynchronousImageCache(ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider);
void requestImage(Utils::PathString name,
CaptureCallback captureCallback,
CaptureImageCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString state = {}) override;
void requestIcon(Utils::PathString name,
CaptureCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString state = {}) override;
Utils::SmallString extraId = {},
ImageCache::AuxiliaryData auxiliaryData = {}) override;
void requestSmallImage(Utils::PathString name,
CaptureImageCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString extraId = {},
ImageCache::AuxiliaryData auxiliaryData = {}) override;
void clean();
void waitForFinished();
private:
enum class RequestType { Image, Icon };
enum class RequestType { Image, SmallImage, Icon };
struct Entry
{
Entry() = default;
Entry(Utils::PathString name,
Utils::SmallString state,
CaptureCallback &&captureCallback,
Utils::SmallString extraId,
CaptureImageCallback &&captureCallback,
AbortCallback &&abortCallback,
ImageCache::AuxiliaryData &&auxiliaryData,
RequestType requestType)
: name{std::move(name)}
, state{std::move(state)}
, extraId{std::move(extraId)}
, captureCallback{std::move(captureCallback)}
, abortCallback{std::move(abortCallback)}
, auxiliaryData{std::move(auxiliaryData)}
, requestType{requestType}
{}
Utils::PathString name;
Utils::SmallString state;
CaptureCallback captureCallback;
Utils::SmallString extraId;
CaptureImageCallback captureCallback;
AbortCallback abortCallback;
ImageCache::AuxiliaryData auxiliaryData;
RequestType requestType = RequestType::Image;
};
std::tuple<bool, Entry> getEntry();
void addEntry(Utils::PathString &&name,
Utils::SmallString &&state,
CaptureCallback &&captureCallback,
Utils::SmallString &&extraId,
CaptureImageCallback &&captureCallback,
AbortCallback &&abortCallback,
ImageCache::AuxiliaryData &&auxiliaryData,
RequestType requestType);
void clearEntries();
void waitForEntries();
void stopThread();
bool isRunning();
static void request(Utils::SmallStringView name,
Utils::SmallStringView state,
ImageCache::RequestType requestType,
ImageCache::CaptureCallback captureCallback,
ImageCache::AbortCallback abortCallback,
Utils::SmallStringView extraId,
AsynchronousImageCache::RequestType requestType,
AsynchronousImageCache::CaptureImageCallback captureCallback,
AsynchronousImageCache::AbortCallback abortCallback,
ImageCache::AuxiliaryData auxiliaryData,
ImageCacheStorageInterface &storage,
ImageCacheGeneratorInterface &generator,
TimeStampProviderInterface &timeStampProvider);

View File

@@ -25,13 +25,15 @@
#pragma once
#include "imagecacheauxiliarydata.h"
#include <utils/smallstring.h>
#include <QImage>
namespace QmlDesigner {
class ImageCacheInterface
class AsynchronousImageCacheInterface
{
public:
using CaptureCallback = std::function<void(const QImage &)>;
@@ -40,18 +42,20 @@ public:
virtual void requestImage(Utils::PathString name,
CaptureCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString state = {})
Utils::SmallString extraId = {},
ImageCache::AuxiliaryData auxiliaryData = {})
= 0;
virtual void requestIcon(Utils::PathString name,
CaptureCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString state = {})
virtual void requestSmallImage(Utils::PathString name,
CaptureCallback captureCallback,
AbortCallback abortCallback,
Utils::SmallString extraId = {},
ImageCache::AuxiliaryData auxiliaryData = {})
= 0;
void clean();
protected:
~ImageCacheInterface() = default;
~AsynchronousImageCacheInterface() = default;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,61 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/
#pragma once
#include <utils/span.h>
#include <utils/variant.h>
#include <QSize>
#include <QString>
namespace QmlDesigner {
namespace ImageCache {
class NullAuxiliaryData
{};
class FontCollectorSizeAuxiliaryData
{
public:
QSize size;
QString colorName;
QString text;
};
class FontCollectorSizesAuxiliaryData
{
public:
Utils::span<const QSize> sizes;
QString colorName;
QString text;
};
using AuxiliaryData = std::variant<NullAuxiliaryData, FontCollectorSizeAuxiliaryData, FontCollectorSizesAuxiliaryData>;
} // namespace ImageCache
} // namespace QmlDesigner

View File

@@ -0,0 +1,76 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/
#pragma once
#include "imagecacheauxiliarydata.h"
#include <utils/smallstring.h>
#include <QImage>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
namespace QmlDesigner {
class TimeStampProviderInterface;
class ImageCacheStorageInterface;
class ImageCacheGeneratorInterface;
class ImageCacheCollectorInterface;
class SynchronousImageCache
{
public:
using CaptureImageCallback = std::function<void(const QImage &)>;
using AbortCallback = std::function<void()>;
SynchronousImageCache(ImageCacheStorageInterface &storage,
TimeStampProviderInterface &timeStampProvider,
ImageCacheCollectorInterface &collector)
: m_storage(storage)
, m_timeStampProvider(timeStampProvider)
, m_collector(collector)
{}
QImage image(Utils::PathString filePath,
Utils::SmallString extraId = {},
const ImageCache::AuxiliaryData &auxiliaryData = {});
QImage smallImage(Utils::PathString filePath,
Utils::SmallString extraId = {},
const ImageCache::AuxiliaryData &auxiliaryData = {});
QIcon icon(Utils::PathString filePath,
Utils::SmallString extraId = {},
const ImageCache::AuxiliaryData &auxiliaryData = {});
private:
ImageCacheStorageInterface &m_storage;
TimeStampProviderInterface &m_timeStampProvider;
ImageCacheCollectorInterface &m_collector;
};
} // namespace QmlDesigner

View File

@@ -47,7 +47,7 @@ class Edit3DView;
namespace Internal { class DesignModeWidget; }
class ViewManagerData;
class ImageCache;
class AsynchronousImageCache;
class QMLDESIGNERCORE_EXPORT ViewManager
{
@@ -104,7 +104,7 @@ public:
void disableStandardViews();
void enableStandardViews();
ImageCache &imageCache();
AsynchronousImageCache &imageCache();
private: // functions
Q_DISABLE_COPY(ViewManager)

View File

@@ -453,7 +453,7 @@ void ViewManager::enableStandardViews()
attachViewsExceptRewriterAndComponetView();
}
ImageCache &ViewManager::imageCache()
AsynchronousImageCache &ViewManager::imageCache()
{
return d->itemLibraryView.imageCache();
}

View File

@@ -567,7 +567,7 @@ void QmlDesignerPlugin::emitUsageStatisticsContextAction(const QString &identifi
emitUsageStatistics(Constants::EVENT_ACTION_EXECUTED + identifier);
}
ImageCache &QmlDesignerPlugin::imageCache()
AsynchronousImageCache &QmlDesignerPlugin::imageCache()
{
return m_instance->d->viewManager.imageCache();
}

View File

@@ -42,7 +42,7 @@ namespace Core {
namespace QmlDesigner {
class QmlDesignerPluginPrivate;
class ImageCache;
class AsynchronousImageCache;
namespace Internal { class DesignModeWidget; }
@@ -87,7 +87,7 @@ public:
static void emitUsageStatisticsContextAction(const QString &identifier);
static void emitUsageStatisticsTime(const QString &identifier, int elapsed);
static ImageCache &imageCache();
static AsynchronousImageCache &imageCache();
signals:
void usageStatisticsNotifier(const QString &identifier);

View File

@@ -414,13 +414,15 @@ Project {
"pluginmanager/widgetpluginmanager.h",
"pluginmanager/widgetpluginpath.cpp",
"pluginmanager/widgetpluginpath.h",
"include/imagecache.h",
"include/imagecacheinterface.h",
"include/asynchronousimagecache.h",
"include/synchronousimagecache.h",
"include/imagecacheauxiliarydata.h",
"include/asynchronousimagecacheinterface.h",
"imagecache/imagecachecollector.cpp",
"imagecache/imagecachecollector.h",
"imagecache/imagecachefontcollector.cpp",
"imagecache/imagecachefontcollector.h",
"imagecache/imagecache.cpp",
"imagecache/asynchronousimagecache.cpp",
"imagecache/imagecachecollectorinterface.h",
"imagecache/imagecacheconnectionmanager.cpp",
"imagecache/imagecacheconnectionmanager.h",

View File

@@ -32,7 +32,8 @@ SOURCES += \
$$PWD/designercore/model/annotation.cpp \
$$PWD/designercore/rewritertransaction.cpp \
$$PWD/components/listmodeleditor/listmodeleditormodel.cpp \
$$PWD/designercore/imagecache/imagecache.cpp \
$$PWD/designercore/imagecache/asynchronousimagecache.cpp \
$$PWD/designercore/imagecache/synchronousimagecache.cpp \
$$PWD/designercore/imagecache/imagecachegenerator.cpp
HEADERS += \
@@ -42,8 +43,10 @@ HEADERS += \
$$PWD/designercore/imagecache/imagecachestorageinterface.h \
$$PWD/designercore/imagecache/imagecachegeneratorinterface.h \
$$PWD/designercore/imagecache/timestampproviderinterface.h \
$$PWD/designercore/include/imagecache.h \
$$PWD/designercore/include/imagecacheinterface.h \
$$PWD/designercore/include/asynchronousimagecache.h \
$$PWD/designercore/include/synchronousimagecache.h \
$$PWD/designercore/include/imagecacheauxiliarydata.h \
$$PWD/designercore/include/asynchronousimagecacheinterface.h \
$$PWD/designercore/include/modelnode.h \
$$PWD/designercore/include/model.h \
$$PWD/../../../share/qtcreator/qml/qmlpuppet/interfaces/commondefines.h \

View File

@@ -179,7 +179,8 @@ add_qtc_test(unittest GTEST
sqlstatementbuilder-test.cpp
createtablesqlstatementbuilder-test.cpp
sqlitevalue-test.cpp
imagecache-test.cpp
asynchronousimagecache-test.cpp
synchronousimagecache-test.cpp
imagecachegenerator-test.cpp
imagecachestorage-test.cpp
sqlitedatabasemock.h
@@ -405,8 +406,10 @@ extend_qtc_test(unittest
model/signalhandlerproperty.cpp include/signalhandlerproperty.h
model/variantproperty.cpp include/variantproperty.h
rewritertransaction.cpp rewritertransaction.h
imagecache/imagecache.cpp include/imagecache.h
include/imagecacheinterface.h
include/imagecacheauxiliarydata.h
imagecache/synchronousimagecache.cpp include/synchronousimagecache.h
imagecache/asynchronousimagecache.cpp include/asynchronousimagecache.h
include/asynchronousimagecacheinterface.h
imagecache/imagecachecollectorinterface.h
imagecache/imagecachegenerator.cpp imagecache/imagecachegenerator.h
imagecache/imagecachegeneratorinterface.h

View File

@@ -0,0 +1,467 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/
#include "googletest.h"
#include "mockimagecachegenerator.h"
#include "mockimagecachestorage.h"
#include "mocktimestampprovider.h"
#include "notification.h"
#include <asynchronousimagecache.h>
namespace {
class AsynchronousImageCache : public testing::Test
{
protected:
Notification notification;
Notification waitInThread;
NiceMock<MockFunction<void()>> mockAbortCallback;
NiceMock<MockFunction<void(const QImage &image)>> mockCaptureCallback;
NiceMock<MockImageCacheStorage> mockStorage;
NiceMock<MockImageCacheGenerator> mockGenerator;
NiceMock<MockTimeStampProvider> mockTimeStampProvider;
QmlDesigner::AsynchronousImageCache cache{mockStorage, mockGenerator, mockTimeStampProvider};
QImage image1{10, 10, QImage::Format_ARGB32};
QImage smallImage1{1, 1, QImage::Format_ARGB32};
};
TEST_F(AsynchronousImageCache, RequestImageFetchesImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::ImageEntry{{}, false};
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageFetchesImageFromStorageWithTimeStamp)
{
EXPECT_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillRepeatedly(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::ImageEntry{QImage{}, false};
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageCallsCaptureCallbackWithImageFromStorage)
{
ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{image1, true}));
EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))).WillRepeatedly([&](const QImage &) {
notification.notify();
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageCallsAbortCallbackWithoutImage)
{
ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{QImage{}, true}));
EXPECT_CALL(mockAbortCallback, Call()).WillRepeatedly([&] { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&callback, auto, auto) { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageCallsCaptureCallbackWithImageFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto) {
callback(QImage{image1}, QImage{smallImage1});
notification.notify();
});
EXPECT_CALL(mockCaptureCallback, Call(Eq(image1)));
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageCallsAbortCallbackFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto) {
abortCallback();
notification.notify();
});
EXPECT_CALL(mockAbortCallback, Call());
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageFetchesSmallImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchSmallImage(Eq("/path/to/Component.qml"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::ImageEntry{{}, false};
});
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageFetchesSmallImageFromStorageWithTimeStamp)
{
EXPECT_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillRepeatedly(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockStorage, fetchSmallImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::ImageEntry{QImage{}, false};
});
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageCallsCaptureCallbackWithImageFromStorage)
{
ON_CALL(mockStorage, fetchSmallImage(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{smallImage1, true}));
EXPECT_CALL(mockCaptureCallback, Call(Eq(smallImage1))).WillRepeatedly([&](const QImage &) {
notification.notify();
});
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageCallsAbortCallbackWithoutSmallImage)
{
ON_CALL(mockStorage, fetchSmallImage(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{QImage{}, true}));
EXPECT_CALL(mockAbortCallback, Call()).WillRepeatedly([&] { notification.notify(); });
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&callback, auto, auto) { notification.notify(); });
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageCallsCaptureCallbackWithImageFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&callback, auto, auto) {
callback(QImage{image1}, QImage{smallImage1});
notification.notify();
});
EXPECT_CALL(mockCaptureCallback, Call(Eq(smallImage1)));
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageCallsAbortCallbackFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback, auto) {
abortCallback();
notification.notify();
});
EXPECT_CALL(mockAbortCallback, Call());
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, CleanRemovesEntries)
{
EXPECT_CALL(mockGenerator, generateImage(_, _, _, _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&captureCallback, auto &&, auto) {
captureCallback(QImage{}, QImage{});
waitInThread.wait();
});
cache.requestSmallImage("/path/to/Component1.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
EXPECT_CALL(mockCaptureCallback, Call(_)).Times(AtMost(1));
cache.requestSmallImage("/path/to/Component3.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.clean();
waitInThread.notify();
}
TEST_F(AsynchronousImageCache, CleanCallsAbort)
{
ON_CALL(mockGenerator, generateImage(_, _, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&mockCaptureCallback, auto &&, auto) {
waitInThread.wait();
});
cache.requestSmallImage("/path/to/Component1.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.requestSmallImage("/path/to/Component2.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
EXPECT_CALL(mockAbortCallback, Call()).Times(AtLeast(2));
cache.requestSmallImage("/path/to/Component3.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.clean();
waitInThread.notify();
}
TEST_F(AsynchronousImageCache, CleanCallsGeneratorClean)
{
EXPECT_CALL(mockGenerator, clean()).Times(AtLeast(1));
cache.clean();
}
TEST_F(AsynchronousImageCache, AfterCleanNewJobsWorks)
{
cache.clean();
EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&, auto &&, auto) { notification.notify(); });
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(AsynchronousImageCache, WaitForFinished)
{
ON_CALL(mockStorage, fetchImage(_, _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{image1, true}));
cache.requestImage("/path/to/Component1.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.requestImage("/path/to/Component2.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
EXPECT_CALL(mockCaptureCallback, Call(_)).Times(2);
cache.waitForFinished();
}
TEST_F(AsynchronousImageCache, WaitForFinishedInGenerator)
{
EXPECT_CALL(mockGenerator, waitForFinished());
cache.waitForFinished();
}
TEST_F(AsynchronousImageCache, RequestImageWithExtraIdFetchesImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml+extraId1"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::ImageEntry{{}, false};
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"extraId1");
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageWithExtraIdFetchesImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchSmallImage(Eq("/path/to/Component.qml+extraId1"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::ImageEntry{{}, false};
});
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"extraId1");
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageWithExtraIdRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(
Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"extraId1");
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageWithExtraIdRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(
Eq("/path/to/Component.qml"), Eq("extraId1"), Eq(Sqlite::TimeStamp{123}), _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); });
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"extraId1");
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestImageWithAuxiliaryDataRequestImageFromGenerator)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"),
Eq("extraId1"),
Eq(Sqlite::TimeStamp{123}),
_,
_,
VariantWith<FontCollectorSizesAuxiliaryData>(
AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes,
ElementsAre(QSize{20, 11})),
Field(&FontCollectorSizesAuxiliaryData::colorName,
Eq(u"color"))))))
.WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"extraId1",
FontCollectorSizesAuxiliaryData{sizes, "color"});
notification.wait();
}
TEST_F(AsynchronousImageCache, RequestSmallImageWithAuxiliaryDataRequestImageFromGenerator)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"),
Eq("extraId1"),
Eq(Sqlite::TimeStamp{123}),
_,
_,
VariantWith<FontCollectorSizesAuxiliaryData>(
AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes,
ElementsAre(QSize{20, 11})),
Field(&FontCollectorSizesAuxiliaryData::colorName,
Eq(u"color"))))))
.WillRepeatedly([&](auto, auto, auto, auto &&, auto, auto) { notification.notify(); });
cache.requestSmallImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"extraId1",
FontCollectorSizesAuxiliaryData{sizes, "color"});
notification.wait();
}
} // namespace

View File

@@ -1566,11 +1566,16 @@ std::ostream &operator<<(std::ostream &out, const VariantProperty &property)
}
namespace Internal {
std::ostream &operator<<(std::ostream &out, const ImageCacheStorageEntry &entry)
std::ostream &operator<<(std::ostream &out, const ImageCacheStorageImageEntry &entry)
{
return out << "(" << entry.image << ", " << entry.hasEntry << ")";
}
std::ostream &operator<<(std::ostream &out, const ImageCacheStorageIconEntry &entry)
{
return out << "(" << entry.icon << ", " << entry.hasEntry << ")";
}
} // namespace Internal
} // namespace QmlDesigner

View File

@@ -379,9 +379,11 @@ std::ostream &operator<<(std::ostream &out, const ModelNode &node);
std::ostream &operator<<(std::ostream &out, const VariantProperty &property);
namespace Internal {
class ImageCacheStorageEntry;
class ImageCacheStorageImageEntry;
class ImageCacheStorageIconEntry;
std::ostream &operator<<(std::ostream &out, const ImageCacheStorageEntry &entry);
std::ostream &operator<<(std::ostream &out, const ImageCacheStorageImageEntry &entry);
std::ostream &operator<<(std::ostream &out, const ImageCacheStorageIconEntry &entry);
} // namespace Internal
} // namespace QmlDesigner

View File

@@ -23,9 +23,10 @@
**
****************************************************************************/
#include <QDebug>
#include <QIcon>
#include <QString>
#include <QTextCharFormat>
#include <QDebug>
#include <gtest/gtest-printers.h>
@@ -90,6 +91,16 @@ std::ostream &operator<<(std::ostream &out, const QImage &image)
return out << "(" << image.width() << ", " << image.height() << ", " << image.format() << ")";
}
std::ostream &operator<<(std::ostream &out, const QIcon &icon)
{
return out << "(";
for (const QSize &size : icon.availableSizes())
out << "(" << size.width() << ", " << size.height() << "), ";
out << icon.cacheKey() << ")";
}
void PrintTo(const QString &text, std::ostream *os)
{
*os << text;

View File

@@ -35,12 +35,14 @@ class QVariant;
class QString;
class QTextCharFormat;
class QImage;
class QIcon;
std::ostream &operator<<(std::ostream &out, const QVariant &QVariant);
std::ostream &operator<<(std::ostream &out, const QString &text);
std::ostream &operator<<(std::ostream &out, const QByteArray &byteArray);
std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format);
std::ostream &operator<<(std::ostream &out, const QImage &image);
std::ostream &operator<<(std::ostream &out, const QIcon &icon);
void PrintTo(const QString &text, std::ostream *os);
void PrintTo(const QVariant &variant, std::ostream *os);

View File

@@ -1,391 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/
#include "googletest.h"
#include "mockimagecachegenerator.h"
#include "mockimagecachestorage.h"
#include "mocktimestampprovider.h"
#include "notification.h"
#include <imagecache.h>
namespace {
class ImageCache : public testing::Test
{
protected:
Notification notification;
Notification waitInThread;
NiceMock<MockFunction<void()>> mockAbortCallback;
NiceMock<MockFunction<void(const QImage &image)>> mockCaptureCallback;
NiceMock<MockImageCacheStorage> mockStorage;
NiceMock<MockImageCacheGenerator> mockGenerator;
NiceMock<MockTimeStampProvider> mockTimeStampProvider;
QmlDesigner::ImageCache cache{mockStorage, mockGenerator, mockTimeStampProvider};
QImage image1{10, 10, QImage::Format_ARGB32};
};
TEST_F(ImageCache, RequestImageFetchesImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::Entry{{}, false};
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestImageFetchesImageFromStorageWithTimeStamp)
{
EXPECT_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillRepeatedly(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, false};
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestImageCallsCaptureCallbackWithImageFromStorage)
{
ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{image1, true}));
EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))).WillRepeatedly([&](const QImage &) {
notification.notify();
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestImageCallsAbortCallbackWithoutImage)
{
ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, true}));
EXPECT_CALL(mockAbortCallback, Call()).WillRepeatedly([&] { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestImageRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&callback, auto) { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestImageCallsCaptureCallbackWithImageFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&callback, auto) {
callback(QImage{image1});
notification.notify();
});
EXPECT_CALL(mockCaptureCallback, Call(Eq(image1)));
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestImageCallsAbortCallbackFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback) {
abortCallback();
notification.notify();
});
EXPECT_CALL(mockAbortCallback, Call());
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconFetchesIconFromStorage)
{
EXPECT_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::Entry{{}, false};
});
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconFetchesIconFromStorageWithTimeStamp)
{
EXPECT_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillRepeatedly(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, false};
});
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconCallsCaptureCallbackWithImageFromStorage)
{
ON_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{image1, true}));
EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))).WillRepeatedly([&](const QImage &) {
notification.notify();
});
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconCallsAbortCallbackWithoutIcon)
{
ON_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, true}));
EXPECT_CALL(mockAbortCallback, Call()).WillRepeatedly([&] { notification.notify(); });
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&callback, auto) { notification.notify(); });
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconCallsCaptureCallbackWithImageFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&callback, auto) {
callback(QImage{image1});
notification.notify();
});
EXPECT_CALL(mockCaptureCallback, Call(Eq(image1)));
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, RequestIconCallsAbortCallbackFromGenerator)
{
ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto &&, auto &&abortCallback) {
abortCallback();
notification.notify();
});
EXPECT_CALL(mockAbortCallback, Call());
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, CleanRemovesEntries)
{
EXPECT_CALL(mockGenerator, generateImage(_, _, _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&mockCaptureCallback, auto &&) {
mockCaptureCallback(QImage{});
waitInThread.wait();
});
cache.requestIcon("/path/to/Component1.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
EXPECT_CALL(mockCaptureCallback, Call(_)).Times(AtMost(1));
cache.requestIcon("/path/to/Component3.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.clean();
waitInThread.notify();
}
TEST_F(ImageCache, CleanCallsAbort)
{
ON_CALL(mockGenerator, generateImage(_, _, _, _, _))
.WillByDefault(
[&](auto, auto, auto, auto &&mockCaptureCallback, auto &&) { waitInThread.wait(); });
cache.requestIcon("/path/to/Component1.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.requestIcon("/path/to/Component2.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
EXPECT_CALL(mockAbortCallback, Call()).Times(AtLeast(2));
cache.requestIcon("/path/to/Component3.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.clean();
waitInThread.notify();
}
TEST_F(ImageCache, CleanCallsGeneratorClean)
{
EXPECT_CALL(mockGenerator, clean()).Times(AtLeast(1));
cache.clean();
}
TEST_F(ImageCache, AfterCleanNewJobsWorks)
{
cache.clean();
EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&, auto &&) { notification.notify(); });
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
notification.wait();
}
TEST_F(ImageCache, WaitForFinished)
{
ON_CALL(mockStorage, fetchImage(_, _))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{image1, true}));
cache.requestImage("/path/to/Component1.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
cache.requestImage("/path/to/Component2.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction());
EXPECT_CALL(mockCaptureCallback, Call(_)).Times(2);
cache.waitForFinished();
}
TEST_F(ImageCache, WaitForFinishedInGenerator)
{
EXPECT_CALL(mockGenerator, waitForFinished());
cache.waitForFinished();
}
TEST_F(ImageCache, RequestImageWithStateFetchesImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml+state1"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::Entry{{}, false};
});
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"state1");
notification.wait();
}
TEST_F(ImageCache, RequestIconWithStateFetchesImageFromStorage)
{
EXPECT_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml+state1"), _))
.WillRepeatedly([&](Utils::SmallStringView, auto) {
notification.notify();
return QmlDesigner::ImageCacheStorageInterface::Entry{{}, false};
});
cache.requestIcon("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"state1");
notification.wait();
}
TEST_F(ImageCache, RequestImageWithStateRequestImageFromGenerator)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
EXPECT_CALL(mockGenerator,
generateImage(Eq("/path/to/Component.qml"), Eq("state1"), Eq(Sqlite::TimeStamp{123}), _, _))
.WillRepeatedly([&](auto, auto, auto, auto &&callback, auto) { notification.notify(); });
cache.requestImage("/path/to/Component.qml",
mockCaptureCallback.AsStdFunction(),
mockAbortCallback.AsStdFunction(),
"state1");
notification.wait();
}
} // namespace

View File

@@ -36,7 +36,22 @@ public:
start,
(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const QmlDesigner::ImageCache::AuxiliaryData &auxiliaryData,
ImageCacheCollectorInterface::CaptureCallback captureCallback,
ImageCacheCollectorInterface::AbortCallback abortCallback),
(override));
MOCK_METHOD(ImagePair,
createImage,
(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const QmlDesigner::ImageCache::AuxiliaryData &auxiliaryData),
(override));
MOCK_METHOD(QIcon,
createIcon,
(Utils::SmallStringView filePath,
Utils::SmallStringView state,
const QmlDesigner::ImageCache::AuxiliaryData &auxiliaryData),
(override));
};

View File

@@ -54,7 +54,8 @@ protected:
Notification waitInThread;
Notification notification;
QImage image1{10, 10, QImage::Format_ARGB32};
NiceMock<MockFunction<void(const QImage &)>> imageCallbackMock;
QImage smallImage1{1, 1, QImage::Format_ARGB32};
NiceMock<MockFunction<void(const QImage &, const QImage &)>> imageCallbackMock;
NiceMock<MockFunction<void()>> abortCallbackMock;
NiceMock<ImageCacheCollectorMock> collectorMock;
NiceMock<MockImageCacheStorage> storageMock;
@@ -63,191 +64,205 @@ protected:
TEST_F(ImageCacheGenerator, CallsCollectorWithCaptureCallback)
{
EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillRepeatedly(
[&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{image1}); });
EXPECT_CALL(imageCallbackMock, Call(_)).WillRepeatedly([&](const QImage &) {
EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
EXPECT_CALL(imageCallbackMock, Call(_, _)).WillRepeatedly([&](const QImage &, const QImage &) {
notification.notify();
});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {});
notification.wait();
}
TEST_F(ImageCacheGenerator, CallsCollectorOnlyIfNotProcessing)
{
EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _)).WillRepeatedly([&](auto, auto, auto, auto) {
notification.notify();
});
EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {});
notification.wait(2);
}
TEST_F(ImageCacheGenerator, ProcessTaskAfterFirstFinished)
{
ON_CALL(imageCallbackMock, Call(_)).WillByDefault([&](const QImage &) { notification.notify(); });
ON_CALL(imageCallbackMock, Call(_, _)).WillByDefault([&](const QImage &, const QImage &) {
notification.notify();
});
EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillOnce([&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{image1}); });
EXPECT_CALL(collectorMock, start(Eq("name2"), _, _, _))
.WillOnce([&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{image1}); });
EXPECT_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillOnce([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
EXPECT_CALL(collectorMock, start(Eq("name2"), _, _, _, _))
.WillOnce([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name2", {}, {}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name", {}, {}, imageCallbackMock.AsStdFunction(), {}, {});
generator.generateImage("name2", {}, {}, imageCallbackMock.AsStdFunction(), {}, {});
notification.wait(2);
}
TEST_F(ImageCacheGenerator, DontCrashAtDestructingGenerator)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillByDefault(
[&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{image1}); });
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
generator.generateImage("name",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name2",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name3",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name4",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage(
"name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name2", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name3", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name4", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
}
TEST_F(ImageCacheGenerator, StoreImage)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillByDefault(
[&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{image1}); });
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(image1)))
.WillRepeatedly([&](auto, auto, auto) { notification.notify(); });
EXPECT_CALL(storageMock,
storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(image1), Eq(smallImage1)))
.WillRepeatedly([&](auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name", {}, {11}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name", {}, {11}, imageCallbackMock.AsStdFunction(), {}, {});
notification.wait();
}
TEST_F(ImageCacheGenerator, StoreImageWithExtraId)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
EXPECT_CALL(storageMock,
storeImage(Eq("name+extraId"), Eq(Sqlite::TimeStamp{11}), Eq(image1), Eq(smallImage1)))
.WillRepeatedly([&](auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name", "extraId", {11}, imageCallbackMock.AsStdFunction(), {}, {});
notification.wait();
}
TEST_F(ImageCacheGenerator, StoreNullImage)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillByDefault([&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{}); });
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{}, QImage{});
});
EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{})))
.WillRepeatedly([&](auto, auto, auto) { notification.notify(); });
EXPECT_CALL(storageMock,
storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{}), Eq(QImage{})))
.WillRepeatedly([&](auto, auto, auto, auto) { notification.notify(); });
generator.generateImage(
"name", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
notification.wait();
}
TEST_F(ImageCacheGenerator, StoreNullImageWithExtraId)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{}, QImage{});
});
EXPECT_CALL(storageMock,
storeImage(Eq("name+extraId"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{}), Eq(QImage{})))
.WillRepeatedly([&](auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name",
{},
"extraId",
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
abortCallbackMock.AsStdFunction(),
{});
notification.wait();
}
TEST_F(ImageCacheGenerator, AbortCallback)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillByDefault(
[&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{image1}); });
ON_CALL(collectorMock, start(Eq("name2"), _, _, _))
.WillByDefault([&](auto, auto, auto, auto abortCallback) { abortCallback(); });
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
ON_CALL(collectorMock, start(Eq("name2"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { abortCallback(); });
EXPECT_CALL(imageCallbackMock, Call(_)).WillOnce([&](const QImage &) { notification.notify(); });
EXPECT_CALL(imageCallbackMock, Call(_, _)).WillOnce([&](const QImage &, const QImage &) {
notification.notify();
});
EXPECT_CALL(abortCallbackMock, Call()).WillOnce([&]() { notification.notify(); });
generator.generateImage("name",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name2",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage(
"name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name2", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
notification.wait(2);
}
TEST_F(ImageCacheGenerator, StoreNullImageForAbortCallback)
{
ON_CALL(collectorMock, start(_, _, _, _)).WillByDefault([&](auto, auto, auto, auto abortCallback) {
abortCallback();
});
ON_CALL(collectorMock, start(_, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto, auto abortCallback) { abortCallback(); });
EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{})))
.WillOnce([&](auto, auto, auto) { notification.notify(); });
EXPECT_CALL(storageMock,
storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{}), Eq(QImage{})))
.WillOnce([&](auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage(
"name", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
notification.wait();
}
TEST_F(ImageCacheGenerator, AbortForEmptyImage)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillByDefault([&](auto, auto, auto captureCallback, auto) { captureCallback(QImage{}); });
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{}, QImage{});
});
EXPECT_CALL(abortCallbackMock, Call()).WillOnce([&]() { notification.notify(); });
generator.generateImage("name",
{},
{},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage(
"name", {}, {}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
notification.wait();
}
TEST_F(ImageCacheGenerator, CallWalCheckpointFullIfQueueIsEmpty)
{
ON_CALL(collectorMock, start(Eq("name"), _, _, _))
.WillByDefault([&](auto, auto, auto captureCallback, auto) { captureCallback({}); });
ON_CALL(collectorMock, start(Eq("name"), _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) { captureCallback({}, {}); });
EXPECT_CALL(storageMock, walCheckpointFull()).WillRepeatedly([&]() { notification.notify(); });
generator.generateImage("name",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name2",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage(
"name", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name2", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
notification.wait();
}
TEST_F(ImageCacheGenerator, CleanIsCallingAbortCallback)
{
ON_CALL(collectorMock, start(_, _, _, _)).WillByDefault([&](auto, auto, auto captureCallback, auto) {
captureCallback({});
waitInThread.wait();
});
generator.generateImage("name",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name2",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
ON_CALL(collectorMock, start(_, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback({}, {});
waitInThread.wait();
});
generator.generateImage(
"name", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name2", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
EXPECT_CALL(abortCallbackMock, Call()).Times(AtLeast(1));
@@ -257,31 +272,51 @@ TEST_F(ImageCacheGenerator, CleanIsCallingAbortCallback)
TEST_F(ImageCacheGenerator, WaitForFinished)
{
ON_CALL(collectorMock, start(_, _, _, _)).WillByDefault([&](auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1});
});
generator.generateImage("name",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
generator.generateImage("name2",
{},
{11},
imageCallbackMock.AsStdFunction(),
abortCallbackMock.AsStdFunction());
ON_CALL(collectorMock, start(_, _, _, _, _))
.WillByDefault([&](auto, auto, auto, auto captureCallback, auto) {
captureCallback(QImage{image1}, QImage{smallImage1});
});
generator.generateImage(
"name", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
generator.generateImage(
"name2", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
EXPECT_CALL(imageCallbackMock, Call(_)).Times(2);
EXPECT_CALL(imageCallbackMock, Call(_, _)).Times(2);
generator.waitForFinished();
}
TEST_F(ImageCacheGenerator, CallsCollectorWithState)
TEST_F(ImageCacheGenerator, CallsCollectorWithExtraId)
{
EXPECT_CALL(collectorMock, start(Eq("name"), Eq("state1"), _, _))
.WillRepeatedly([&](auto, auto, auto, auto) { notification.notify(); });
EXPECT_CALL(collectorMock, start(Eq("name"), Eq("extraId1"), _, _, _))
.WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name", "state1", {}, imageCallbackMock.AsStdFunction(), {});
generator.generateImage("name", "extraId1", {}, imageCallbackMock.AsStdFunction(), {}, {});
notification.wait();
}
TEST_F(ImageCacheGenerator, CallsCollectorWithAuxiliaryData)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
EXPECT_CALL(collectorMock,
start(Eq("name"),
_,
VariantWith<FontCollectorSizesAuxiliaryData>(
AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes,
ElementsAre(QSize{20, 11})),
Field(&FontCollectorSizesAuxiliaryData::colorName, Eq(u"color")))),
_,
_))
.WillRepeatedly([&](auto, auto, auto, auto, auto) { notification.notify(); });
generator.generateImage("name",
{},
{},
imageCallbackMock.AsStdFunction(),
{},
FontCollectorSizesAuxiliaryData{sizes, "color"});
notification.wait();
}

View File

@@ -32,28 +32,28 @@
namespace {
MATCHER_P2(IsEntry,
MATCHER_P2(IsImageEntry,
image,
hasEntry,
std::string(negation ? "is't" : "is")
+ PrintToString(QmlDesigner::ImageCacheStorageInterface::Entry{image, hasEntry}))
+ PrintToString(QmlDesigner::ImageCacheStorageInterface::ImageEntry{image, hasEntry}))
{
const QmlDesigner::ImageCacheStorageInterface::Entry &entry = arg;
const QmlDesigner::ImageCacheStorageInterface::ImageEntry &entry = arg;
return entry.image == image && entry.hasEntry == hasEntry;
}
MATCHER_P2(IsIconEntry,
icon,
hasEntry,
std::string(negation ? "is't" : "is")
+ PrintToString(QmlDesigner::ImageCacheStorageInterface::IconEntry{icon, hasEntry}))
{
const QmlDesigner::ImageCacheStorageInterface::IconEntry &entry = arg;
return entry.icon.availableSizes() == icon.availableSizes() && entry.hasEntry == hasEntry;
}
class ImageCacheStorageTest : public testing::Test
{
protected:
QImage createImage()
{
QImage image{150, 150, QImage::Format_ARGB32};
image.fill(QColor{128, 64, 0, 11});
image.setPixelColor(75, 75, QColor{1, 255, 33, 196});
return image;
}
protected:
using ReadStatement = QmlDesigner::ImageCacheStorage<SqliteDatabaseMock>::ReadStatement;
using WriteStatement = QmlDesigner::ImageCacheStorage<SqliteDatabaseMock>::WriteStatement;
@@ -61,9 +61,13 @@ protected:
NiceMock<SqliteDatabaseMock> databaseMock;
QmlDesigner::ImageCacheStorage<SqliteDatabaseMock> storage{databaseMock};
ReadStatement &selectImageStatement = storage.selectImageStatement;
ReadStatement &selectSmallImageStatement = storage.selectSmallImageStatement;
ReadStatement &selectIconStatement = storage.selectIconStatement;
WriteStatement &upsertImageStatement = storage.upsertImageStatement;
QImage image1{createImage()};
WriteStatement &upsertIconStatement = storage.upsertIconStatement;
QImage image1{10, 10, QImage::Format_ARGB32};
QImage smallImage1{10, 10, QImage::Format_ARGB32};
QIcon icon1{QPixmap::fromImage(image1)};
};
TEST_F(ImageCacheStorageTest, Initialize)
@@ -73,12 +77,17 @@ TEST_F(ImageCacheStorageTest, Initialize)
EXPECT_CALL(databaseMock, exclusiveBegin());
EXPECT_CALL(databaseMock,
execute(Eq("CREATE TABLE IF NOT EXISTS images(id INTEGER PRIMARY KEY, name TEXT "
"NOT NULL UNIQUE, mtime INTEGER, image BLOB, icon BLOB)")));
"NOT NULL UNIQUE, mtime INTEGER, image BLOB, smallImage BLOB)")));
EXPECT_CALL(databaseMock,
execute(Eq("CREATE TABLE IF NOT EXISTS icons(id INTEGER PRIMARY KEY, name TEXT "
"NOT NULL UNIQUE, mtime INTEGER, icon BLOB)")));
EXPECT_CALL(databaseMock, commit());
EXPECT_CALL(databaseMock, immediateBegin());
EXPECT_CALL(databaseMock, prepare(Eq(selectImageStatement.sqlStatement)));
EXPECT_CALL(databaseMock, prepare(Eq(selectSmallImageStatement.sqlStatement)));
EXPECT_CALL(databaseMock, prepare(Eq(selectIconStatement.sqlStatement)));
EXPECT_CALL(databaseMock, prepare(Eq(upsertImageStatement.sqlStatement)));
EXPECT_CALL(databaseMock, prepare(Eq(upsertIconStatement.sqlStatement)));
EXPECT_CALL(databaseMock, commit());
QmlDesigner::ImageCacheStorage<SqliteDatabaseMock> storage{databaseMock};
@@ -116,6 +125,38 @@ TEST_F(ImageCacheStorageTest, FetchImageCallsIsBusy)
storage.fetchImage("/path/to/component", {123});
}
TEST_F(ImageCacheStorageTest, FetchSmallImageCalls)
{
InSequence s;
EXPECT_CALL(databaseMock, deferredBegin());
EXPECT_CALL(selectSmallImageStatement,
valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123)));
EXPECT_CALL(databaseMock, commit());
storage.fetchSmallImage("/path/to/component", {123});
}
TEST_F(ImageCacheStorageTest, FetchSmallImageCallsIsBusy)
{
InSequence s;
EXPECT_CALL(databaseMock, deferredBegin());
EXPECT_CALL(selectSmallImageStatement,
valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123)))
.WillOnce(Throw(Sqlite::StatementIsBusy("busy")));
EXPECT_CALL(databaseMock, rollback());
EXPECT_CALL(databaseMock, deferredBegin());
EXPECT_CALL(selectSmallImageStatement,
valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123)));
EXPECT_CALL(databaseMock, commit());
storage.fetchSmallImage("/path/to/component", {123});
}
TEST_F(ImageCacheStorageTest, FetchIconCalls)
{
InSequence s;
@@ -156,11 +197,11 @@ TEST_F(ImageCacheStorageTest, StoreImageCalls)
EXPECT_CALL(upsertImageStatement,
write(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123),
A<Sqlite::BlobView>(),
A<Sqlite::BlobView>()));
Not(IsEmpty()),
Not(IsEmpty())));
EXPECT_CALL(databaseMock, commit());
storage.storeImage("/path/to/component", {123}, image1);
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
}
TEST_F(ImageCacheStorageTest, StoreEmptyImageCalls)
@@ -171,11 +212,11 @@ TEST_F(ImageCacheStorageTest, StoreEmptyImageCalls)
EXPECT_CALL(upsertImageStatement,
write(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123),
A<Sqlite::NullValue>(),
A<Sqlite::NullValue>()));
IsEmpty(),
IsEmpty()));
EXPECT_CALL(databaseMock, commit());
storage.storeImage("/path/to/component", {123}, QImage{});
storage.storeImage("/path/to/component", {123}, QImage{}, QImage{});
}
TEST_F(ImageCacheStorageTest, StoreImageCallsIsBusy)
@@ -187,11 +228,54 @@ TEST_F(ImageCacheStorageTest, StoreImageCallsIsBusy)
EXPECT_CALL(upsertImageStatement,
write(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123),
A<Sqlite::NullValue>(),
A<Sqlite::NullValue>()));
IsEmpty(),
IsEmpty()));
EXPECT_CALL(databaseMock, commit());
storage.storeImage("/path/to/component", {123}, QImage{});
storage.storeImage("/path/to/component", {123}, QImage{}, QImage{});
}
TEST_F(ImageCacheStorageTest, StoreIconCalls)
{
InSequence s;
EXPECT_CALL(databaseMock, immediateBegin());
EXPECT_CALL(upsertIconStatement,
write(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123),
A<Sqlite::BlobView>()));
EXPECT_CALL(databaseMock, commit());
storage.storeIcon("/path/to/component", {123}, icon1);
}
TEST_F(ImageCacheStorageTest, StoreEmptyIconCalls)
{
InSequence s;
EXPECT_CALL(databaseMock, immediateBegin());
EXPECT_CALL(upsertIconStatement,
write(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123),
IsEmpty()));
EXPECT_CALL(databaseMock, commit());
storage.storeIcon("/path/to/component", {123}, QIcon{});
}
TEST_F(ImageCacheStorageTest, StoreIconCallsIsBusy)
{
InSequence s;
EXPECT_CALL(databaseMock, immediateBegin()).WillOnce(Throw(Sqlite::StatementIsBusy("busy")));
EXPECT_CALL(databaseMock, immediateBegin());
EXPECT_CALL(upsertIconStatement,
write(TypedEq<Utils::SmallStringView>("/path/to/component"),
TypedEq<long long>(123),
IsEmpty()));
EXPECT_CALL(databaseMock, commit());
storage.storeIcon("/path/to/component", {123}, QIcon{});
}
TEST_F(ImageCacheStorageTest, CallWalCheckointFull)
@@ -227,108 +311,155 @@ protected:
Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory};
QmlDesigner::ImageCacheStorage<Sqlite::Database> storage{database};
QImage image1{createImage()};
QImage image2{10, 10, QImage::Format_ARGB32};
QImage icon1{image1.scaled(96, 96)};
QImage smallImage1{image1.scaled(96, 96)};
QIcon icon1{QPixmap::fromImage(image1)};
};
TEST_F(ImageCacheStorageSlowTest, StoreImage)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsEntry(image1, true));
ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsImageEntry(image1, true));
}
TEST_F(ImageCacheStorageSlowTest, StoreEmptyImageAfterEntry)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
storage.storeImage("/path/to/component", {123}, QImage{});
storage.storeImage("/path/to/component", {123}, QImage{}, QImage{});
ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsEntry(QImage{}, true));
ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsImageEntry(QImage{}, true));
}
TEST_F(ImageCacheStorageSlowTest, StoreEmptyEntry)
{
storage.storeImage("/path/to/component", {123}, QImage{});
storage.storeImage("/path/to/component", {123}, QImage{}, QImage{});
ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsEntry(QImage{}, true));
ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsImageEntry(QImage{}, true));
}
TEST_F(ImageCacheStorageSlowTest, FetchNonExistingImageIsEmpty)
{
auto image = storage.fetchImage("/path/to/component", {123});
ASSERT_THAT(image, IsEntry(QImage{}, false));
ASSERT_THAT(image, IsImageEntry(QImage{}, false));
}
TEST_F(ImageCacheStorageSlowTest, FetchSameTimeImage)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
auto image = storage.fetchImage("/path/to/component", {123});
ASSERT_THAT(image, IsEntry(image1, true));
ASSERT_THAT(image, IsImageEntry(image1, true));
}
TEST_F(ImageCacheStorageSlowTest, DoNotFetchOlderImage)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
auto image = storage.fetchImage("/path/to/component", {124});
ASSERT_THAT(image, IsEntry(QImage{}, false));
ASSERT_THAT(image, IsImageEntry(QImage{}, false));
}
TEST_F(ImageCacheStorageSlowTest, FetchNewerImage)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
auto image = storage.fetchImage("/path/to/component", {122});
ASSERT_THAT(image, IsEntry(image1, true));
ASSERT_THAT(image, IsImageEntry(image1, true));
}
TEST_F(ImageCacheStorageSlowTest, FetchNonExistingSmallImageIsEmpty)
{
auto image = storage.fetchSmallImage("/path/to/component", {123});
ASSERT_THAT(image, IsImageEntry(QImage{}, false));
}
TEST_F(ImageCacheStorageSlowTest, FetchSameTimeSmallImage)
{
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
auto image = storage.fetchSmallImage("/path/to/component", {123});
ASSERT_THAT(image, IsImageEntry(smallImage1, true));
}
TEST_F(ImageCacheStorageSlowTest, DoNotFetchOlderSmallImage)
{
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
auto image = storage.fetchSmallImage("/path/to/component", {124});
ASSERT_THAT(image, IsImageEntry(QImage{}, false));
}
TEST_F(ImageCacheStorageSlowTest, FetchNewerSmallImage)
{
storage.storeImage("/path/to/component", {123}, image1, smallImage1);
auto image = storage.fetchSmallImage("/path/to/component", {122});
ASSERT_THAT(image, IsImageEntry(smallImage1, true));
}
TEST_F(ImageCacheStorageSlowTest, StoreIcon)
{
storage.storeIcon("/path/to/component", {123}, icon1);
ASSERT_THAT(storage.fetchIcon("/path/to/component", {123}), IsIconEntry(icon1, true));
}
TEST_F(ImageCacheStorageSlowTest, StoreEmptyIconAfterEntry)
{
storage.storeIcon("/path/to/component", {123}, icon1);
storage.storeIcon("/path/to/component", {123}, QIcon{});
ASSERT_THAT(storage.fetchIcon("/path/to/component", {123}), IsIconEntry(QIcon{}, true));
}
TEST_F(ImageCacheStorageSlowTest, StoreEmptyIconEntry)
{
storage.storeIcon("/path/to/component", {123}, QIcon{});
ASSERT_THAT(storage.fetchIcon("/path/to/component", {123}), IsIconEntry(QIcon{}, true));
}
TEST_F(ImageCacheStorageSlowTest, FetchNonExistingIconIsEmpty)
{
auto image = storage.fetchIcon("/path/to/component", {123});
ASSERT_THAT(image, IsEntry(QImage{}, false));
ASSERT_THAT(image, IsIconEntry(QIcon{}, false));
}
TEST_F(ImageCacheStorageSlowTest, FetchSameTimeIcon)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeIcon("/path/to/component", {123}, icon1);
auto image = storage.fetchIcon("/path/to/component", {123});
ASSERT_THAT(image, IsEntry(icon1, true));
ASSERT_THAT(image, IsIconEntry(icon1, true));
}
TEST_F(ImageCacheStorageSlowTest, DoNotFetchOlderIcon)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeIcon("/path/to/component", {123}, icon1);
auto image = storage.fetchIcon("/path/to/component", {124});
ASSERT_THAT(image, IsEntry(QImage{}, false));
ASSERT_THAT(image, IsIconEntry(QIcon{}, false));
}
TEST_F(ImageCacheStorageSlowTest, FetchNewerIcon)
{
storage.storeImage("/path/to/component", {123}, image1);
storage.storeIcon("/path/to/component", {123}, icon1);
auto image = storage.fetchIcon("/path/to/component", {122});
ASSERT_THAT(image, IsEntry(icon1, true));
ASSERT_THAT(image, IsIconEntry(icon1, true));
}
TEST_F(ImageCacheStorageSlowTest, DontScaleSmallerIcon)
{
storage.storeImage("/path/to/component", {123}, image2);
auto image = storage.fetchImage("/path/to/component", {122});
ASSERT_THAT(image, IsEntry(image2, true));
}
} // namespace

View File

@@ -38,7 +38,8 @@ public:
Utils::SmallStringView state,
Sqlite::TimeStamp timeStamp,
CaptureCallback &&captureCallback,
AbortCallback &&abortCallback),
AbortCallback &&abortCallback,
QmlDesigner::ImageCache::AuxiliaryData &&auxiliaryData),
(override));
MOCK_METHOD(void, clean, (), (override));
MOCK_METHOD(void, waitForFinished, (), (override));

View File

@@ -32,19 +32,33 @@
class MockImageCacheStorage : public QmlDesigner::ImageCacheStorageInterface
{
public:
MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::Entry,
MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::ImageEntry,
fetchImage,
(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp),
(const, override));
MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::Entry,
MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::ImageEntry,
fetchSmallImage,
(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp),
(const, override));
MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::IconEntry,
fetchIcon,
(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp),
(const, override));
MOCK_METHOD(void,
storeImage,
(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image),
(Utils::SmallStringView name,
Sqlite::TimeStamp newTimeStamp,
const QImage &image,
const QImage &smallImage),
(override));
MOCK_METHOD(void,
storeIcon,
(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QIcon &icon),
(override));
MOCK_METHOD(void, walCheckpointFull, (), (override));
};

View File

@@ -390,7 +390,7 @@ TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundValu
TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundBlob)
{
SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database);
Sqlite::BlobView bytes;
Sqlite::BlobView bytes{QByteArray{"XXX"}};
ASSERT_THROW(statement.bind(2, bytes), Sqlite::BindingIndexIsOutOfRange);
}
@@ -505,6 +505,18 @@ TEST_F(SqliteStatement, WriteEmptyBlobs)
ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty());
}
TEST_F(SqliteStatement, EmptyBlobsAreNull)
{
SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT ifnull(blob, 1) FROM T",
database);
Sqlite::BlobView bytes;
statement.write(bytes);
ASSERT_THAT(statement.fetchType(0), Eq(Sqlite::Type::Null));
}
TEST_F(SqliteStatement, WriteBlobs)
{
SqliteTestStatement statement("INSERT INTO test VALUES ('blob', 40, ?)", database);

View File

@@ -52,11 +52,8 @@ public:
MOCK_METHOD(void, write, (Utils::SmallStringView, Utils::SmallStringView, double), ());
MOCK_METHOD(void, write, (long long, Utils::SmallStringView, Utils::SmallStringView), ());
MOCK_METHOD(void, write, (long long, Utils::SmallStringView, const Sqlite::Value &), ());
MOCK_METHOD(void, write, (Utils::SmallStringView, long long, Sqlite::BlobView), ());
MOCK_METHOD(void, write, (Utils::SmallStringView, long long, Sqlite::BlobView, Sqlite::BlobView), ());
MOCK_METHOD(void,
write,
(Utils::SmallStringView, long long, Sqlite::NullValue, Sqlite::NullValue),
());
MOCK_METHOD(void,
write,
(Utils::SmallStringView,

View File

@@ -0,0 +1,269 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
****************************************************************************/
#include "googletest.h"
#include "imagecachecollectormock.h"
#include "mockimagecachestorage.h"
#include "mocktimestampprovider.h"
#include <synchronousimagecache.h>
namespace {
MATCHER_P(IsIcon, icon, std::string(negation ? "isn't " : "is ") + PrintToString(icon))
{
const QIcon &other = arg;
return icon.availableSizes() == other.availableSizes();
}
class SynchronousImageCache : public testing::Test
{
protected:
SynchronousImageCache()
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{123}));
ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{image1, true}));
ON_CALL(mockStorage,
fetchImage(Eq("/path/to/Component.qml+extraId1"), Eq(Sqlite::TimeStamp{123})))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{image2, true}));
ON_CALL(mockStorage, fetchSmallImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillByDefault(
Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{smallImage1, true}));
ON_CALL(mockStorage,
fetchSmallImage(Eq("/path/to/Component.qml+extraId1"), Eq(Sqlite::TimeStamp{123})))
.WillByDefault(
Return(QmlDesigner::ImageCacheStorageInterface::ImageEntry{smallImage2, true}));
ON_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123})))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::IconEntry{icon1, true}));
ON_CALL(mockStorage,
fetchIcon(Eq("/path/to/Component.qml+extraId1"), Eq(Sqlite::TimeStamp{123})))
.WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::IconEntry{icon2, true}));
ON_CALL(mockCollector, createImage(Eq("/path/to/Component.qml"), Eq("extraId1"), _))
.WillByDefault(Return(std::make_pair(image3, smallImage3)));
ON_CALL(mockCollector, createIcon(Eq("/path/to/Component.qml"), Eq("extraId1"), _))
.WillByDefault(Return(icon3));
}
protected:
NiceMock<MockFunction<void(const QImage &image)>> mockCaptureCallback;
NiceMock<MockImageCacheStorage> mockStorage;
NiceMock<ImageCacheCollectorMock> mockCollector;
NiceMock<MockTimeStampProvider> mockTimeStampProvider;
QmlDesigner::SynchronousImageCache cache{mockStorage, mockTimeStampProvider, mockCollector};
QImage image1{1, 1, QImage::Format_ARGB32};
QImage image2{2, 2, QImage::Format_ARGB32};
QImage image3{3, 3, QImage::Format_ARGB32};
QImage smallImage1{1, 1, QImage::Format_ARGB32};
QImage smallImage2{2, 1, QImage::Format_ARGB32};
QImage smallImage3{3, 1, QImage::Format_ARGB32};
QIcon icon1{QPixmap::fromImage(image1)};
QIcon icon2{QPixmap::fromImage(image2)};
QIcon icon3{QPixmap::fromImage(image3)};
};
TEST_F(SynchronousImageCache, GetImageFromStorage)
{
auto image = cache.image("/path/to/Component.qml");
ASSERT_THAT(image, image1);
}
TEST_F(SynchronousImageCache, GetImageWithExtraIdFromStorage)
{
auto image = cache.image("/path/to/Component.qml", "extraId1");
ASSERT_THAT(image, image2);
}
TEST_F(SynchronousImageCache, GetImageWithOutdatedTimeStampFromCollector)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
auto image = cache.image("/path/to/Component.qml", "extraId1");
ASSERT_THAT(image, image3);
}
TEST_F(SynchronousImageCache, GetImageWithOutdatedTimeStampStored)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
EXPECT_CALL(mockStorage,
storeImage(Eq("/path/to/Component.qml+extraId1"),
Eq(Sqlite::TimeStamp{124}),
Eq(image3),
Eq(smallImage3)));
auto image = cache.image("/path/to/Component.qml", "extraId1");
}
TEST_F(SynchronousImageCache, GetSmallImageFromStorage)
{
auto image = cache.smallImage("/path/to/Component.qml");
ASSERT_THAT(image, smallImage1);
}
TEST_F(SynchronousImageCache, GetSmallImageWithExtraIdFromStorage)
{
auto image = cache.smallImage("/path/to/Component.qml", "extraId1");
ASSERT_THAT(image, smallImage2);
}
TEST_F(SynchronousImageCache, GetSmallImageWithOutdatedTimeStampFromCollector)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
auto image = cache.smallImage("/path/to/Component.qml", "extraId1");
ASSERT_THAT(image, smallImage3);
}
TEST_F(SynchronousImageCache, GetSmallImageWithOutdatedTimeStampStored)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
EXPECT_CALL(mockStorage,
storeImage(Eq("/path/to/Component.qml+extraId1"),
Eq(Sqlite::TimeStamp{124}),
Eq(image3),
Eq(smallImage3)));
auto image = cache.smallImage("/path/to/Component.qml", "extraId1");
}
TEST_F(SynchronousImageCache, GetIconFromStorage)
{
auto icon = cache.icon("/path/to/Component.qml");
ASSERT_THAT(icon, IsIcon(icon1));
}
TEST_F(SynchronousImageCache, GetIconWithExtraIdFromStorage)
{
auto icon = cache.icon("/path/to/Component.qml", "extraId1");
ASSERT_THAT(icon, IsIcon(icon2));
}
TEST_F(SynchronousImageCache, GetIconWithOutdatedTimeStampFromCollector)
{
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
auto icon = cache.icon("/path/to/Component.qml", "extraId1");
ASSERT_THAT(icon, IsIcon(icon3));
}
TEST_F(SynchronousImageCache, GetIconWithOutdatedTimeStampStored)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
EXPECT_CALL(mockStorage,
storeIcon(Eq("/path/to/Component.qml+extraId1"),
Eq(Sqlite::TimeStamp{124}),
IsIcon(icon3)));
auto icon = cache.icon("/path/to/Component.qml",
"extraId1",
FontCollectorSizesAuxiliaryData{sizes, "color"});
}
TEST_F(SynchronousImageCache, IconCallsCollectorWithAuxiliaryData)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
EXPECT_CALL(mockCollector,
createIcon(Eq("/path/to/Component.qml"),
Eq("extraId1"),
VariantWith<FontCollectorSizesAuxiliaryData>(
AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes,
ElementsAre(QSize{20, 11})),
Field(&FontCollectorSizesAuxiliaryData::colorName,
Eq(u"color"))))));
auto icon = cache.icon("/path/to/Component.qml",
"extraId1",
FontCollectorSizesAuxiliaryData{sizes, "color"});
}
TEST_F(SynchronousImageCache, ImageCallsCollectorWithAuxiliaryData)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
EXPECT_CALL(mockCollector,
createImage(Eq("/path/to/Component.qml"),
Eq("extraId1"),
VariantWith<FontCollectorSizesAuxiliaryData>(
AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes,
ElementsAre(QSize{20, 11})),
Field(&FontCollectorSizesAuxiliaryData::colorName,
Eq(u"color"))))));
auto icon = cache.image("/path/to/Component.qml",
"extraId1",
FontCollectorSizesAuxiliaryData{sizes, "color"});
}
TEST_F(SynchronousImageCache, SmallImageCallsCollectorWithAuxiliaryData)
{
using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData;
std::vector<QSize> sizes{{20, 11}};
ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml")))
.WillByDefault(Return(Sqlite::TimeStamp{124}));
EXPECT_CALL(mockCollector,
createImage(Eq("/path/to/Component.qml"),
Eq("extraId1"),
VariantWith<FontCollectorSizesAuxiliaryData>(
AllOf(Field(&FontCollectorSizesAuxiliaryData::sizes,
ElementsAre(QSize{20, 11})),
Field(&FontCollectorSizesAuxiliaryData::colorName,
Eq(u"color"))))));
auto icon = cache.smallImage("/path/to/Component.qml",
"extraId1",
FontCollectorSizesAuxiliaryData{sizes, "color"});
}
} // namespace

View File

@@ -66,7 +66,8 @@ SOURCES += \
filepathview-test.cpp \
gtest-creator-printing.cpp \
gtest-qt-printing.cpp \
imagecache-test.cpp \
asynchronousimagecache-test.cpp \
synchronousimagecache-test.cpp \
imagecachegenerator-test.cpp \
imagecachestorage-test.cpp \
lastchangedrowid-test.cpp \

View File

@@ -29,7 +29,7 @@
#include <sqliteglobal.h>
#include <utils/temporarydirectory.h>
#include <QCoreApplication>
#include <QGuiApplication>
#include <QLoggingCategory>
#ifdef WITH_BENCHMARKS
@@ -54,7 +54,7 @@ int main(int argc, char *argv[])
{
Sqlite::Database::activateLogging();
QCoreApplication application(argc, argv);
QGuiApplication application(argc, argv);
testing::InitGoogleTest(&argc, argv);
#ifdef WITH_BENCHMARKS