forked from qt-creator/qt-creator
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:
@@ -44,7 +44,6 @@ Rectangle {
|
|||||||
image.source = "image://nodeInstance/preview"
|
image.source = "image://nodeInstance/preview"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.backend
|
target: root.backend
|
||||||
|
|
||||||
|
@@ -15,13 +15,15 @@ StudioControls.SplitView {
|
|||||||
property Component previewComponent: null
|
property Component previewComponent: null
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
implicitHeight: showImage ? previewLoader.implicitHeight + nameSection.implicitHeight : nameSection.implicitHeight
|
implicitHeight: showImage ? previewLoader.activeHeight + nameSection.implicitHeight : nameSection.implicitHeight
|
||||||
|
|
||||||
orientation: Qt.Vertical
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: previewLoader
|
id: previewLoader
|
||||||
|
|
||||||
|
property real activeHeight: previewLoader.active ? implicitHeight : 0
|
||||||
|
|
||||||
SplitView.fillWidth: true
|
SplitView.fillWidth: true
|
||||||
SplitView.minimumWidth: 152
|
SplitView.minimumWidth: 152
|
||||||
SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0
|
SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0
|
||||||
@@ -58,6 +60,7 @@ StudioControls.SplitView {
|
|||||||
placeholderText: qsTr("Material name")
|
placeholderText: qsTr("Material name")
|
||||||
showTranslateCheckBox: false
|
showTranslateCheckBox: false
|
||||||
showExtendedFunctionButton: false
|
showExtendedFunctionButton: false
|
||||||
|
enabled: !hasMultiSelection
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
running: true
|
running: true
|
||||||
|
@@ -6,6 +6,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import HelperWidgets 2.0
|
import HelperWidgets 2.0
|
||||||
import StudioControls 1.0 as StudioControls
|
import StudioControls 1.0 as StudioControls
|
||||||
|
import StudioTheme 1.0 as StudioTheme
|
||||||
import "Material" as Material
|
import "Material" as Material
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -58,7 +59,7 @@ Item {
|
|||||||
active: splitView.isHorizontal
|
active: splitView.isHorizontal
|
||||||
visible: leftSideView.active && leftSideView.item
|
visible: leftSideView.active && leftSideView.item
|
||||||
|
|
||||||
sourceComponent: PreviewComponent {}
|
sourceComponent: hasMultiSelection ? blankPreview : preview
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyEditorPane {
|
PropertyEditorPane {
|
||||||
@@ -77,8 +78,8 @@ Item {
|
|||||||
|
|
||||||
Component.onCompleted: topSection.restoreState(settings.topSection)
|
Component.onCompleted: topSection.restoreState(settings.topSection)
|
||||||
Component.onDestruction: settings.topSection = topSection.saveState()
|
Component.onDestruction: settings.topSection = topSection.saveState()
|
||||||
previewComponent: PreviewComponent {}
|
previewComponent: preview
|
||||||
showImage: !splitView.isHorizontal
|
showImage: !hasMultiSelection && !splitView.isHorizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicPropertiesSection {
|
DynamicPropertiesSection {
|
||||||
@@ -119,7 +120,10 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component PreviewComponent : Material.Preview {
|
Component {
|
||||||
|
id: preview
|
||||||
|
|
||||||
|
Material.Preview {
|
||||||
id: previewItem
|
id: previewItem
|
||||||
|
|
||||||
pinned: settings.dockMode
|
pinned: settings.dockMode
|
||||||
@@ -135,3 +139,12 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: blankPreview
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: StudioTheme.Values.themePanelBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -191,18 +191,15 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
|
|||||||
|
|
||||||
QTC_ASSERT(!rewriterView->selectedModelNodes().isEmpty(), return);
|
QTC_ASSERT(!rewriterView->selectedModelNodes().isEmpty(), return);
|
||||||
|
|
||||||
try {
|
auto changeNodeTypeName = [&](ModelNode &selectedNode) {
|
||||||
auto transaction = RewriterTransaction(rewriterView, "PropertyEditorContextObject:changeTypeName");
|
|
||||||
|
|
||||||
ModelNode selectedNode = rewriterView->selectedModelNodes().constFirst();
|
|
||||||
|
|
||||||
// Check if the requested type is the same as already set
|
// Check if the requested type is the same as already set
|
||||||
if (selectedNode.simplifiedTypeName() == typeName)
|
if (selectedNode.simplifiedTypeName() == typeName)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
NodeMetaInfo metaInfo = m_model->metaInfo(typeName.toLatin1());
|
NodeMetaInfo metaInfo = m_model->metaInfo(typeName.toLatin1());
|
||||||
if (!metaInfo.isValid()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +207,9 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
|
|||||||
auto propertiesAndSignals = Utils::transform<PropertyNameList>(
|
auto propertiesAndSignals = Utils::transform<PropertyNameList>(
|
||||||
PropertyEditorUtils::filteredProperties(metaInfo), &PropertyMetaInfo::name);
|
PropertyEditorUtils::filteredProperties(metaInfo), &PropertyMetaInfo::name);
|
||||||
// Add signals to the list
|
// Add signals to the list
|
||||||
for (const auto &signal : metaInfo.signalNames()) {
|
|
||||||
|
const PropertyNameList &signalNames = metaInfo.signalNames();
|
||||||
|
for (const PropertyName &signal : signalNames) {
|
||||||
if (signal.isEmpty())
|
if (signal.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -222,7 +221,8 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add dynamic properties and respective change signals
|
// 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())
|
if (!property.isDynamic())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
|
|||||||
|
|
||||||
// Compare current properties and signals with the once available for change type
|
// Compare current properties and signals with the once available for change type
|
||||||
QList<PropertyName> incompatibleProperties;
|
QList<PropertyName> incompatibleProperties;
|
||||||
for (const auto &property : selectedNode.properties()) {
|
for (const AbstractProperty &property : nodeProperties) {
|
||||||
if (!propertiesAndSignals.contains(property.name()))
|
if (!propertiesAndSignals.contains(property.name()))
|
||||||
incompatibleProperties.append(property.name().toByteArray());
|
incompatibleProperties.append(property.name().toByteArray());
|
||||||
}
|
}
|
||||||
@@ -259,11 +259,11 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
|
|||||||
msgBox.setTextFormat(Qt::RichText);
|
msgBox.setTextFormat(Qt::RichText);
|
||||||
msgBox.setIcon(QMessageBox::Question);
|
msgBox.setIcon(QMessageBox::Question);
|
||||||
msgBox.setWindowTitle("Change Type");
|
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")
|
msgBox.setText(QString("Changing the type from %1 to %2 can't be done without removing "
|
||||||
.arg(selectedNode.simplifiedTypeName())
|
"incompatible properties.<br><br>%3")
|
||||||
.arg(typeName)
|
.arg(selectedNode.simplifiedTypeName(), typeName, detailedText));
|
||||||
.arg(detailedText));
|
msgBox.setInformativeText(
|
||||||
msgBox.setInformativeText("Do you want to continue by removing incompatible properties?");
|
"Do you want to continue by removing incompatible properties?");
|
||||||
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||||
msgBox.setDefaultButton(QMessageBox::Ok);
|
msgBox.setDefaultButton(QMessageBox::Ok);
|
||||||
|
|
||||||
@@ -281,10 +281,23 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
|
|||||||
selectedNode.changeType(typeName.toUtf8(), -1, -1);
|
selectedNode.changeType(typeName.toUtf8(), -1, -1);
|
||||||
#else
|
#else
|
||||||
if (selectedNode.isRootNode())
|
if (selectedNode.isRootNode())
|
||||||
rewriterView->changeRootNodeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
|
rewriterView->changeRootNodeType(metaInfo.typeName(),
|
||||||
|
metaInfo.majorVersion(),
|
||||||
|
metaInfo.minorVersion());
|
||||||
else
|
else
|
||||||
selectedNode.changeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion());
|
selectedNode.changeType(metaInfo.typeName(),
|
||||||
|
metaInfo.majorVersion(),
|
||||||
|
metaInfo.minorVersion());
|
||||||
#endif
|
#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();
|
transaction.commit();
|
||||||
} catch (const Exception &e) {
|
} catch (const Exception &e) {
|
||||||
e.showException();
|
e.showException();
|
||||||
|
@@ -535,6 +535,31 @@ void QmlDesigner::PropertyEditorQmlBackend::createPropertyEditorValues(const Qml
|
|||||||
#endif
|
#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()
|
void PropertyEditorQmlBackend::updateInstanceImage()
|
||||||
{
|
{
|
||||||
m_view->instanceImageProvider()->invalidate();
|
m_view->instanceImageProvider()->invalidate();
|
||||||
@@ -564,29 +589,12 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
|
|||||||
m_backendMaterialNode.setup(qmlObjectNode);
|
m_backendMaterialNode.setup(qmlObjectNode);
|
||||||
m_backendTextureNode.setup(qmlObjectNode);
|
m_backendTextureNode.setup(qmlObjectNode);
|
||||||
|
|
||||||
// className
|
insertValue(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY,
|
||||||
auto valueObject = qobject_cast<PropertyEditorValue *>(variantToQObject(
|
m_backendModelNode.simplifiedTypeName(),
|
||||||
m_backendValuesPropertyMap.value(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY)));
|
qmlObjectNode.modelNode());
|
||||||
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));
|
|
||||||
|
|
||||||
// id
|
insertValue("id"_L1, m_backendModelNode.nodeId());
|
||||||
valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(QLatin1String("id"))));
|
insertValue("objectName"_L1, m_backendModelNode.nodeObjectName());
|
||||||
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));
|
|
||||||
|
|
||||||
QmlItemNode itemNode(qmlObjectNode.modelNode());
|
QmlItemNode itemNode(qmlObjectNode.modelNode());
|
||||||
|
|
||||||
|
@@ -113,6 +113,10 @@ private:
|
|||||||
const NodeMetaInfo &type);
|
const NodeMetaInfo &type);
|
||||||
void createPropertyEditorValues(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor);
|
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 QUrl fileToUrl(const QString &filePath);
|
||||||
static QString fileFromUrl(const QUrl &url);
|
static QString fileFromUrl(const QUrl &url);
|
||||||
#ifndef QDS_USE_PROJECTSTORAGE
|
#ifndef QDS_USE_PROJECTSTORAGE
|
||||||
|
@@ -135,6 +135,18 @@ void PropertyEditorView::changeValue(const QString &name)
|
|||||||
return;
|
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);
|
PropertyName underscoreName(propertyName);
|
||||||
underscoreName.replace('.', '_');
|
underscoreName.replace('.', '_');
|
||||||
PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName));
|
PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName));
|
||||||
|
@@ -16,6 +16,19 @@ namespace QmlDesigner {
|
|||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
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()
|
QmlMaterialNodeProxy::QmlMaterialNodeProxy()
|
||||||
: QObject()
|
: QObject()
|
||||||
, m_previewUpdateTimer(this)
|
, m_previewUpdateTimer(this)
|
||||||
@@ -62,9 +75,27 @@ void QmlMaterialNodeProxy::updatePossibleTypes()
|
|||||||
"SpecularGlossyMaterial",
|
"SpecularGlossyMaterial",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!materialNode()) {
|
||||||
|
setPossibleTypes({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const QString &matType = materialNode().simplifiedTypeName();
|
const QString &matType = materialNode().simplifiedTypeName();
|
||||||
setPossibleTypes(basicTypes.contains(matType) ? basicTypes : QStringList{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);
|
setCurrentType(matType);
|
||||||
|
} else if (allMaterialTypesAre(selectedNodes, matType)) {
|
||||||
|
setPossibleTypes(QStringList{matType});
|
||||||
|
setCurrentType(matType);
|
||||||
|
} else {
|
||||||
|
setPossibleTypes(QStringList{"multiselection"});
|
||||||
|
setCurrentType("multiselection");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlMaterialNodeProxy::setCurrentType(const QString &type)
|
void QmlMaterialNodeProxy::setCurrentType(const QString &type)
|
||||||
|
@@ -80,6 +80,17 @@ QString QmlModelNodeProxy::nodeId() const
|
|||||||
return m_qmlObjectNode.id();
|
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
|
QString QmlModelNodeProxy::simplifiedTypeName() const
|
||||||
{
|
{
|
||||||
if (!m_qmlObjectNode.isValid())
|
if (!m_qmlObjectNode.isValid())
|
||||||
|
@@ -38,6 +38,8 @@ public:
|
|||||||
|
|
||||||
QString nodeId() const;
|
QString nodeId() const;
|
||||||
|
|
||||||
|
QString nodeObjectName() const;
|
||||||
|
|
||||||
QString simplifiedTypeName() const;
|
QString simplifiedTypeName() const;
|
||||||
|
|
||||||
Q_INVOKABLE QList<int> allChildren(int internalId = -1) const;
|
Q_INVOKABLE QList<int> allChildren(int internalId = -1) const;
|
||||||
|
Reference in New Issue
Block a user