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"
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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());
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
|
@@ -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)
|
||||
|
@@ -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())
|
||||
|
@@ -38,6 +38,8 @@ public:
|
||||
|
||||
QString nodeId() const;
|
||||
|
||||
QString nodeObjectName() const;
|
||||
|
||||
QString simplifiedTypeName() const;
|
||||
|
||||
Q_INVOKABLE QList<int> allChildren(int internalId = -1) const;
|
||||
|
Reference in New Issue
Block a user