QmlDesigner: Add tooltips to UrlChooser

* Add tooltips with thumbnails to UrlChooser enable preview of image
  formats and meshes
* Add property editor image provider which makes use of the image cache
* Add mesh image cache collector in order to create thumbnails for
  meshes and built-in primitves
* Fix typo in explicit image cache image provider
* Add return value in time stamp provider if provided file does not
  exist

Change-Id: I2290d2ace87ddd90e9899e343f2ad1ecd2993fdf
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2022-06-03 14:34:11 +02:00
committed by Henning Gründl
parent 1c6fa83648
commit bd51b4fdc2
23 changed files with 682 additions and 57 deletions

View File

@@ -44,6 +44,9 @@ Row {
// by QtQuick3D to add built-in primitives to the model.
property var defaultItems
// Current item
property string absoluteFilePath: ""
FileResourcesModel {
id: fileModel
modelNodeBackendProperty: modelNodeBackend
@@ -74,16 +77,64 @@ Row {
visible: comboBox.hover && toolTip.text !== ""
text: root.backendValue.valueToString
delay: StudioTheme.Values.toolTipDelay
height: StudioTheme.Values.toolTipHeight
background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground
border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border
}
contentItem: Text {
color: StudioTheme.Values.themeToolTipText
text: toolTip.text
verticalAlignment: Text.AlignVCenter
contentItem: RowLayout {
spacing: 10
Item {
visible: thumbnail.status === Image.Ready
Layout.preferredWidth: 100
Layout.preferredHeight: 100
Image {
id: checker
visible: !root.isMesh(root.absoluteFilePath)
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: thumbnail
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: {
if (root.isBuiltInPrimitive(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/"
+ root.absoluteFilePath.substring(1, root.absoluteFilePath.length)
+ ".builtin"
if (fileModel.isLocal(root.absoluteFilePath))
return "image://qmldesigner_thumbnails/" + root.absoluteFilePath
return root.absoluteFilePath
}
}
}
ColumnLayout {
Text {
text: root.fileName(toolTip.text)
color: StudioTheme.Values.themeToolTipText
font: toolTip.font
}
Text {
Layout.fillWidth: true
text: root.isBuiltInPrimitive(toolTip.text) ? qsTr("Built-in primitive")
: toolTip.text
font: toolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
}
}
@@ -155,16 +206,62 @@ Row {
visible: delegateRoot.hovered
text: delegateRoot.relativeFilePath
delay: StudioTheme.Values.toolTipDelay
height: StudioTheme.Values.toolTipHeight
background: Rectangle {
color: StudioTheme.Values.themeToolTipBackground
border.color: StudioTheme.Values.themeToolTipOutline
border.width: StudioTheme.Values.border
}
contentItem: Text {
color: StudioTheme.Values.themeToolTipText
text: itemToolTip.text
verticalAlignment: Text.AlignVCenter
contentItem: RowLayout {
spacing: 10
Item {
visible: delegateThumbnail.status === Image.Ready
Layout.preferredWidth: 100
Layout.preferredHeight: 100
Image {
id: delegateChecker
visible: !root.isMesh(delegateRoot.absoluteFilePath)
anchors.fill: parent
fillMode: Image.Tile
source: "images/checkers.png"
}
Image {
id: delegateThumbnail
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: {
if (root.isBuiltInPrimitive(delegateRoot.name))
return "image://qmldesigner_thumbnails/"
+ delegateRoot.name.substring(1, delegateRoot.name.length)
+ ".builtin"
return "image://qmldesigner_thumbnails/" + delegateRoot.absoluteFilePath
}
}
}
ColumnLayout {
Text {
text: delegateRoot.name
color: StudioTheme.Values.themeToolTipText
font: delegateToolTip.font
}
Text {
Layout.fillWidth: true
text: root.isBuiltInPrimitive(delegateToolTip.text)
? qsTr("Built-in primitive")
: delegateToolTip.text
font: delegateToolTip.font
color: StudioTheme.Values.themeToolTipText
wrapMode: Text.WordWrap
}
}
}
}
}
@@ -235,6 +332,10 @@ Row {
inputValue = comboBox.items.get(index).model.relativeFilePath
root.backendValue.value = inputValue
if (!root.backendValue.isBound)
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
comboBox.dirty = false
}
@@ -259,6 +360,9 @@ Row {
if (root.backendValue.value !== inputValue)
root.backendValue.value = inputValue
if (!root.backendValue.isBound)
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
comboBox.dirty = false
}
@@ -275,6 +379,23 @@ Row {
}
}
function isBuiltInPrimitive(value) {
return value.startsWith('#')
}
function isMesh(value) {
return root.isBuiltInPrimitive(value)
|| root.hasFileExtension(root.fileName(value), "mesh")
}
function hasFileExtension(fileName, extension) {
return fileName.split('.').pop() === extension
}
function fileName(filePath) {
return filePath.substr(filePath.lastIndexOf('/') + 1)
}
function createModel() {
// Build the combobox model
comboBox.listModel.clear()
@@ -322,6 +443,9 @@ Row {
Component.onCompleted: {
root.createModel()
comboBox.updateTextValue()
if (!root.backendValue.isBound)
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
}
function indexOf(model, criteria) {
@@ -340,7 +464,7 @@ Row {
if (comboBox.popup.opened && !root.backendValue.isBound) {
var index = root.indexOf(comboBox.items,
function(item) {
return item.fullPath === root.backendValue.value
return item.relativeFilePath === root.backendValue.value
})
if (index !== -1) {
@@ -359,8 +483,10 @@ Row {
iconColor: root.textColor
onClicked: {
fileModel.openFileDialog()
if (fileModel.fileName !== "")
if (fileModel.fileName !== "") {
root.backendValue.value = fileModel.fileName
root.absoluteFilePath = fileModel.resolve(root.backendValue.value)
}
}
}
}

View File

@@ -302,6 +302,7 @@ extend_qtc_plugin(QmlDesigner
gradientpresetitem.cpp gradientpresetitem.h
gradientpresetlistmodel.cpp gradientpresetlistmodel.h
propertyeditorcontextobject.cpp propertyeditorcontextobject.h
propertyeditorimageprovider.cpp propertyeditorimageprovider.h
propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h
propertyeditortransaction.cpp propertyeditortransaction.h
propertyeditorvalue.cpp propertyeditorvalue.h
@@ -389,7 +390,8 @@ extend_qtc_plugin(QmlDesigner
SOURCES
explicitimagecacheimageprovider.cpp
explicitimagecacheimageprovider.h
smallimagecacheprovider.cpp
smallimagecacheprovider.h
)
extend_qtc_plugin(QmlDesigner

View File

@@ -0,0 +1,68 @@
/****************************************************************************
**
** Copyright (C) 2022 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 "propertyeditorimageprovider.h"
#include "assetslibrarymodel.h"
#include <hdrimage.h>
#include <projectexplorer/target.h>
#include <utils/stylehelper.h>
#include <QMetaObject>
#include <QQuickImageResponse>
namespace QmlDesigner {
QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QString &id,
const QSize &requestedSize)
{
const QString suffix = "*." + id.split('.').last().toLower();
if (suffix == "*.mesh")
return m_smallImageCacheProvider.requestImageResponse(id, requestedSize);
if (suffix == "*.builtin")
return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(),
requestedSize);
QImage image;
auto response = std::make_unique<QmlDesigner::ImageResponse>(image);
QMetaObject::invokeMethod(
response.get(),
[response = QPointer<QmlDesigner::ImageResponse>(response.get()), image, suffix, id] {
if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix))
response->setImage(QImage(Utils::StyleHelper::dpiSpecificImageFile(id)));
else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix))
response->setImage(HdrImage{id}.image());
else
response->abort();
},
Qt::QueuedConnection);
return response.release();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,48 @@
/****************************************************************************
**
** Copyright (C) 2022 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 "imagecache/smallimagecacheprovider.h"
#include <QQuickAsyncImageProvider>
namespace QmlDesigner {
class PropertyEditorImageProvider : public QQuickAsyncImageProvider
{
public:
PropertyEditorImageProvider(AsynchronousImageCache &imageCache, const QImage &defaultImage = {})
: m_smallImageCacheProvider(imageCache, defaultImage)
{}
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override;
private:
SmallImageCacheProvider m_smallImageCacheProvider;
};
} // namespace QmlDesigner

View File

@@ -95,9 +95,12 @@ static QObject *variantToQObject(const QVariant &value)
namespace QmlDesigner {
PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor) :
m_view(new Quick2PropertyEditorView), m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)), m_dummyPropertyEditorValue(new PropertyEditorValue()),
m_contextObject(new PropertyEditorContextObject())
PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor,
AsynchronousImageCache &imageCache)
: m_view(new Quick2PropertyEditorView(imageCache))
, m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor))
, m_dummyPropertyEditorValue(new PropertyEditorValue())
, m_contextObject(new PropertyEditorContextObject())
{
m_view->engine()->setOutputWarningsToStandardError(QmlDesignerPlugin::instance()
->settings().value(DesignerSettingsKey::SHOW_PROPERTYEDITOR_WARNINGS).toBool());
@@ -115,7 +118,9 @@ PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyE
PropertyEditorQmlBackend::~PropertyEditorQmlBackend() = default;
void PropertyEditorQmlBackend::setupPropertyEditorValue(const PropertyName &name, PropertyEditorView *propertyEditor, const QString &type)
void PropertyEditorQmlBackend::setupPropertyEditorValue(const PropertyName &name,
PropertyEditorView *propertyEditor,
const QString &type)
{
QmlDesigner::PropertyName propertyName(name);
propertyName.replace('.', '_');

View File

@@ -50,7 +50,8 @@ class PropertyEditorQmlBackend
public:
PropertyEditorQmlBackend(PropertyEditorView *propertyEditor);
PropertyEditorQmlBackend(PropertyEditorView *propertyEditor,
class AsynchronousImageCache &imageCache);
~PropertyEditorQmlBackend();
void setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor);

View File

@@ -69,16 +69,16 @@ static bool propertyIsAttachedLayoutProperty(const PropertyName &propertyName)
return propertyName.contains("Layout.");
}
PropertyEditorView::PropertyEditorView(QWidget *parent) :
AbstractView(parent),
m_parent(parent),
m_updateShortcut(nullptr),
m_timerId(0),
m_stackedWidget(new PropertyEditorWidget(parent)),
m_qmlBackEndForCurrentType(nullptr),
m_locked(false),
m_setupCompleted(false),
m_singleShotTimer(new QTimer(this))
PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache)
: AbstractView()
, m_imageCache(imageCache)
, m_updateShortcut(nullptr)
, m_timerId(0)
, m_stackedWidget(new PropertyEditorWidget())
, m_qmlBackEndForCurrentType(nullptr)
, m_locked(false)
, m_setupCompleted(false)
, m_singleShotTimer(new QTimer(this))
{
m_qmlDir = PropertyEditorQmlBackend::propertyEditorResourcesPath();
@@ -117,7 +117,7 @@ void PropertyEditorView::setupPane(const TypeName &typeName)
PropertyEditorQmlBackend *qmlBackend = m_qmlBackendHash.value(qmlFile.toString());
if (!qmlBackend) {
qmlBackend = new PropertyEditorQmlBackend(this);
qmlBackend = new PropertyEditorQmlBackend(this, m_imageCache);
qmlBackend->initialSetup(typeName, qmlSpecificsFile, this);
qmlBackend->setSource(qmlFile);
@@ -484,7 +484,7 @@ void PropertyEditorView::setupQmlBackend()
QString currentStateName = currentState().isBaseState() ? currentState().name() : QStringLiteral("invalid state");
if (!currentQmlBackend) {
currentQmlBackend = new PropertyEditorQmlBackend(this);
currentQmlBackend = new PropertyEditorQmlBackend(this, m_imageCache);
m_stackedWidget->addWidget(currentQmlBackend->widget());
m_qmlBackendHash.insert(qmlFile.toString(), currentQmlBackend);

View File

@@ -51,7 +51,7 @@ class PropertyEditorView: public AbstractView
Q_OBJECT
public:
PropertyEditorView(QWidget *parent = nullptr);
PropertyEditorView(class AsynchronousImageCache &imageCache);
~PropertyEditorView() override;
bool hasWidget() const override;
@@ -119,8 +119,8 @@ private: //functions
bool noValidSelection() const;
private: //variables
AsynchronousImageCache &m_imageCache;
ModelNode m_selectedNode;
QWidget *m_parent;
QShortcut *m_updateShortcut;
int m_timerId;
PropertyEditorWidget* m_stackedWidget;

View File

@@ -36,6 +36,7 @@
#include "gradientpresetdefaultlistmodel.h"
#include "itemfiltermodel.h"
#include "propertyeditorcontextobject.h"
#include "propertyeditorimageprovider.h"
#include "propertyeditorqmlbackend.h"
#include "propertyeditorvalue.h"
#include "qmlanchorbindingproxy.h"
@@ -45,11 +46,13 @@
namespace QmlDesigner {
Quick2PropertyEditorView::Quick2PropertyEditorView(QWidget *parent) :
QQuickWidget(parent)
Quick2PropertyEditorView::Quick2PropertyEditorView(AsynchronousImageCache &imageCache)
: QQuickWidget()
{
setResizeMode(QQuickWidget::SizeRootObjectToView);
Theme::setupTheme(engine());
engine()->addImageProvider("qmldesigner_thumbnails",
new PropertyEditorImageProvider(imageCache));
}
void Quick2PropertyEditorView::registerQmlTypes()

View File

@@ -35,7 +35,7 @@ class Quick2PropertyEditorView : public QQuickWidget
Q_OBJECT
public:
explicit Quick2PropertyEditorView(QWidget *parent = nullptr);
explicit Quick2PropertyEditorView(class AsynchronousImageCache &imageCache);
static void registerQmlTypes();
};

View File

@@ -30,12 +30,12 @@
#include <QMetaObject>
#include <QQuickImageResponse>
namespace QmlDesigner {
namespace {
class ImageRespose : public QQuickImageResponse
class ImageResponse : public QQuickImageResponse
{
public:
ImageRespose(const QImage &defaultImage)
ImageResponse(const QImage &defaultImage)
: m_image(defaultImage)
{}
@@ -57,14 +57,18 @@ private:
QImage m_image;
};
} // namespace
namespace QmlDesigner {
QQuickImageResponse *ExplicitImageCacheImageProvider::requestImageResponse(const QString &id,
const QSize &)
{
auto response = std::make_unique<ImageRespose>(m_defaultImage);
auto response = std::make_unique<::ImageResponse>(m_defaultImage);
m_cache.requestImage(
id,
[response = QPointer<ImageRespose>(response.get())](const QImage &image) {
[response = QPointer<::ImageResponse>(response.get())](const QImage &image) {
QMetaObject::invokeMethod(
response,
[response, image] {
@@ -73,7 +77,7 @@ QQuickImageResponse *ExplicitImageCacheImageProvider::requestImageResponse(const
},
Qt::QueuedConnection);
},
[response = QPointer<ImageRespose>(response.get())](ImageCache::AbortReason abortReason) {
[response = QPointer<::ImageResponse>(response.get())](ImageCache::AbortReason abortReason) {
QMetaObject::invokeMethod(
response,
[response, abortReason] {

View File

@@ -0,0 +1,112 @@
/****************************************************************************
**
** Copyright (C) 2022 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 "meshimagecachecollector.h"
#include "imagecacheconnectionmanager.h"
#include <projectexplorer/target.h>
#include <utils/smallstring.h>
#include <qtsupport/qtkitinformation.h>
#include <QTemporaryFile>
namespace QmlDesigner {
MeshImageCacheCollector::MeshImageCacheCollector(
ImageCacheConnectionManager &connectionManager,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize,
ImageCacheCollectorNullImageHandling nullImageHandling)
: m_imageCacheCollector(connectionManager,
captureImageMinimumSize,
captureImageMaximumSize,
nullImageHandling)
{}
MeshImageCacheCollector::~MeshImageCacheCollector() = default;
void MeshImageCacheCollector::start(Utils::SmallStringView name,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback,
AbortCallback abortCallback)
{
QTemporaryFile file(QDir::tempPath() + "/mesh-XXXXXX.qml");
if (file.open()) {
QString qtQuickVersion;
QString qtQuick3DVersion;
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target()->kit());
if (qtVersion && qtVersion->qtVersion() < QtSupport::QtVersionNumber(6, 0, 0)) {
qtQuickVersion = "2.15";
qtQuick3DVersion = "1.15";
}
QString content{
R"(import QtQuick %1
import QtQuick3D %2
Node {
Model {
source: "%3"
DefaultMaterial { id: defaultMaterial; diffuseColor: "#ff999999" }
materials: [ defaultMaterial ]
}
})"};
content = content.arg(qtQuickVersion, qtQuick3DVersion, QString(name));
file.write(content.toUtf8());
file.close();
}
Utils::PathString path{file.fileName()};
m_imageCacheCollector.start(path, state, auxiliaryData, captureCallback, abortCallback);
}
std::pair<QImage, QImage> MeshImageCacheCollector::createImage(Utils::SmallStringView,
Utils::SmallStringView,
const ImageCache::AuxiliaryData &)
{
return {};
}
QIcon MeshImageCacheCollector::createIcon(Utils::SmallStringView,
Utils::SmallStringView,
const ImageCache::AuxiliaryData &)
{
return {};
}
void MeshImageCacheCollector::setTarget(ProjectExplorer::Target *target)
{
m_imageCacheCollector.setTarget(target);
}
ProjectExplorer::Target *MeshImageCacheCollector::target() const
{
return m_imageCacheCollector.target();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,70 @@
/****************************************************************************
**
** Copyright (C) 2022 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 "imagecachecollectorinterface.h"
#include "imagecachecollector.h"
namespace ProjectExplorer {
class Target;
}
namespace QmlDesigner {
class ImageCacheConnectionManager;
class MeshImageCacheCollector final : public ImageCacheCollectorInterface
{
public:
MeshImageCacheCollector(ImageCacheConnectionManager &connectionManager,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize,
ImageCacheCollectorNullImageHandling nullImageHandling = {});
~MeshImageCacheCollector();
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);
ProjectExplorer::Target *target() const;
private:
ImageCacheCollector m_imageCacheCollector;
};
} // namespace QmlDesigner

View File

@@ -0,0 +1,87 @@
/****************************************************************************
**
** Copyright (C) 2022 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 "smallimagecacheprovider.h"
#include <asynchronousimagecache.h>
#include <QMetaObject>
namespace QmlDesigner {
QQuickTextureFactory *ImageResponse::textureFactory() const
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
void ImageResponse::setImage(const QImage &image)
{
m_image = image;
emit finished();
}
void ImageResponse::abort()
{
emit finished();
}
QQuickImageResponse *SmallImageCacheProvider::requestImageResponse(const QString &id, const QSize &)
{
auto response = std::make_unique<QmlDesigner::ImageResponse>(m_defaultImage);
m_cache.requestSmallImage(
id,
[response = QPointer<QmlDesigner::ImageResponse>(response.get())](const QImage &image) {
QMetaObject::invokeMethod(
response,
[response, image] {
if (response)
response->setImage(image);
},
Qt::QueuedConnection);
},
[response = QPointer<QmlDesigner::ImageResponse>(response.get())](
ImageCache::AbortReason abortReason) {
QMetaObject::invokeMethod(
response,
[response, abortReason] {
switch (abortReason) {
case ImageCache::AbortReason::Failed:
if (response)
response->abort();
break;
case ImageCache::AbortReason::Abort:
response->cancel();
break;
}
},
Qt::QueuedConnection);
});
return response.release();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,68 @@
/****************************************************************************
**
** Copyright (C) 2022 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 <QQuickAsyncImageProvider>
#include <QQuickImageResponse>
namespace QmlDesigner {
class AsynchronousImageCache;
class ImageResponse : public QQuickImageResponse
{
public:
ImageResponse(const QImage &defaultImage)
: m_image(defaultImage)
{}
QQuickTextureFactory *textureFactory() const override;
void setImage(const QImage &image);
void abort();
private:
QImage m_image;
};
class SmallImageCacheProvider : public QQuickAsyncImageProvider
{
public:
SmallImageCacheProvider(AsynchronousImageCache &imageCache, const QImage &defaultImage = {})
: m_cache{imageCache}
, m_defaultImage(defaultImage)
{}
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override;
private:
AsynchronousImageCache &m_cache;
QImage m_defaultImage;
};
} // namespace QmlDesigner

View File

@@ -28,11 +28,17 @@
#include <QDateTime>
#include <QFileInfo>
#include <limits>
namespace QmlDesigner {
Sqlite::TimeStamp TimeStampProvider::timeStamp(Utils::SmallStringView name) const
{
return QFileInfo{QString{name}}.lastModified().toSecsSinceEpoch();
QFileInfo info{QString{name}};
if (info.exists())
return info.lastModified().toSecsSinceEpoch();
return {std::numeric_limits<long long>::max()};
}
} // namespace QmlDesigner

View File

@@ -51,7 +51,8 @@ class ViewManagerData;
class QMLDESIGNERCORE_EXPORT ViewManager
{
public:
ViewManager(class AsynchronousImageCache &imageCache);
ViewManager(class AsynchronousImageCache &imageCache,
class AsynchronousImageCache &meshImageCache);
~ViewManager();
void attachRewriterView();

View File

@@ -62,8 +62,9 @@ static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg)
class ViewManagerData
{
public:
ViewManagerData(AsynchronousImageCache &imageCache)
ViewManagerData(AsynchronousImageCache &imageCache, AsynchronousImageCache &meshImageCache)
: itemLibraryView(imageCache)
, propertyEditorView(meshImageCache)
{}
InteractiveConnectionManager connectionManager;
@@ -94,8 +95,8 @@ static CrumbleBar *crumbleBar() {
return QmlDesignerPlugin::instance()->mainWidget()->crumbleBar();
}
ViewManager::ViewManager(AsynchronousImageCache &imageCache)
: d(std::make_unique<ViewManagerData>(imageCache))
ViewManager::ViewManager(AsynchronousImageCache &imageCache, AsynchronousImageCache &meshImageCache)
: d(std::make_unique<ViewManagerData>(imageCache, meshImageCache))
{
d->formEditorView.setGotoErrorCallback([this](int line, int column) {
d->textEditorView.gotoCursorPosition(line, column);

View File

@@ -123,6 +123,8 @@ function(extend_with_qmldesigner_core target_name)
imagecache/imagecachegeneratorinterface.h
imagecache/imagecachestorage.h
imagecache/imagecachestorageinterface.h
imagecache/meshimagecachecollector.cpp
imagecache/meshimagecachecollector.h
imagecache/synchronousimagecache.cpp
imagecache/timestampprovider.cpp
imagecache/timestampprovider.h

View File

@@ -137,7 +137,8 @@ class QmlDesignerPluginPrivate
{
public:
QmlDesignerProjectManager projectManager;
ViewManager viewManager{projectManager.asynchronousImageCache()};
ViewManager viewManager{projectManager.asynchronousImageCache(),
projectManager.asynchronousMeshImageCache()};
DocumentManager documentManager;
ShortCutManager shortCutManager;
SettingsPage settingsPage;

View File

@@ -442,6 +442,10 @@ Project {
"imagecache/imagecachegenerator.h",
"imagecache/imagecachestorageinterface.h",
"imagecache/imagecachestorage.h",
"imagecache/meshimagecachecollector.cpp",
"imagecache/meshimagecachecollector.h",
"imagecache/smallimagecacheprovider.cpp",
"imagecache/smallimagecacheprovider.h",
"imagecache/synchronousimagecache.cpp",
"imagecache/timestampproviderinterface.h",
"imagecache/timestampprovider.h",
@@ -737,6 +741,8 @@ Project {
"propertyeditor/gradientpresetlistmodel.h",
"propertyeditor/propertyeditorcontextobject.cpp",
"propertyeditor/propertyeditorcontextobject.h",
"propertyeditor/propertyeditorimageprovider.cpp",
"propertyeditor/propertyeditorimageprovider.h",
"propertyeditor/propertyeditortransaction.cpp",
"propertyeditor/propertyeditortransaction.h",
"propertyeditor/propertyeditorvalue.cpp",

View File

@@ -51,7 +51,8 @@
#include <imagecache/imagecacheconnectionmanager.h>
#include <imagecache/imagecachegenerator.h>
#include <imagecache/imagecachestorage.h>
#include <imagecache/timestampproviderinterface.h>
#include <imagecache/meshimagecachecollector.h>
#include <imagecache/timestampprovider.h>
#include <coreplugin/icore.h>
@@ -79,7 +80,7 @@ QString defaultImagePath()
return qobject_cast<::QmlProjectManager::QmlBuildSystem *>(target->buildSystem());
}
class TimeStampProvider : public TimeStampProviderInterface
class PreviewTimeStampProvider : public TimeStampProviderInterface
{
public:
Sqlite::TimeStamp timeStamp(Utils::SmallStringView) const override
@@ -102,15 +103,18 @@ class QmlDesignerProjectManager::ImageCacheData
{
public:
Sqlite::Database database{Utils::PathString{
Core::ICore::cacheResourcePath("imagecache-v2.db").toString()},
Core::ICore::cacheResourcePath("imagecache-v2.db").toString()},
Sqlite::JournalMode::Wal,
Sqlite::LockingMode::Normal};
ImageCacheStorage<Sqlite::Database> storage{database};
ImageCacheConnectionManager connectionManager;
ImageCacheCollector collector{connectionManager, QSize{300, 300}, QSize{600, 600}};
ImageCacheGenerator generator{collector, storage};
MeshImageCacheCollector meshImageCollector{connectionManager, QSize{300, 300}, QSize{600, 600}};
ImageCacheGenerator meshGenerator{meshImageCollector, storage};
ImageCacheCollector nodeInstanceCollector{connectionManager, QSize{300, 300}, QSize{600, 600}};
ImageCacheGenerator nodeInstanceGenerator{nodeInstanceCollector, storage};
TimeStampProvider timeStampProvider;
AsynchronousImageCache asynchronousImageCache{storage, generator, timeStampProvider};
AsynchronousImageCache asynchronousImageCache{storage, nodeInstanceGenerator, timeStampProvider};
AsynchronousImageCache asynchronousMeshImageCache{storage, meshGenerator, timeStampProvider};
};
class QmlDesignerProjectManager::PreviewImageCacheData
@@ -135,7 +139,7 @@ public:
QSize{300, 300},
QSize{1000, 1000},
ImageCacheCollectorNullImageHandling::DontCaptureNullImage};
TimeStampProvider timeStampProvider;
PreviewTimeStampProvider timeStampProvider;
AsynchronousImageFactory factory;
::ProjectExplorer::Target *activeTarget = nullptr;
};
@@ -180,6 +184,11 @@ AsynchronousImageCache &QmlDesignerProjectManager::asynchronousImageCache()
return imageCacheData()->asynchronousImageCache;
}
AsynchronousImageCache &QmlDesignerProjectManager::asynchronousMeshImageCache()
{
return imageCacheData()->asynchronousMeshImageCache;
}
void QmlDesignerProjectManager::editorOpened(::Core::IEditor *) {}
void QmlDesignerProjectManager::currentEditorChanged(::Core::IEditor *)
@@ -218,17 +227,21 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache
m_imageCacheData = std::make_unique<ImageCacheData>();
auto setTargetInImageCache =
[imageCacheData = m_imageCacheData.get()](ProjectExplorer::Target *target) {
if (target == imageCacheData->collector.target())
if (target == imageCacheData->nodeInstanceCollector.target())
return;
if (target)
imageCacheData->asynchronousImageCache.clean();
imageCacheData->collector.setTarget(target);
// TODO wrap in function in image cache data
imageCacheData->meshImageCollector.setTarget(target);
imageCacheData->nodeInstanceCollector.setTarget(target);
};
if (auto project = ProjectExplorer::SessionManager::startupProject(); project) {
m_imageCacheData->collector.setTarget(project->activeTarget());
// TODO wrap in function in image cache data
m_imageCacheData->meshImageCollector.setTarget(project->activeTarget());
m_imageCacheData->nodeInstanceCollector.setTarget(project->activeTarget());
QObject::connect(project,
&ProjectExplorer::Project::activeTargetChanged,
this,

View File

@@ -59,6 +59,7 @@ public:
void registerPreviewImageProvider(QQmlEngine *engine) const;
class AsynchronousImageCache &asynchronousImageCache();
class AsynchronousImageCache &asynchronousMeshImageCache();
private:
void editorOpened(::Core::IEditor *editor);