QmlDesigner: Handle multiselection for Materials and textures

* Material Preview is removed for multiselected nodes
* Name field shows `multiselection`
* Showing material type in multiselection is handled
* Setting a type for multiselected materials is handled
* Renaming a material/texture changes the id as well

Task-number: QDS-14895
Change-Id: I61b6ebf8bf320f5a9e27e318f66d6823d9e91272
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Ali Kianian
2025-03-11 16:46:22 +02:00
parent fe2f389c38
commit c5f2f7984c
10 changed files with 150 additions and 54 deletions

View File

@@ -44,7 +44,6 @@ Rectangle {
image.source = "image://nodeInstance/preview"
}
Connections {
target: root.backend

View File

@@ -15,13 +15,15 @@ StudioControls.SplitView {
property Component previewComponent: null
width: parent.width
implicitHeight: showImage ? previewLoader.implicitHeight + nameSection.implicitHeight : nameSection.implicitHeight
implicitHeight: showImage ? previewLoader.activeHeight + nameSection.implicitHeight : nameSection.implicitHeight
orientation: Qt.Vertical
Loader {
id: previewLoader
property real activeHeight: previewLoader.active ? implicitHeight : 0
SplitView.fillWidth: true
SplitView.minimumWidth: 152
SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0
@@ -58,6 +60,7 @@ StudioControls.SplitView {
placeholderText: qsTr("Material name")
showTranslateCheckBox: false
showExtendedFunctionButton: false
enabled: !hasMultiSelection
Timer {
running: true

View File

@@ -6,6 +6,7 @@ import QtQuick
import QtQuick.Controls
import HelperWidgets 2.0
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
import "Material" as Material
Item {
@@ -58,7 +59,7 @@ Item {
active: splitView.isHorizontal
visible: leftSideView.active && leftSideView.item
sourceComponent: PreviewComponent {}
sourceComponent: hasMultiSelection ? blankPreview : preview
}
PropertyEditorPane {
@@ -77,8 +78,8 @@ Item {
Component.onCompleted: topSection.restoreState(settings.topSection)
Component.onDestruction: settings.topSection = topSection.saveState()
previewComponent: PreviewComponent {}
showImage: !splitView.isHorizontal
previewComponent: preview
showImage: !hasMultiSelection && !splitView.isHorizontal
}
DynamicPropertiesSection {
@@ -119,19 +120,31 @@ Item {
}
}
component PreviewComponent : Material.Preview {
id: previewItem
Component {
id: preview
pinned: settings.dockMode
showPinButton: !leftSideView.visible
onPinnedChanged: settings.dockMode = previewItem.pinned
Material.Preview {
id: previewItem
Connections {
target: root
pinned: settings.dockMode
showPinButton: !leftSideView.visible
onPinnedChanged: settings.dockMode = previewItem.pinned
function onRefreshPreview() {
previewItem.refreshPreview()
Connections {
target: root
function onRefreshPreview() {
previewItem.refreshPreview()
}
}
}
}
Component {
id: blankPreview
Rectangle {
color: StudioTheme.Values.themePanelBackground
}
}
}

View File

@@ -191,18 +191,15 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
QTC_ASSERT(!rewriterView->selectedModelNodes().isEmpty(), return);
try {
auto transaction = RewriterTransaction(rewriterView, "PropertyEditorContextObject:changeTypeName");
ModelNode selectedNode = rewriterView->selectedModelNodes().constFirst();
auto changeNodeTypeName = [&](ModelNode &selectedNode) {
// Check if the requested type is the same as already set
if (selectedNode.simplifiedTypeName() == typeName)
return;
NodeMetaInfo metaInfo = m_model->metaInfo(typeName.toLatin1());
if (!metaInfo.isValid()) {
Core::AsynchronousMessageBox::warning(tr("Invalid Type"), tr("%1 is an invalid type.").arg(typeName));
Core::AsynchronousMessageBox::warning(tr("Invalid Type"),
tr("%1 is an invalid type.").arg(typeName));
return;
}
@@ -210,7 +207,9 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
auto propertiesAndSignals = Utils::transform<PropertyNameList>(
PropertyEditorUtils::filteredProperties(metaInfo), &PropertyMetaInfo::name);
// Add signals to the list
for (const auto &signal : metaInfo.signalNames()) {
const PropertyNameList &signalNames = metaInfo.signalNames();
for (const PropertyName &signal : signalNames) {
if (signal.isEmpty())
continue;
@@ -222,7 +221,8 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
}
// Add dynamic properties and respective change signals
for (const auto &property : selectedNode.properties()) {
const QList<AbstractProperty> &nodeProperties = selectedNode.properties();
for (const AbstractProperty &property : nodeProperties) {
if (!property.isDynamic())
continue;
@@ -239,7 +239,7 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
// Compare current properties and signals with the once available for change type
QList<PropertyName> incompatibleProperties;
for (const auto &property : selectedNode.properties()) {
for (const AbstractProperty &property : nodeProperties) {
if (!propertiesAndSignals.contains(property.name()))
incompatibleProperties.append(property.name().toByteArray());
}
@@ -259,11 +259,11 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
msgBox.setTextFormat(Qt::RichText);
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle("Change Type");
msgBox.setText(QString("Changing the type from %1 to %2 can't be done without removing incompatible properties.<br><br>%3")
.arg(selectedNode.simplifiedTypeName())
.arg(typeName)
.arg(detailedText));
msgBox.setInformativeText("Do you want to continue by removing incompatible properties?");
msgBox.setText(QString("Changing the type from %1 to %2 can't be done without removing "
"incompatible properties.<br><br>%3")
.arg(selectedNode.simplifiedTypeName(), typeName, detailedText));
msgBox.setInformativeText(
"Do you want to continue by removing incompatible properties?");
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
@@ -281,10 +281,23 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
selectedNode.changeType(typeName.toUtf8(), -1, -1);
#else
if (selectedNode.isRootNode())
rewriterView->changeRootNodeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
rewriterView->changeRootNodeType(metaInfo.typeName(),
metaInfo.majorVersion(),
metaInfo.minorVersion());
else
selectedNode.changeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
selectedNode.changeType(metaInfo.typeName(),
metaInfo.majorVersion(),
metaInfo.minorVersion());
#endif
};
try {
auto transaction = RewriterTransaction(rewriterView, "PropertyEditorContextObject:changeTypeName");
ModelNodes selectedNodes = rewriterView->selectedModelNodes(); // TODO: replace it by PropertyEditorView::currentNodes()
for (ModelNode &selectedNode : selectedNodes)
changeNodeTypeName(selectedNode);
transaction.commit();
} catch (const Exception &e) {
e.showException();

View File

@@ -535,6 +535,31 @@ void QmlDesigner::PropertyEditorQmlBackend::createPropertyEditorValues(const Qml
#endif
}
PropertyEditorValue *PropertyEditorQmlBackend::insertValue(const QString &name,
const QVariant &value,
const ModelNode &modelNode)
{
auto valueObject = qobject_cast<PropertyEditorValue *>(
variantToQObject(m_backendValuesPropertyMap.value(name)));
if (!valueObject)
valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
valueObject->setName(name.toLatin1());
if (modelNode)
valueObject->setModelNode(modelNode);
if (value.isValid())
valueObject->setValue(value);
QObject::connect(valueObject,
&PropertyEditorValue::valueChanged,
&backendValuesPropertyMap(),
&DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(name, QVariant::fromValue(valueObject));
return valueObject;
}
void PropertyEditorQmlBackend::updateInstanceImage()
{
m_view->instanceImageProvider()->invalidate();
@@ -564,29 +589,12 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
m_backendMaterialNode.setup(qmlObjectNode);
m_backendTextureNode.setup(qmlObjectNode);
// className
auto valueObject = qobject_cast<PropertyEditorValue *>(variantToQObject(
m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY)));
if (!valueObject)
valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
valueObject->setName(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY);
valueObject->setModelNode(qmlObjectNode.modelNode());
valueObject->setValue(m_backendModelNode.simplifiedTypeName());
QObject::connect(valueObject,
&PropertyEditorValue::valueChanged,
&backendValuesPropertyMap(),
&DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY,
QVariant::fromValue(valueObject));
insertValue(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY,
m_backendModelNode.simplifiedTypeName(),
qmlObjectNode.modelNode());
// id
valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(QLatin1String("id"))));
if (!valueObject)
valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap);
valueObject->setName("id");
valueObject->setValue(m_backendModelNode.nodeId());
QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged);
m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject));
insertValue("id"_L1, m_backendModelNode.nodeId());
insertValue("objectName"_L1, m_backendModelNode.nodeObjectName());
QmlItemNode itemNode(qmlObjectNode.modelNode());

View File

@@ -113,6 +113,10 @@ private:
const NodeMetaInfo &type);
void createPropertyEditorValues(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor);
PropertyEditorValue *insertValue(const QString &name,
const QVariant &value = {},
const ModelNode &modelNode = {});
static QUrl fileToUrl(const QString &filePath);
static QString fileFromUrl(const QUrl &url);
#ifndef QDS_USE_PROJECTSTORAGE

View File

@@ -135,6 +135,18 @@ void PropertyEditorView::changeValue(const QString &name)
return;
}
if (propertyName == "objectName" && currentNodes().size() == 1) {
if (activeNode().metaInfo().isQtQuick3DMaterial()
|| activeNode().metaInfo().isQtQuick3DTexture()) {
PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(
"objectName");
const QString &newObjectName = value->value().toString();
QmlObjectNode objectNode(activeNode());
objectNode.setNameAndId(newObjectName, QString::fromLatin1(activeNode().type()));
return;
}
}
PropertyName underscoreName(propertyName);
underscoreName.replace('.', '_');
PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName));

View File

@@ -16,6 +16,19 @@ namespace QmlDesigner {
using namespace Qt::StringLiterals;
static bool allMaterialTypesAre(const ModelNodes &materials, const QString &materialType)
{
if (materials.isEmpty())
return false;
for (const ModelNode &material : materials) {
if (material.simplifiedTypeName() != materialType)
return false;
}
return true;
}
QmlMaterialNodeProxy::QmlMaterialNodeProxy()
: QObject()
, m_previewUpdateTimer(this)
@@ -62,9 +75,27 @@ void QmlMaterialNodeProxy::updatePossibleTypes()
"SpecularGlossyMaterial",
};
if (!materialNode()) {
setPossibleTypes({});
return;
}
const QString &matType = materialNode().simplifiedTypeName();
setPossibleTypes(basicTypes.contains(matType) ? basicTypes : QStringList{matType});
setCurrentType(matType);
const ModelNodes selectedNodes = materialView()->selectedModelNodes(); // TODO: replace it by PropertyEditorView::currentNodes()
bool allAreBasic = Utils::allOf(selectedNodes, [&](const ModelNode &node) {
return basicTypes.contains(node.simplifiedTypeName());
});
if (allAreBasic) {
setPossibleTypes(basicTypes);
setCurrentType(matType);
} else if (allMaterialTypesAre(selectedNodes, matType)) {
setPossibleTypes(QStringList{matType});
setCurrentType(matType);
} else {
setPossibleTypes(QStringList{"multiselection"});
setCurrentType("multiselection");
}
}
void QmlMaterialNodeProxy::setCurrentType(const QString &type)

View File

@@ -80,6 +80,17 @@ QString QmlModelNodeProxy::nodeId() const
return m_qmlObjectNode.id();
}
QString QmlModelNodeProxy::nodeObjectName() const
{
if (!m_qmlObjectNode.isValid())
return {};
if (multiSelection())
return tr("multiselection");
return m_qmlObjectNode.modelNode().variantProperty("objectName").value().toString();
}
QString QmlModelNodeProxy::simplifiedTypeName() const
{
if (!m_qmlObjectNode.isValid())

View File

@@ -38,6 +38,8 @@ public:
QString nodeId() const;
QString nodeObjectName() const;
QString simplifiedTypeName() const;
Q_INVOKABLE QList<int> allChildren(int internalId = -1) const;