QmlDesigner: Implement Texture Editor view

Fixes: QDS-8209
Change-Id: Ief0c9f56da79841c745595024dbcd9219072b681
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2022-11-17 12:54:19 +02:00
parent 9880d9a33a
commit 815dd39e47
32 changed files with 2324 additions and 55 deletions

View File

@@ -0,0 +1,866 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "textureeditorview.h"
#include "textureeditorqmlbackend.h"
#include "textureeditorcontextobject.h"
#include "textureeditordynamicpropertiesproxymodel.h"
#include "propertyeditorvalue.h"
#include "textureeditortransaction.h"
#include "assetslibrarywidget.h"
#include <auxiliarydataproperties.h>
#include <bindingproperty.h>
#include <dynamicpropertiesmodel.h>
#include <metainfo.h>
#include <nodeinstanceview.h>
#include <nodelistproperty.h>
#include <nodemetainfo.h>
#include <nodeproperty.h>
#include <rewritingexception.h>
#include <variantproperty.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
#include <qmltimeline.h>
#include <theme.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <designmodewidget.h>
#include <propertyeditorqmlbackend.h>
#include <utils/environment.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <qmldesignerplugin.h>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QQuickWidget>
#include <QQuickItem>
#include <QScopedPointer>
#include <QStackedWidget>
#include <QShortcut>
#include <QTimer>
#include <QColorDialog>
namespace QmlDesigner {
TextureEditorView::TextureEditorView(ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies}
, m_stackedWidget(new QStackedWidget)
, m_dynamicPropertiesModel(new Internal::DynamicPropertiesModel(true, this))
{
m_updateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F12), m_stackedWidget);
connect(m_updateShortcut, &QShortcut::activated, this, &TextureEditorView::reloadQml);
m_ensureMatLibTimer.callOnTimeout([this] {
if (model() && model()->rewriterView() && !model()->rewriterView()->hasIncompleteTypeInformation()
&& model()->rewriterView()->errors().isEmpty()) {
ensureMaterialLibraryNode();
if (m_qmlBackEnd && m_qmlBackEnd->contextObject())
m_qmlBackEnd->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid());
m_ensureMatLibTimer.stop();
}
});
m_stackedWidget->setStyleSheet(Theme::replaceCssColors(
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
m_stackedWidget->setMinimumWidth(250);
QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_TEXTUREEDITOR_TIME);
TextureEditorDynamicPropertiesProxyModel::registerDeclarativeType();
}
TextureEditorView::~TextureEditorView()
{
qDeleteAll(m_qmlBackendHash);
}
// from texture editor to model
void TextureEditorView::changeValue(const QString &name)
{
PropertyName propertyName = name.toUtf8();
if (propertyName.isNull() || locked() || noValidSelection() || propertyName == "id"
|| propertyName == Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY) {
return;
}
PropertyName underscoreName(propertyName);
underscoreName.replace('.', '_');
PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName));
if (!value)
return;
if (propertyName.endsWith("__AUX")) {
commitAuxValueToModel(propertyName, value->value());
return;
}
const NodeMetaInfo metaInfo = m_selectedTexture.metaInfo();
QVariant castedValue;
if (auto property = metaInfo.property(propertyName)) {
castedValue = property.castedValue(value->value());
} else {
qWarning() << __FUNCTION__ << propertyName << "cannot be casted (metainfo)";
return;
}
if (value->value().isValid() && !castedValue.isValid()) {
qWarning() << __FUNCTION__ << propertyName << "not properly casted (metainfo)";
return;
}
bool propertyTypeUrl = false;
if (auto property = metaInfo.property(propertyName)) {
if (property.propertyType().isUrl()) {
// turn absolute local file paths into relative paths
propertyTypeUrl = true;
QString filePath = castedValue.toUrl().toString();
QFileInfo fi(filePath);
if (fi.exists() && fi.isAbsolute()) {
QDir fileDir(QFileInfo(model()->fileUrl().toLocalFile()).absolutePath());
castedValue = QUrl(fileDir.relativeFilePath(filePath));
}
}
}
if (name == "state" && castedValue.toString() == "base state")
castedValue = "";
if (castedValue.type() == QVariant::Color) {
QColor color = castedValue.value<QColor>();
QColor newColor = QColor(color.name());
newColor.setAlpha(color.alpha());
castedValue = QVariant(newColor);
}
if (!value->value().isValid() || (propertyTypeUrl && value->value().toString().isEmpty())) { // reset
removePropertyFromModel(propertyName);
} else {
// QVector*D(0, 0, 0) detects as null variant though it is valid value
if (castedValue.isValid()
&& (!castedValue.isNull() || castedValue.type() == QVariant::Vector2D
|| castedValue.type() == QVariant::Vector3D
|| castedValue.type() == QVariant::Vector4D)) {
commitVariantValueToModel(propertyName, castedValue);
}
}
}
static bool isTrueFalseLiteral(const QString &expression)
{
return (expression.compare("false", Qt::CaseInsensitive) == 0)
|| (expression.compare("true", Qt::CaseInsensitive) == 0);
}
void TextureEditorView::changeExpression(const QString &propertyName)
{
PropertyName name = propertyName.toUtf8();
if (name.isNull() || locked() || noValidSelection())
return;
executeInTransaction("TextureEditorView::changeExpression", [this, name] {
PropertyName underscoreName(name);
underscoreName.replace('.', '_');
QmlObjectNode qmlObjectNode(m_selectedTexture);
PropertyEditorValue *value = m_qmlBackEnd->propertyValueForName(QString::fromLatin1(underscoreName));
if (!value) {
qWarning() << __FUNCTION__ << "no value for " << underscoreName;
return;
}
if (auto property = m_selectedTexture.metaInfo().property(name)) {
auto propertyTypeName = property.propertyType().typeName();
if (propertyTypeName == "QColor") {
if (QColor(value->expression().remove('"')).isValid()) {
qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"')));
return;
}
} else if (propertyTypeName == "bool") {
if (isTrueFalseLiteral(value->expression())) {
if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
qmlObjectNode.setVariantProperty(name, true);
else
qmlObjectNode.setVariantProperty(name, false);
return;
}
} else if (propertyTypeName == "int") {
bool ok;
int intValue = value->expression().toInt(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, intValue);
return;
}
} else if (propertyTypeName == "qreal") {
bool ok;
qreal realValue = value->expression().toDouble(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, realValue);
return;
}
} else if (propertyTypeName == "QVariant") {
bool ok;
qreal realValue = value->expression().toDouble(&ok);
if (ok) {
qmlObjectNode.setVariantProperty(name, realValue);
return;
} else if (isTrueFalseLiteral(value->expression())) {
if (value->expression().compare("true", Qt::CaseInsensitive) == 0)
qmlObjectNode.setVariantProperty(name, true);
else
qmlObjectNode.setVariantProperty(name, false);
return;
}
}
}
if (value->expression().isEmpty()) {
value->resetValue();
return;
}
if (qmlObjectNode.expression(name) != value->expression() || !qmlObjectNode.propertyAffectedByCurrentState(name))
qmlObjectNode.setBindingProperty(name, value->expression());
});
}
void TextureEditorView::exportPropertyAsAlias(const QString &name)
{
if (name.isNull() || locked() || noValidSelection())
return;
executeInTransaction("TextureEditorView::exportPopertyAsAlias", [this, name] {
const QString id = m_selectedTexture.validId();
QString upperCasePropertyName = name;
upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper());
QString aliasName = id + upperCasePropertyName;
aliasName.replace(".", ""); //remove all dots
PropertyName propertyName = aliasName.toUtf8();
if (rootModelNode().hasProperty(propertyName)) {
Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"),
tr("Property %1 does already exist for root component.").arg(aliasName));
return;
}
rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name);
});
}
void TextureEditorView::removeAliasExport(const QString &name)
{
if (name.isNull() || locked() || noValidSelection())
return;
executeInTransaction("TextureEditorView::removeAliasExport", [this, name] {
const QString id = m_selectedTexture.validId();
const QList<BindingProperty> bindingProps = rootModelNode().bindingProperties();
for (const BindingProperty &property : bindingProps) {
if (property.expression() == (id + "." + name)) {
rootModelNode().removeProperty(property.name());
break;
}
}
});
}
bool TextureEditorView::locked() const
{
return m_locked;
}
void TextureEditorView::currentTimelineChanged(const ModelNode &)
{
m_qmlBackEnd->contextObject()->setHasActiveTimeline(QmlTimeline::hasActiveTimeline(this));
}
Internal::DynamicPropertiesModel *TextureEditorView::dynamicPropertiesModel() const
{
return m_dynamicPropertiesModel;
}
TextureEditorView *TextureEditorView::instance()
{
static TextureEditorView *s_instance = nullptr;
if (s_instance)
return s_instance;
const auto views = QmlDesignerPlugin::instance()->viewManager().views();
for (auto *view : views) {
TextureEditorView *myView = qobject_cast<TextureEditorView *>(view);
if (myView)
s_instance = myView;
}
QTC_ASSERT(s_instance, return nullptr);
return s_instance;
}
void TextureEditorView::timerEvent(QTimerEvent *timerEvent)
{
if (m_timerId == timerEvent->timerId())
resetView();
}
void TextureEditorView::resetView()
{
if (!model())
return;
m_locked = true;
if (m_timerId)
killTimer(m_timerId);
setupQmlBackend();
if (m_qmlBackEnd)
m_qmlBackEnd->emitSelectionChanged();
m_locked = false;
if (m_timerId)
m_timerId = 0;
}
// static
QString TextureEditorView::textureEditorResourcesPath()
{
#ifdef SHARE_QML_PATH
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/textureEditorQmlSource";
#endif
return Core::ICore::resourcePath("qmldesigner/textureEditorQmlSource").toString();
}
void TextureEditorView::applyTextureToSelectedModel(const ModelNode &texture)
{
if (!m_selectedModel.isValid())
return;
QTC_ASSERT(texture.isValid(), return);
emitCustomNotification("apply_texture_to_model3D", {m_selectedModel, m_selectedTexture});
}
void TextureEditorView::handleToolBarAction(int action)
{
QTC_ASSERT(m_hasQuick3DImport, return);
switch (action) {
case TextureEditorContextObject::ApplyToSelected: {
applyTextureToSelectedModel(m_selectedTexture);
break;
}
case TextureEditorContextObject::AddNewTexture: {
if (!model())
break;
executeInTransaction("TextureEditorView:handleToolBarAction", [&] {
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid())
return;
NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.Texture");
ModelNode newTextureNode = createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(),
metaInfo.minorVersion());
newTextureNode.validId();
matLib.defaultNodeListProperty().reparentHere(newTextureNode);
});
break;
}
case TextureEditorContextObject::DeleteCurrentTexture: {
if (m_selectedTexture.isValid())
m_selectedTexture.destroy();
break;
}
case TextureEditorContextObject::OpenMaterialBrowser: {
QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("MaterialBrowser", true);
break;
}
}
}
void TextureEditorView::setupQmlBackend()
{
QUrl qmlPaneUrl;
QUrl qmlSpecificsUrl;
QString specificQmlData;
if (m_selectedTexture.isValid() && m_hasQuick3DImport && (materialLibraryNode().isValid() || m_hasTextureRoot)) {
qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/TextureEditorPane.qml");
TypeName diffClassName;
if (NodeMetaInfo metaInfo = m_selectedTexture.metaInfo()) {
diffClassName = metaInfo.typeName();
for (const NodeMetaInfo &metaInfo : metaInfo.classHierarchy()) {
if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsUrl))
break;
qmlSpecificsUrl = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName()
+ "Specifics", metaInfo);
diffClassName = metaInfo.typeName();
}
if (diffClassName != m_selectedTexture.type()) {
specificQmlData = PropertyEditorQmlBackend::templateGeneration(metaInfo,
model()->metaInfo(
diffClassName),
m_selectedTexture);
}
}
} else {
qmlPaneUrl = QUrl::fromLocalFile(textureEditorResourcesPath() + "/EmptyTextureEditorPane.qml");
}
TextureEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlPaneUrl.toString());
QString currentStateName = currentState().isBaseState() ? currentState().name() : "invalid state";
if (!currentQmlBackend) {
currentQmlBackend = new TextureEditorQmlBackend(this);
m_stackedWidget->addWidget(currentQmlBackend->widget());
m_qmlBackendHash.insert(qmlPaneUrl.toString(), currentQmlBackend);
currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this);
currentQmlBackend->setSource(qmlPaneUrl);
QObject *rootObj = currentQmlBackend->widget()->rootObject();
QObject::connect(rootObj, SIGNAL(toolBarAction(int)), this, SLOT(handleToolBarAction(int)));
} else {
currentQmlBackend->setup(m_selectedTexture, currentStateName, qmlSpecificsUrl, this);
}
currentQmlBackend->widget()->installEventFilter(this);
currentQmlBackend->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
currentQmlBackend->contextObject()->setHasMaterialLibrary(materialLibraryNode().isValid());
currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
m_qmlBackEnd = currentQmlBackend;
if (m_hasTextureRoot)
m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture);
else
m_dynamicPropertiesModel->reset();
m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget());
}
void TextureEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value)
{
m_locked = true;
executeInTransaction("TextureEditorView:commitVariantValueToModel", [&] {
QmlObjectNode(m_selectedTexture).setVariantProperty(propertyName, value);
});
m_locked = false;
}
void TextureEditorView::commitAuxValueToModel(const PropertyName &propertyName, const QVariant &value)
{
m_locked = true;
PropertyName name = propertyName;
name.chop(5);
try {
if (value.isValid())
m_selectedTexture.setAuxiliaryData(AuxiliaryDataType::Document, name, value);
else
m_selectedTexture.removeAuxiliaryData(AuxiliaryDataType::Document, name);
}
catch (const Exception &e) {
e.showException();
}
m_locked = false;
}
void TextureEditorView::removePropertyFromModel(const PropertyName &propertyName)
{
m_locked = true;
executeInTransaction("MaterialEditorView:removePropertyFromModel", [&] {
QmlObjectNode(m_selectedTexture).removeProperty(propertyName);
});
m_locked = false;
}
bool TextureEditorView::noValidSelection() const
{
QTC_ASSERT(m_qmlBackEnd, return true);
return !QmlObjectNode::isValidQmlObjectNode(m_selectedTexture);
}
void TextureEditorView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
m_locked = true;
m_hasQuick3DImport = model->hasImport("QtQuick3D");
m_hasTextureRoot = rootModelNode().metaInfo().isQtQuick3DTexture();
if (m_hasTextureRoot) {
m_selectedTexture = rootModelNode();
} else if (m_hasQuick3DImport) {
// Creating the material library node on model attach causes errors as long as the type
// information is not complete yet, so we keep checking until type info is complete.
m_ensureMatLibTimer.start(500);
}
if (!m_setupCompleted) {
reloadQml();
m_setupCompleted = true;
}
resetView();
m_locked = false;
}
void TextureEditorView::modelAboutToBeDetached(Model *model)
{
AbstractView::modelAboutToBeDetached(model);
m_dynamicPropertiesModel->reset();
m_qmlBackEnd->textureEditorTransaction()->end();
m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false);
}
void TextureEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
{
if (noValidSelection())
return;
for (const AbstractProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (node.isRootNode())
m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported());
if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) {
setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name()));
}
dynamicPropertiesModel()->dispatchPropertyChanges(property);
}
}
void TextureEditorView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags /*propertyChange*/)
{
if (noValidSelection())
return;
for (const VariantProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) {
if (property.isDynamic())
m_dynamicPropertiesModel->variantPropertyChanged(property);
if (m_selectedTexture.property(property.name()).isBindingProperty())
setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name()));
else
setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name()));
}
dynamicPropertiesModel()->dispatchPropertyChanges(property);
}
}
void TextureEditorView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags /*propertyChange*/)
{
if (noValidSelection())
return;
for (const BindingProperty &property : propertyList) {
ModelNode node(property.parentModelNode());
if (property.isAliasExport())
m_qmlBackEnd->contextObject()->setHasAliasExport(QmlObjectNode(m_selectedTexture).isAliasExported());
if (node == m_selectedTexture || QmlObjectNode(m_selectedTexture).propertyChangeForCurrentState() == node) {
if (property.isDynamic())
m_dynamicPropertiesModel->bindingPropertyChanged(property);
if (QmlObjectNode(m_selectedTexture).modelNode().property(property.name()).isBindingProperty())
setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).instanceValue(property.name()));
else
setValue(m_selectedTexture, property.name(), QmlObjectNode(m_selectedTexture).modelValue(property.name()));
}
dynamicPropertiesModel()->dispatchPropertyChanges(property);
}
}
void TextureEditorView::auxiliaryDataChanged(const ModelNode &node,
AuxiliaryDataKeyView key,
const QVariant &)
{
if (noValidSelection() || !node.isSelected())
return;
m_qmlBackEnd->setValueforAuxiliaryProperties(m_selectedTexture, key);
}
void TextureEditorView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList)
{
for (const auto &property : propertyList) {
if (property.isBindingProperty())
m_dynamicPropertiesModel->bindingRemoved(property.toBindingProperty());
else if (property.isVariantProperty())
m_dynamicPropertiesModel->variantRemoved(property.toVariantProperty());
}
}
void TextureEditorView::nodeReparented(const ModelNode &node,
const NodeAbstractProperty &newPropertyParent,
const NodeAbstractProperty &oldPropertyParent,
PropertyChangeFlags propertyChange)
{
if (node.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject())
m_qmlBackEnd->contextObject()->setHasMaterialLibrary(true);
}
void TextureEditorView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
if (removedNode.id() == Constants::MATERIAL_LIB_ID && m_qmlBackEnd && m_qmlBackEnd->contextObject())
m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false);
}
bool TextureEditorView::hasWidget() const
{
return true;
}
WidgetInfo TextureEditorView::widgetInfo()
{
return createWidgetInfo(m_stackedWidget,
"TextureEditor",
WidgetInfo::RightPane,
0,
tr("Texture Editor"));
}
void TextureEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
[[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
{
m_selectedModel = {};
if (selectedNodeList.size() == 1 && selectedNodeList.at(0).metaInfo().isQtQuick3DModel())
m_selectedModel = selectedNodeList.at(0);
m_qmlBackEnd->contextObject()->setHasModelSelection(m_selectedModel.isValid());
}
void TextureEditorView::currentStateChanged(const ModelNode &node)
{
QmlModelState newQmlModelState(node);
Q_ASSERT(newQmlModelState.isValid());
resetView();
}
void TextureEditorView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName>> &propertyList)
{
if (!m_selectedTexture.isValid() || !m_qmlBackEnd)
return;
m_locked = true;
for (const QPair<ModelNode, PropertyName> &propertyPair : propertyList) {
const ModelNode modelNode = propertyPair.first;
const QmlObjectNode qmlObjectNode(modelNode);
const PropertyName propertyName = propertyPair.second;
if (qmlObjectNode.isValid() && modelNode == m_selectedTexture && qmlObjectNode.currentState().isValid()) {
const AbstractProperty property = modelNode.property(propertyName);
if (!modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty())
setValue(modelNode, property.name(), qmlObjectNode.instanceValue(property.name()));
else
setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name()));
}
}
m_locked = false;
}
void TextureEditorView::importsChanged([[maybe_unused]] const QList<Import> &addedImports,
[[maybe_unused]] const QList<Import> &removedImports)
{
m_hasQuick3DImport = model()->hasImport("QtQuick3D");
m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport);
if (m_hasQuick3DImport)
m_ensureMatLibTimer.start(500);
resetView();
}
void TextureEditorView::duplicateTexture(const ModelNode &texture)
{
QTC_ASSERT(texture.isValid(), return);
if (!model())
return;
TypeName matType = texture.type();
QmlObjectNode sourceTexture(texture);
ModelNode duplicateTextureNode;
QList<AbstractProperty> dynamicProps;
executeInTransaction(__FUNCTION__, [&] {
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid())
return;
// create the duplicate texture
NodeMetaInfo metaInfo = model()->metaInfo(matType);
QmlObjectNode duplicateTex = createModelNode(matType, metaInfo.majorVersion(), metaInfo.minorVersion());
duplicateTextureNode = duplicateTex .modelNode();
duplicateTextureNode.validId();
// sync properties. Only the base state is duplicated.
const QList<AbstractProperty> props = texture.properties();
for (const AbstractProperty &prop : props) {
if (prop.name() == "objectName" || prop.name() == "data")
continue;
if (prop.isVariantProperty()) {
if (prop.isDynamic()) {
dynamicProps.append(prop);
} else {
duplicateTextureNode.variantProperty(prop.name())
.setValue(prop.toVariantProperty().value());
}
} else if (prop.isBindingProperty()) {
if (prop.isDynamic()) {
dynamicProps.append(prop);
} else {
duplicateTextureNode.bindingProperty(prop.name())
.setExpression(prop.toBindingProperty().expression());
}
}
}
matLib.defaultNodeListProperty().reparentHere(duplicateTex);
});
// For some reason, creating dynamic properties in the same transaction doesn't work, so
// let's do it in separate transaction.
// TODO: Fix the issue and merge transactions (QDS-8094)
if (!dynamicProps.isEmpty()) {
executeInTransaction(__FUNCTION__, [&] {
for (const AbstractProperty &prop : std::as_const(dynamicProps)) {
if (prop.isVariantProperty()) {
duplicateTextureNode.variantProperty(prop.name())
.setDynamicTypeNameAndValue(prop.dynamicTypeName(),
prop.toVariantProperty().value());
} else if (prop.isBindingProperty()) {
duplicateTextureNode.bindingProperty(prop.name())
.setDynamicTypeNameAndExpression(prop.dynamicTypeName(),
prop.toBindingProperty().expression());
}
}
});
}
}
void TextureEditorView::customNotification([[maybe_unused]] const AbstractView *view,
const QString &identifier,
const QList<ModelNode> &nodeList,
const QList<QVariant> &data)
{
if (identifier == "selected_texture_changed") {
if (!m_hasTextureRoot) {
m_selectedTexture = nodeList.first();
m_dynamicPropertiesModel->setSelectedNode(m_selectedTexture);
QTimer::singleShot(0, this, &TextureEditorView::resetView);
}
} else if (identifier == "apply_texture_to_selected_triggered") {
applyTextureToSelectedModel(nodeList.first());
} else if (identifier == "add_new_texture") {
handleToolBarAction(TextureEditorContextObject::AddNewTexture);
} else if (identifier == "duplicate_texture") {
duplicateTexture(nodeList.first());
}
}
void QmlDesigner::TextureEditorView::highlightSupportedProperties(bool highlight)
{
if (!m_selectedTexture.isValid())
return;
DesignerPropertyMap &propMap = m_qmlBackEnd->backendValuesPropertyMap();
const QStringList propNames = propMap.keys();
NodeMetaInfo metaInfo = m_selectedTexture.metaInfo();
QTC_ASSERT(metaInfo.isValid(), return);
for (const QString &propName : propNames) {
if (metaInfo.property(propName.toUtf8()).propertyType().isQtQuick3DTexture()) { // TODO: support dropping to texture source
QObject *propEditorValObj = propMap.value(propName).value<QObject *>();
PropertyEditorValue *propEditorVal = qobject_cast<PropertyEditorValue *>(propEditorValObj);
propEditorVal->setHasActiveDrag(highlight);
}
}
}
void TextureEditorView::dragStarted(QMimeData *mimeData)
{
if (!mimeData->hasFormat(Constants::MIME_TYPE_ASSETS))
return;
const QString assetPath = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(',')[0];
QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPath).first;
if (assetType != Constants::MIME_TYPE_ASSET_IMAGE) // currently only image assets have dnd-supported properties
return;
highlightSupportedProperties();
}
void TextureEditorView::dragEnded()
{
highlightSupportedProperties(false);
}
// from model to texture editor
void TextureEditorView::setValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value)
{
m_locked = true;
m_qmlBackEnd->setValue(qmlObjectNode, name, value);
m_locked = false;
}
bool TextureEditorView::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::FocusOut) {
if (m_qmlBackEnd && m_qmlBackEnd->widget() == obj)
QMetaObject::invokeMethod(m_qmlBackEnd->widget()->rootObject(), "closeContextMenu");
}
return QObject::eventFilter(obj, event);
}
void TextureEditorView::reloadQml()
{
m_qmlBackendHash.clear();
while (QWidget *widget = m_stackedWidget->widget(0)) {
m_stackedWidget->removeWidget(widget);
delete widget;
}
m_qmlBackEnd = nullptr;
resetView();
}
} // namespace QmlDesigner