From 2dda34f781048f67637df68d574573018bf94224 Mon Sep 17 00:00:00 2001 From: Knud Dollereder Date: Fri, 8 Sep 2023 15:49:58 +0200 Subject: [PATCH] Extend bindingmodel and minor fixes - Include singletons in the list of sources (former sourceModelNodes) - Do not suggest sources on bindings that are initialized without a source node. Prevent constructs like "property string p: button.qsTr("STREXPR3")". - Allow changing the name of the target property and update the delegate properly when doing so. - Remove superfluous emitCurrentIndex emission from the dynamicpropertiesmodel. - Add missing qml imports. Note that it is still possible to create illegal constructs if the user wants to. Change-Id: I084978f2f8d451df076d1b556db15764e17e31fb Reviewed-by: Thomas Hartmann --- .../connectionseditor/BindingsDialog.qml | 3 +- .../connectionseditor/BindingsDialogForm.qml | 14 +- .../connectionseditor/ConnectionsDialog.qml | 3 +- .../connectionseditor/PopupLabel.qml | 2 +- .../connectionseditor/PropertiesDialog.qml | 3 +- .../connectioneditor/bindingmodel.cpp | 85 ++++++++-- .../connectioneditor/bindingmodel.h | 2 + .../connectioneditorutils.cpp | 155 ++++++++++-------- .../connectioneditor/connectioneditorutils.h | 2 +- .../dynamicpropertiesmodel.cpp | 1 - 10 files changed, 172 insertions(+), 98 deletions(-) diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml index 65321b3b6e4..8830e298df3 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialog.qml @@ -3,6 +3,7 @@ import QtQuick import StudioTheme 1.0 as StudioTheme +import HelperWidgets as HelperWidgets PopupDialog { property alias backend: form.backend @@ -15,7 +16,7 @@ PopupDialog { text: qsTr("Owner") font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter - ToolTipArea { + HelperWidgets.ToolTipArea { anchors.fill: parent tooltip: qsTr("The owner of the property") } diff --git a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml index 912bbad8287..157d0d35177 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/BindingsDialogForm.qml @@ -64,10 +64,16 @@ Column { onCurrentTypeIndexChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex } - PopupLabel { - width: root.columnWidth - text: backend.property.currentText - } + StudioControls.TopLevelComboBox { + id: name + width: root.columnWidth + style: StudioTheme.Values.connectionPopupControlStyle + model: backend.property.model ?? [] + + onActivated: backend.property.activateIndex(name.currentIndex) + property int currentTypeIndex: backend.property.currentIndex ?? 0 + onCurrentTypeIndexChanged: name.currentIndex = name.currentTypeIndex + } } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index 210fcc81d3a..b880916e4d8 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -4,6 +4,7 @@ import QtQuick import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme +import HelperWidgets 2.0 as HelperWidgets PopupDialog { @@ -18,7 +19,7 @@ PopupDialog { text: qsTr("Target") font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter - ToolTipArea { + HelperWidgets.ToolTipArea { anchors.fill: parent tooltip: qsTr("Choose the target for the signal.") } diff --git a/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml b/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml index 1af0950be4f..6e5a117d8ce 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PopupLabel.qml @@ -12,7 +12,7 @@ Text { color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.myFontSize property alias tooltip: area.tooltip - ToolTipArea { + HelperWidgets.ToolTipArea { id: area anchors.fill: parent tooltip: qsTr("missing") diff --git a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml index d649d6380ef..20e9568c14b 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/PropertiesDialog.qml @@ -3,6 +3,7 @@ import QtQuick import StudioTheme 1.0 as StudioTheme +import HelperWidgets as HelperWidgets PopupDialog { property alias backend: form.backend @@ -16,7 +17,7 @@ PopupDialog { text: qsTr("Owner") font.pixelSize: StudioTheme.Values.myFontSize anchors.verticalCenter: parent.verticalCenter - ToolTipArea { + HelperWidgets.ToolTipArea { anchors.fill: parent tooltip: qsTr("The owner of the property") } diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index 69f6b5cc127..0b38b9d35d9 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -5,6 +5,7 @@ #include "bindingmodelitem.h" #include "connectionview.h" #include "connectioneditorutils.h" +#include "modelfwd.h" #include #include @@ -145,11 +146,12 @@ void BindingModel::updateItem(const BindingProperty &property) item->updateProperty(property); else appendRow(new BindingModelItem(property)); + + m_delegate->update(currentProperty(), m_connectionView); } void BindingModel::removeItem(const AbstractProperty &property) { - AbstractProperty current = currentProperty(); if (auto index = rowForProperty(property)) static_cast(removeRow(*index)); @@ -167,7 +169,33 @@ void BindingModel::commitExpression(int row, const QString &expression) return; connectionView()->executeInTransaction(__FUNCTION__, [&bindingProperty, expression]() { - bindingProperty.setExpression(expression.trimmed()); + if (bindingProperty.isDynamic()) { + TypeName type = bindingProperty.dynamicTypeName(); + bindingProperty.setDynamicTypeNameAndExpression(type, expression); + } else { + bindingProperty.setExpression(expression.trimmed()); + } + }); +} + +void BindingModel::commitPropertyName(int row, const PropertyName &name) +{ + QTC_ASSERT(connectionView(), return); + + BindingProperty bindingProperty = propertyForRow(row); + if (!bindingProperty.isValid()) + return; + + connectionView()->executeInTransaction(__FUNCTION__, [&]() { + const TypeName type = bindingProperty.dynamicTypeName(); + const QString expression = bindingProperty.expression(); + + ModelNode node = bindingProperty.parentModelNode(); + node.removeProperty(bindingProperty.name()); + if (bindingProperty.isDynamic()) + node.bindingProperty(name).setDynamicTypeNameAndExpression(type, expression); + else + node.bindingProperty(name).setExpression(expression); }); } @@ -228,6 +256,10 @@ BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent) connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() { expressionChanged(); }); + + connect(&m_property, &StudioQmlComboBoxBackend::activated, this, [this]() { + propertyNameChanged(); + }); } void BindingModelBackendDelegate::update(const BindingProperty &property, AbstractView *view) @@ -243,17 +275,20 @@ void BindingModelBackendDelegate::update(const BindingProperty &property, Abstra auto [sourceNodeName, sourcePropertyName] = splitExpression(property.expression()); - QString targetName = QString::fromUtf8(property.name()); - m_targetNode = idOrTypeName(property.parentModelNode()); + QStringList sourceNodes = {}; + if (!sourceNodeName.isEmpty()) + sourceNodes = addName(availableSources(view), sourceNodeName); - auto modelNodes = addName(availableModelNodes(view), sourceNodeName); - m_sourceNode.setModel(modelNodes); + m_sourceNode.setModel(sourceNodes); m_sourceNode.setCurrentText(sourceNodeName); auto sourceproperties = addName(availableSourceProperties(property, view), sourcePropertyName); m_sourceNodeProperty.setModel(sourceproperties); m_sourceNodeProperty.setCurrentText(sourcePropertyName); + QString targetName = QString::fromUtf8(property.name()); + m_targetNode = idOrTypeName(property.parentModelNode()); + auto targetProperties = addName(availableTargetProperties(property), targetName); m_property.setModel(targetProperties); m_property.setCurrentText(targetName); @@ -283,20 +318,36 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty() void BindingModelBackendDelegate::expressionChanged() const { - BindingModel *model = qobject_cast(parent()); - QTC_ASSERT(model, return); + auto commit = [this]() { + BindingModel *model = qobject_cast(parent()); + QTC_ASSERT(model, return); - const QString sourceNode = m_sourceNode.currentText(); - const QString sourceProperty = m_sourceNodeProperty.currentText(); + const QString sourceNode = m_sourceNode.currentText(); + const QString sourceProperty = m_sourceNodeProperty.currentText(); + QString expression; + if (sourceProperty.isEmpty()) + expression = sourceNode; + else + expression = sourceNode + QLatin1String(".") + sourceProperty; - QString expression; - if (sourceProperty.isEmpty()) - expression = sourceNode; - else - expression = sourceNode + QLatin1String(".") + sourceProperty; + int row = model->currentIndex(); + model->commitExpression(row, expression); + }; - int row = model->currentIndex(); - model->commitExpression(row, expression); + callLater(commit); +} + +void BindingModelBackendDelegate::propertyNameChanged() const +{ + auto commit = [this]() { + BindingModel *model = qobject_cast(parent()); + QTC_ASSERT(model, return); + const PropertyName propertyName = m_property.currentText().toUtf8(); + int row = model->currentIndex(); + model->commitPropertyName(row, propertyName); + }; + + callLater(commit); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h index e167f2b5afb..4c3dd1210e5 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h @@ -49,6 +49,7 @@ public: void removeItem(const AbstractProperty &property); void commitExpression(int row, const QString &expression); + void commitPropertyName(int row, const PropertyName &name); protected: QHash roleNames() const override; @@ -86,6 +87,7 @@ public: private: QString targetNode() const; void expressionChanged() const; + void propertyNameChanged() const; StudioQmlComboBoxBackend *property(); StudioQmlComboBoxBackend *sourceNode(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp index c4ce5c23fe4..d43ac648a6f 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -82,6 +83,38 @@ NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property) return { }; } +bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &sourceType, const NodeMetaInfo &targetType) +{ + if (sourceType.isVariant()) + return true; + + if (sourceType.isBool() && targetType.isBool()) + return true; + + if (sourceType == targetType) + return true; + + if (sourceType.isNumber() && targetType.isNumber()) + return true; + + if (sourceType.isString() && targetType.isString()) + return true; + + if (sourceType.isUrl() && targetType.isUrl()) + return true; + + if (sourceType.isColor() && targetType.isColor()) + return true; + + return false; +} + +bool metaInfoIsCompatible(const NodeMetaInfo &sourceType, const PropertyMetaInfo &metaInfo) +{ + NodeMetaInfo targetType = metaInfo.propertyType(); + return metaInfoIsCompatibleUnsafe(sourceType, targetType); +} + QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName) { QVariant returnValue = variant; @@ -217,7 +250,21 @@ QString defaultExpressionForType(const TypeName &type) return expression; } -QStringList availableModelNodes(AbstractView *view) +QStringList singletonsFromView(AbstractView *view) +{ + RewriterView *rv = view->rewriterView(); + if (!rv) + return { }; + + QStringList out; + for (const QmlTypeData &data : rv->getQMLTypes()) { + if (data.isSingleton && !data.typeName.isEmpty()) + out.push_back(data.typeName); + } + return out; +} + +QStringList availableSources(AbstractView *view) { QStringList sourceNodes; for (const ModelNode &modelNode : view->allModelNodes()) { @@ -225,22 +272,7 @@ QStringList availableModelNodes(AbstractView *view) sourceNodes.append(modelNode.id()); } std::sort(sourceNodes.begin(), sourceNodes.end()); - return sourceNodes; -} - -QStringList dynamicPropertyNamesFromNode(const ModelNode& node) -{ - QStringList dynamicProperties; - for (const VariantProperty &variantProperty : node.variantProperties()) { - if (variantProperty.isDynamic()) - dynamicProperties << QString::fromUtf8(variantProperty.name()); - } - - for (const BindingProperty &bindingProperty : node.bindingProperties()) { - if (bindingProperty.isDynamic()) - dynamicProperties << QString::fromUtf8((bindingProperty.name())); - } - return dynamicProperties; + return singletonsFromView(view) + sourceNodes; } QStringList availableTargetProperties(const BindingProperty &bindingProperty) @@ -261,10 +293,9 @@ QStringList availableTargetProperties(const BindingProperty &bindingProperty) writableProperties.push_back(QString::fromUtf8(property.name())); } - return dynamicPropertyNamesFromNode(modelNode) + writableProperties; + return writableProperties; } - - return dynamicPropertyNamesFromNode(modelNode); + return { }; } ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const ModelNode &targetNode) @@ -278,64 +309,13 @@ ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const Model return {}; } -bool metaInfoIsCompatible(const NodeMetaInfo& sourceType, const PropertyMetaInfo& metaInfo) -{ - if (sourceType.isVariant()) - return true; - - NodeMetaInfo targetType = metaInfo.propertyType(); - if (sourceType.isBool() && targetType.isBool()) - return true; - - if (sourceType == targetType) - return true; - - if (sourceType.isNumber() && targetType.isNumber()) - return true; - - if (sourceType.isString() && targetType.isString()) - return true; - - if (sourceType.isUrl() && targetType.isUrl()) - return true; - - if (sourceType.isColor() && targetType.isColor()) - return true; - - return false; -} - QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view) { const QString expression = bindingProperty.expression(); const QStringList stringlist = expression.split(QLatin1String(".")); - QStringList possibleProperties; const QString &id = stringlist.constFirst(); ModelNode modelNode = getNodeByIdOrParent(view, id, bindingProperty.parentModelNode()); - if (!modelNode.isValid()) { - //if it's not a valid model node, maybe it's a singleton - if (RewriterView *rv = view->rewriterView()) { - for (const QmlTypeData &data : rv->getQMLTypes()) { - if (!data.typeName.isEmpty() && data.typeName == id) { - NodeMetaInfo metaInfo = view->model()->metaInfo(data.typeName.toUtf8()); - - if (metaInfo.isValid()) { - for (const auto &property : metaInfo.properties()) { - //without check for now - possibleProperties.push_back(QString::fromUtf8(property.name())); - } - - return possibleProperties; - } - } - } - } - qWarning() << __FUNCTION__ << " invalid model node"; - return QStringList(); - } - - possibleProperties = possibleProperties + dynamicPropertyNamesFromNode(modelNode); NodeMetaInfo type; if (bindingProperty.isDynamic()) { @@ -345,6 +325,39 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab } else qWarning() << __FUNCTION__ << " no meta info for target node"; + QStringList possibleProperties; + if (!modelNode.isValid()) { + QStringList singletons = singletonsFromView(view); + if (singletons.contains(id)) { + Model *model = view->model(); + QTC_ASSERT(model, return {}); + if (NodeMetaInfo metaInfo = model->metaInfo(id.toUtf8()); metaInfo.isValid()) { + for (const auto &property : metaInfo.properties()) { + if (metaInfoIsCompatible(type, property)) + possibleProperties.push_back(QString::fromUtf8(property.name())); + } + } + return possibleProperties; + } + qWarning() << __FUNCTION__ << " invalid model node"; + return {}; + } + + auto isCompatible = [type](const AbstractProperty& other) { + auto otherType = dynamicTypeMetaInfo(other); + return metaInfoIsCompatibleUnsafe(type, otherType); + }; + + for (const VariantProperty &variantProperty : modelNode.variantProperties()) { + if (variantProperty.isDynamic() && isCompatible(variantProperty)) + possibleProperties << QString::fromUtf8(variantProperty.name()); + } + + for (const BindingProperty &bindingProperty : modelNode.bindingProperties()) { + if (bindingProperty.isDynamic() && isCompatible(bindingProperty)) + possibleProperties << QString::fromUtf8((bindingProperty.name())); + } + NodeMetaInfo metaInfo = modelNode.metaInfo(); if (metaInfo.isValid()) { for (const auto &property : metaInfo.properties()) { diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h index 8a1cafa3e54..1f05d654596 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.h @@ -34,7 +34,7 @@ bool isDynamicVariantPropertyType(const TypeName &type); QVariant defaultValueForType(const TypeName &type); QString defaultExpressionForType(const TypeName &type); -QStringList availableModelNodes(AbstractView *view); +QStringList availableSources(AbstractView *view); QStringList availableTargetProperties(const BindingProperty &bindingProperty); QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view); QList dynamicPropertiesFromNode(const ModelNode &node); diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 084afb8e100..69f51a277d2 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -168,7 +168,6 @@ void DynamicPropertiesModel::removeItem(const AbstractProperty &property) static_cast(removeRow(*index)); setCurrentProperty(current); - emit currentIndexChanged(); } QHash DynamicPropertiesModel::roleNames() const