forked from qt-creator/qt-creator
EffectComposer: Allow user to choose custom preview image
The current preview image selection and any custom image path is stored in the .qep file on effect save. When a custom image outside of project is chosen, the image is imported into the project into the default image assets folder. Fixes: QDS-13438 Change-Id: If15049612bbca9a744a383c49716cb3648a52af3 Reviewed-by: Mats Honkamaa <mats.honkamaa@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: Ali Kianian <ali.kianian@qt.io>
This commit is contained in:
@@ -22,12 +22,17 @@ StudioControls.ComboBox {
|
|||||||
|
|
||||||
required property Item mainRoot
|
required property Item mainRoot
|
||||||
|
|
||||||
property var images: ["images/preview0.png",
|
property var images: [Qt.url(""),
|
||||||
"images/preview1.png",
|
Qt.url("images/preview0.png"),
|
||||||
"images/preview2.png",
|
Qt.url("images/preview1.png"),
|
||||||
"images/preview3.png",
|
Qt.url("images/preview2.png"),
|
||||||
"images/preview4.png"]
|
Qt.url("images/preview3.png"),
|
||||||
property string selectedImage: images[0]
|
Qt.url("images/preview4.png")]
|
||||||
|
property url selectedImage: EffectComposerBackend.effectComposerModel.currentPreviewImage != Qt.url("")
|
||||||
|
? EffectComposerBackend.effectComposerModel.currentPreviewImage
|
||||||
|
: images[1]
|
||||||
|
|
||||||
|
Component.onCompleted: EffectComposerBackend.effectComposerModel.currentPreviewImage = images[1]
|
||||||
|
|
||||||
readonly property int popupHeight: Math.min(800, col.height + 2)
|
readonly property int popupHeight: Math.min(800, col.height + 2)
|
||||||
|
|
||||||
@@ -122,45 +127,77 @@ StudioControls.ComboBox {
|
|||||||
border.width: 1
|
border.width: 1
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
HelperWidgets.ScrollView {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
id: col
|
id: setCustomItem
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
|
||||||
padding: 10
|
HelperWidgets.Button {
|
||||||
spacing: 10
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
text: qsTr("Set Custom Image")
|
||||||
|
onClicked: {
|
||||||
|
EffectComposerBackend.effectComposerModel.chooseCustomPreviewImage()
|
||||||
|
root.popup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.images
|
|
||||||
|
|
||||||
Rectangle {
|
HelperWidgets.ScrollView {
|
||||||
required property int index
|
width: parent.width - 2
|
||||||
required property var modelData
|
height: parent.height - setCustomItem.height
|
||||||
|
|
||||||
color: "transparent"
|
clip: true
|
||||||
border.color: root.selectedImage === modelData ? StudioTheme.Values.themeInteraction
|
|
||||||
: "transparent"
|
|
||||||
|
|
||||||
width: 200
|
Column {
|
||||||
height: 200
|
id: col
|
||||||
|
|
||||||
Image {
|
padding: 10
|
||||||
source: modelData
|
spacing: 10
|
||||||
anchors.fill: parent
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
anchors.margins: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
Repeater {
|
||||||
anchors.fill: parent
|
model: root.images
|
||||||
|
|
||||||
onClicked: {
|
Rectangle {
|
||||||
root.selectedImage = root.images[index]
|
required property int index
|
||||||
root.popup.close()
|
required property var modelData
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
border.color: root.selectedImage === modelData ? StudioTheme.Values.themeInteraction
|
||||||
|
: "transparent"
|
||||||
|
|
||||||
|
width: 200
|
||||||
|
height: 200
|
||||||
|
visible: index > 0
|
||||||
|
|| EffectComposerBackend.effectComposerModel.customPreviewImage !== Qt.url("")
|
||||||
|
|
||||||
|
Image {
|
||||||
|
source: index > 0
|
||||||
|
? parent.modelData
|
||||||
|
: EffectComposerBackend.effectComposerModel.customPreviewImage
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
anchors.margins: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (parent.index > 0) {
|
||||||
|
EffectComposerBackend.effectComposerModel.currentPreviewImage
|
||||||
|
= root.images[index]
|
||||||
|
} else {
|
||||||
|
EffectComposerBackend.effectComposerModel.currentPreviewImage
|
||||||
|
= EffectComposerBackend.effectComposerModel.customPreviewImage
|
||||||
|
}
|
||||||
|
root.popup.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,18 +10,24 @@
|
|||||||
#include "syntaxhighlighterdata.h"
|
#include "syntaxhighlighterdata.h"
|
||||||
#include "uniform.h"
|
#include "uniform.h"
|
||||||
|
|
||||||
|
#include <asset.h>
|
||||||
|
#include <designdocument.h>
|
||||||
|
#include <modelnodeoperations.h>
|
||||||
|
#include <qmldesignerplugin.h>
|
||||||
|
#include <uniquename.h>
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
#include <projectexplorer/projecttree.h>
|
#include <projectexplorer/projecttree.h>
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
#include <qtsupport/qtkitaspect.h>
|
#include <qtsupport/qtkitaspect.h>
|
||||||
|
|
||||||
#include <uniquename.h>
|
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
|
||||||
#include <modelnodeoperations.h>
|
|
||||||
|
|
||||||
#include <QByteArrayView>
|
#include <QByteArrayView>
|
||||||
|
#include <QFileDialog>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
@@ -247,6 +253,47 @@ bool EffectComposerModel::nameExists(const QString &name) const
|
|||||||
return QFile::exists(path.arg(name));
|
return QFile::exists(path.arg(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EffectComposerModel::chooseCustomPreviewImage()
|
||||||
|
{
|
||||||
|
QTimer::singleShot(0, this, [&]() {
|
||||||
|
using Utils::FilePath;
|
||||||
|
static FilePath lastDir;
|
||||||
|
const QStringList &suffixes = QmlDesigner::Asset::supportedImageSuffixes();
|
||||||
|
QmlDesigner::DesignDocument *document = QmlDesigner::QmlDesignerPlugin::instance()->currentDesignDocument();
|
||||||
|
const FilePath currentDir = lastDir.isEmpty() ? document->fileName().parentDir()
|
||||||
|
: lastDir;
|
||||||
|
const QStringList fileNames = QFileDialog::getOpenFileNames(Core::ICore::dialogParent(),
|
||||||
|
tr("Select custom effect background image"),
|
||||||
|
currentDir.toFSPathString(),
|
||||||
|
tr("Image Files (%1)").arg(suffixes.join(" ")));
|
||||||
|
|
||||||
|
if (!fileNames.isEmpty()) {
|
||||||
|
FilePath imageFile = FilePath::fromString(fileNames.first());
|
||||||
|
lastDir = imageFile.absolutePath();
|
||||||
|
if (imageFile.exists()) {
|
||||||
|
FilePath projDir = QmlDesigner::QmlDesignerPlugin::instance()->documentManager()
|
||||||
|
.currentProjectDirPath();
|
||||||
|
if (!imageFile.isChildOf(projDir)) {
|
||||||
|
FilePath imagesDir = QmlDesigner::ModelNodeOperations::getImagesDefaultDirectory();
|
||||||
|
FilePath targetFile = imagesDir.pathAppended(imageFile.fileName());
|
||||||
|
if (!targetFile.exists())
|
||||||
|
imageFile.copyFile(targetFile);
|
||||||
|
if (targetFile.exists())
|
||||||
|
imageFile = targetFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_customPreviewImage = QUrl::fromLocalFile(imageFile.toFSPathString());
|
||||||
|
m_currentPreviewImage = m_customPreviewImage;
|
||||||
|
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
|
||||||
|
emit currentPreviewImageChanged();
|
||||||
|
emit customPreviewImageChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QString EffectComposerModel::fragmentShader() const
|
QString EffectComposerModel::fragmentShader() const
|
||||||
{
|
{
|
||||||
return m_fragmentShader;
|
return m_fragmentShader;
|
||||||
@@ -1000,6 +1047,9 @@ void EffectComposerModel::saveComposition(const QString &name)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Utils::FilePath compositionPath = Utils::FilePath::fromString(path);
|
||||||
|
const Utils::FilePath compositionDir = compositionPath.absolutePath();
|
||||||
|
|
||||||
updateExtraMargin();
|
updateExtraMargin();
|
||||||
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
@@ -1007,6 +1057,17 @@ void EffectComposerModel::saveComposition(const QString &name)
|
|||||||
json.insert("version", 1);
|
json.insert("version", 1);
|
||||||
json.insert("tool", "EffectComposer");
|
json.insert("tool", "EffectComposer");
|
||||||
|
|
||||||
|
Utils::FilePath customPreviewPath = Utils::FilePath::fromUrl(m_customPreviewImage);
|
||||||
|
if (m_customPreviewImage.isLocalFile())
|
||||||
|
customPreviewPath = customPreviewPath.relativePathFrom(compositionDir);
|
||||||
|
json.insert("customPreviewImage", customPreviewPath.toUrl().toString());
|
||||||
|
|
||||||
|
QUrl previewUrl = m_currentPreviewImage;
|
||||||
|
if (m_currentPreviewImage == m_customPreviewImage)
|
||||||
|
previewUrl = customPreviewPath.toUrl();
|
||||||
|
|
||||||
|
json.insert("previewImage", previewUrl.toString());
|
||||||
|
|
||||||
// Add nodes
|
// Add nodes
|
||||||
QJsonArray nodesArray;
|
QJsonArray nodesArray;
|
||||||
for (const CompositionNode *node : std::as_const(m_nodes)) {
|
for (const CompositionNode *node : std::as_const(m_nodes)) {
|
||||||
@@ -1034,7 +1095,7 @@ void EffectComposerModel::saveComposition(const QString &name)
|
|||||||
saveFile.close();
|
saveFile.close();
|
||||||
|
|
||||||
setCurrentComposition(name);
|
setCurrentComposition(name);
|
||||||
setCompositionPath(Utils::FilePath::fromString(path));
|
setCompositionPath(compositionPath);
|
||||||
|
|
||||||
saveResources(name);
|
saveResources(name);
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
@@ -1083,10 +1144,11 @@ void EffectComposerModel::openComposition(const QString &path)
|
|||||||
{
|
{
|
||||||
clear(true);
|
clear(true);
|
||||||
|
|
||||||
const QString effectName = QFileInfo(path).baseName();
|
Utils::FilePath effectPath = Utils::FilePath::fromString(path);
|
||||||
|
const QString effectName = effectPath.baseName();
|
||||||
|
|
||||||
setCurrentComposition(effectName);
|
setCurrentComposition(effectName);
|
||||||
setCompositionPath(Utils::FilePath::fromString(path));
|
setCompositionPath(effectPath);
|
||||||
|
|
||||||
QFile compFile(path);
|
QFile compFile(path);
|
||||||
if (!compFile.open(QIODevice::ReadOnly)) {
|
if (!compFile.open(QIODevice::ReadOnly)) {
|
||||||
@@ -1166,6 +1228,39 @@ void EffectComposerModel::openComposition(const QString &path)
|
|||||||
else
|
else
|
||||||
resetRootFragmentShader();
|
resetRootFragmentShader();
|
||||||
|
|
||||||
|
m_currentPreviewImage.clear();
|
||||||
|
if (json.contains("previewImage")) {
|
||||||
|
const QString imageStr = json["previewImage"].toString();
|
||||||
|
if (!imageStr.isEmpty()) {
|
||||||
|
const QUrl imageUrl{imageStr};
|
||||||
|
Utils::FilePath imagePath = Utils::FilePath::fromUrl(imageUrl);
|
||||||
|
if (imageStr.startsWith("images/preview")) { // built-in preview image
|
||||||
|
m_currentPreviewImage = imageUrl;
|
||||||
|
} else if (imagePath.isAbsolutePath()) {
|
||||||
|
if (imagePath.exists())
|
||||||
|
m_currentPreviewImage = imageUrl;
|
||||||
|
} else {
|
||||||
|
imagePath = effectPath.absolutePath().resolvePath(imagePath);
|
||||||
|
if (imagePath.exists())
|
||||||
|
m_currentPreviewImage = imagePath.toUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_customPreviewImage.clear();
|
||||||
|
if (json.contains("customPreviewImage")) {
|
||||||
|
QUrl imageUrl{json["customPreviewImage"].toString()};
|
||||||
|
Utils::FilePath imagePath = Utils::FilePath::fromUrl(imageUrl);
|
||||||
|
if (imagePath.isAbsolutePath()) {
|
||||||
|
if (imagePath.exists())
|
||||||
|
m_customPreviewImage = imageUrl;
|
||||||
|
} else {
|
||||||
|
imagePath = effectPath.absolutePath().resolvePath(imagePath);
|
||||||
|
if (imagePath.exists())
|
||||||
|
m_customPreviewImage = imagePath.toUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (json.contains("nodes") && json["nodes"].isArray()) {
|
if (json.contains("nodes") && json["nodes"].isArray()) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
QHash<QString, int> refCounts;
|
QHash<QString, int> refCounts;
|
||||||
@@ -1194,6 +1289,8 @@ void EffectComposerModel::openComposition(const QString &path)
|
|||||||
|
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
emit nodesChanged();
|
emit nodesChanged();
|
||||||
|
emit currentPreviewImageChanged();
|
||||||
|
emit customPreviewImageChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectComposerModel::saveResources(const QString &name)
|
void EffectComposerModel::saveResources(const QString &name)
|
||||||
@@ -2151,6 +2248,28 @@ void EffectComposerModel::setCurrentComposition(const QString &newCurrentComposi
|
|||||||
m_shadersCodeEditor.reset();
|
m_shadersCodeEditor.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl EffectComposerModel::customPreviewImage() const
|
||||||
|
{
|
||||||
|
return m_customPreviewImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl EffectComposerModel::currentPreviewImage() const
|
||||||
|
{
|
||||||
|
return m_currentPreviewImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectComposerModel::setCurrentPreviewImage(const QUrl &path)
|
||||||
|
{
|
||||||
|
if (m_currentPreviewImage == path)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_nodes.isEmpty())
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
|
||||||
|
m_currentPreviewImage = path;
|
||||||
|
emit currentPreviewImageChanged();
|
||||||
|
}
|
||||||
|
|
||||||
Utils::FilePath EffectComposerModel::compositionPath() const
|
Utils::FilePath EffectComposerModel::compositionPath() const
|
||||||
{
|
{
|
||||||
return m_compositionPath;
|
return m_compositionPath;
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
class Target;
|
class Target;
|
||||||
@@ -53,6 +54,8 @@ class EffectComposerModel : public QAbstractListModel
|
|||||||
Q_PROPERTY(bool isEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged)
|
Q_PROPERTY(bool isEnabled READ isEnabled WRITE setIsEnabled NOTIFY isEnabledChanged)
|
||||||
Q_PROPERTY(bool hasValidTarget READ hasValidTarget WRITE setHasValidTarget NOTIFY hasValidTargetChanged)
|
Q_PROPERTY(bool hasValidTarget READ hasValidTarget WRITE setHasValidTarget NOTIFY hasValidTargetChanged)
|
||||||
Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged)
|
Q_PROPERTY(QString currentComposition READ currentComposition WRITE setCurrentComposition NOTIFY currentCompositionChanged)
|
||||||
|
Q_PROPERTY(QUrl currentPreviewImage READ currentPreviewImage WRITE setCurrentPreviewImage NOTIFY currentPreviewImageChanged)
|
||||||
|
Q_PROPERTY(QUrl customPreviewImage READ customPreviewImage NOTIFY customPreviewImageChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EffectComposerModel(QObject *parent = nullptr);
|
EffectComposerModel(QObject *parent = nullptr);
|
||||||
@@ -77,6 +80,7 @@ public:
|
|||||||
Q_INVOKABLE void assignToSelected();
|
Q_INVOKABLE void assignToSelected();
|
||||||
Q_INVOKABLE QString getUniqueEffectName() const;
|
Q_INVOKABLE QString getUniqueEffectName() const;
|
||||||
Q_INVOKABLE bool nameExists(const QString &name) const;
|
Q_INVOKABLE bool nameExists(const QString &name) const;
|
||||||
|
Q_INVOKABLE void chooseCustomPreviewImage();
|
||||||
|
|
||||||
bool shadersUpToDate() const;
|
bool shadersUpToDate() const;
|
||||||
void setShadersUpToDate(bool newShadersUpToDate);
|
void setShadersUpToDate(bool newShadersUpToDate);
|
||||||
@@ -116,6 +120,10 @@ public:
|
|||||||
QString currentComposition() const;
|
QString currentComposition() const;
|
||||||
void setCurrentComposition(const QString &newCurrentComposition);
|
void setCurrentComposition(const QString &newCurrentComposition);
|
||||||
|
|
||||||
|
QUrl customPreviewImage() const;
|
||||||
|
QUrl currentPreviewImage() const;
|
||||||
|
void setCurrentPreviewImage(const QUrl &path);
|
||||||
|
|
||||||
Utils::FilePath compositionPath() const;
|
Utils::FilePath compositionPath() const;
|
||||||
void setCompositionPath(const Utils::FilePath &newCompositionPath);
|
void setCompositionPath(const Utils::FilePath &newCompositionPath);
|
||||||
|
|
||||||
@@ -140,6 +148,8 @@ signals:
|
|||||||
void hasUnsavedChangesChanged();
|
void hasUnsavedChangesChanged();
|
||||||
void assignToSelectedTriggered(const QString &effectPath);
|
void assignToSelectedTriggered(const QString &effectPath);
|
||||||
void removePropertiesFromScene(QSet<QByteArray> props, const QString &typeName);
|
void removePropertiesFromScene(QSet<QByteArray> props, const QString &typeName);
|
||||||
|
void currentPreviewImageChanged();
|
||||||
|
void customPreviewImageChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
@@ -242,6 +252,8 @@ private:
|
|||||||
QString m_effectTypePrefix;
|
QString m_effectTypePrefix;
|
||||||
Utils::FilePath m_compositionPath;
|
Utils::FilePath m_compositionPath;
|
||||||
Utils::UniqueObjectLatePtr<EffectShadersCodeEditor> m_shadersCodeEditor;
|
Utils::UniqueObjectLatePtr<EffectShadersCodeEditor> m_shadersCodeEditor;
|
||||||
|
QUrl m_currentPreviewImage;
|
||||||
|
QUrl m_customPreviewImage;
|
||||||
|
|
||||||
const QRegularExpression m_spaceReg = QRegularExpression("\\s+");
|
const QRegularExpression m_spaceReg = QRegularExpression("\\s+");
|
||||||
};
|
};
|
||||||
|
@@ -137,7 +137,7 @@ bool useLayerEffect();
|
|||||||
bool validateEffect(const QString &effectPath);
|
bool validateEffect(const QString &effectPath);
|
||||||
bool isEffectComposerActivated();
|
bool isEffectComposerActivated();
|
||||||
|
|
||||||
Utils::FilePath getImagesDefaultDirectory();
|
QMLDESIGNERCOMPONENTS_EXPORT Utils::FilePath getImagesDefaultDirectory();
|
||||||
|
|
||||||
//Item Library and Assets related drop operations
|
//Item Library and Assets related drop operations
|
||||||
QMLDESIGNERCOMPONENTS_EXPORT ModelNode handleItemLibraryEffectDrop(const QString &effectPath,
|
QMLDESIGNERCOMPONENTS_EXPORT ModelNode handleItemLibraryEffectDrop(const QString &effectPath,
|
||||||
|
Reference in New Issue
Block a user