Prevent creation of nonexistent binding expressions

Change-Id: Id6f9f35cd40667d694fcdf06d51c4642ae71afcd
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2023-09-14 16:38:54 +02:00
committed by Thomas Hartmann
parent 9be23b73ea
commit 3a7f41b78c
4 changed files with 151 additions and 106 deletions

View File

@@ -3,8 +3,8 @@
#include "bindingmodel.h" #include "bindingmodel.h"
#include "bindingmodelitem.h" #include "bindingmodelitem.h"
#include "connectionview.h"
#include "connectioneditorutils.h" #include "connectioneditorutils.h"
#include "connectionview.h"
#include "modelfwd.h" #include "modelfwd.h"
#include <bindingproperty.h> #include <bindingproperty.h>
@@ -16,6 +16,8 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QSignalBlocker>
namespace QmlDesigner { namespace QmlDesigner {
BindingModel::BindingModel(ConnectionView *parent) BindingModel::BindingModel(ConnectionView *parent)
@@ -250,15 +252,15 @@ BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent)
, m_sourceNodeProperty() , m_sourceNodeProperty()
{ {
connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() { connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() {
expressionChanged(); sourceNodeChanged();
}); });
connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() { connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() {
expressionChanged(); sourcePropertyNameChanged();
}); });
connect(&m_property, &StudioQmlComboBoxBackend::activated, this, [this]() { connect(&m_property, &StudioQmlComboBoxBackend::activated, this, [this]() {
propertyNameChanged(); targetPropertyNameChanged();
}); });
} }
@@ -282,7 +284,8 @@ void BindingModelBackendDelegate::update(const BindingProperty &property, Abstra
m_sourceNode.setModel(sourceNodes); m_sourceNode.setModel(sourceNodes);
m_sourceNode.setCurrentText(sourceNodeName); m_sourceNode.setCurrentText(sourceNodeName);
auto sourceproperties = addName(availableSourceProperties(property, view), sourcePropertyName); auto availableProperties = availableSourceProperties(sourceNodeName, property, view);
auto sourceproperties = addName(std::move(availableProperties), sourcePropertyName);
m_sourceNodeProperty.setModel(sourceproperties); m_sourceNodeProperty.setModel(sourceproperties);
m_sourceNodeProperty.setCurrentText(sourcePropertyName); m_sourceNodeProperty.setCurrentText(sourcePropertyName);
@@ -316,14 +319,40 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty()
return &m_sourceNodeProperty; return &m_sourceNodeProperty;
} }
void BindingModelBackendDelegate::expressionChanged() const void BindingModelBackendDelegate::sourceNodeChanged()
{ {
auto commit = [this]() { BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return);
ConnectionView *view = model->connectionView();
QTC_ASSERT(view, return);
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
BindingProperty targetProperty = model->currentProperty();
QStringList properties = availableSourceProperties(sourceNode, targetProperty, view);
if (!properties.contains(sourceProperty)) {
QSignalBlocker blocker(this);
properties.prepend("---");
m_sourceNodeProperty.setModel(properties);
m_sourceNodeProperty.setCurrentText({"---"});
}
sourcePropertyNameChanged();
}
void BindingModelBackendDelegate::sourcePropertyNameChanged() const
{
const QString sourceProperty = m_sourceNodeProperty.currentText();
if (sourceProperty.isEmpty() || sourceProperty == "---")
return;
auto commit = [this, sourceProperty]() {
BindingModel *model = qobject_cast<BindingModel *>(parent()); BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return); QTC_ASSERT(model, return);
const QString sourceNode = m_sourceNode.currentText(); const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
QString expression; QString expression;
if (sourceProperty.isEmpty()) if (sourceProperty.isEmpty())
expression = sourceNode; expression = sourceNode;
@@ -337,7 +366,7 @@ void BindingModelBackendDelegate::expressionChanged() const
callLater(commit); callLater(commit);
} }
void BindingModelBackendDelegate::propertyNameChanged() const void BindingModelBackendDelegate::targetPropertyNameChanged() const
{ {
auto commit = [this]() { auto commit = [this]() {
BindingModel *model = qobject_cast<BindingModel *>(parent()); BindingModel *model = qobject_cast<BindingModel *>(parent());

View File

@@ -86,8 +86,9 @@ public:
private: private:
QString targetNode() const; QString targetNode() const;
void expressionChanged() const; void sourceNodeChanged();
void propertyNameChanged() const; void sourcePropertyNameChanged() const;
void targetPropertyNameChanged() const;
StudioQmlComboBoxBackend *property(); StudioQmlComboBoxBackend *property();
StudioQmlComboBoxBackend *sourceNode(); StudioQmlComboBoxBackend *sourceNode();

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "connectioneditorutils.h" #include "connectioneditorutils.h"
#include <QtCore/qvariant.h>
#include <abstractproperty.h> #include <abstractproperty.h>
#include <abstractview.h> #include <abstractview.h>
#include <bindingproperty.h> #include <bindingproperty.h>
@@ -58,61 +57,66 @@ PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode
return {}; return {};
} }
NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property) NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model)
{ {
// Note: Uses old mechanism to create the NodeMetaInfo and supports // Note: Uses old mechanism to create the NodeMetaInfo and supports
// only types we care about in the connection editor. // only types we care about in the connection editor.
// TODO: Support all possible AbstractProperty types and move to the // TODO: Support all possible AbstractProperty types and move to the
// AbstractProperty class. // AbstractProperty class.
if (property.dynamicTypeName() == "bool") if (typeName == "bool")
return property.model()->boolMetaInfo(); return model->boolMetaInfo();
else if (property.dynamicTypeName() == "int") else if (typeName == "int")
return property.model()->metaInfo("QML.int"); return model->metaInfo("QML.int");
else if (property.dynamicTypeName() == "real") else if (typeName == "real")
return property.model()->metaInfo("QML.real"); return model->metaInfo("QML.real");
else if (property.dynamicTypeName() == "color") else if (typeName == "color")
return property.model()->metaInfo("QML.color"); return model->metaInfo("QML.color");
else if (property.dynamicTypeName() == "string") else if (typeName == "string")
return property.model()->metaInfo("QML.string"); return model->metaInfo("QML.string");
else if (property.dynamicTypeName() == "url") else if (typeName == "url")
return property.model()->metaInfo("QML.url"); return model->metaInfo("QML.url");
else if (property.dynamicTypeName() == "variant") else if (typeName == "variant")
return property.model()->metaInfo("QML.variant"); return model->metaInfo("QML.variant");
else else
qWarning() << __FUNCTION__ << " type " << property.dynamicTypeName() << "not found"; qWarning() << __FUNCTION__ << " type " << typeName << "not found";
return {}; return {};
} }
bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &sourceType, const NodeMetaInfo &targetType) NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property)
{ {
if (sourceType.isVariant()) return dynamicTypeNameToNodeMetaInfo(property.dynamicTypeName(), property.model());
}
bool metaInfoIsCompatibleUnsafe(const NodeMetaInfo &target, const NodeMetaInfo &source)
{
if (target.isVariant())
return true; return true;
if (sourceType.isBool() && targetType.isBool()) if (target == source)
return true; return true;
if (sourceType == targetType) if (target.isBool() && source.isBool())
return true; return true;
if (sourceType.isNumber() && targetType.isNumber()) if (target.isNumber() && source.isNumber())
return true; return true;
if (sourceType.isString() && targetType.isString()) if (target.isString() && source.isString())
return true; return true;
if (sourceType.isUrl() && targetType.isUrl()) if (target.isUrl() && source.isUrl())
return true; return true;
if (sourceType.isColor() && targetType.isColor()) if (target.isColor() && source.isColor())
return true; return true;
return false; return false;
} }
bool metaInfoIsCompatible(const NodeMetaInfo &sourceType, const PropertyMetaInfo &metaInfo) bool metaInfoIsCompatible(const NodeMetaInfo &targetType, const PropertyMetaInfo &sourceInfo)
{ {
NodeMetaInfo targetType = metaInfo.propertyType(); NodeMetaInfo sourceType = sourceInfo.propertyType();
return metaInfoIsCompatibleUnsafe(sourceType, targetType); return metaInfoIsCompatibleUnsafe(targetType, sourceType);
} }
QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName) QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName)
@@ -250,6 +254,25 @@ QString defaultExpressionForType(const TypeName &type)
return expression; return expression;
} }
std::pair<QString, QString> splitExpression(const QString &expression)
{
// ### Todo from original code (getExpressionStrings):
// We assume no expressions yet
const QStringList stringList = expression.split(QLatin1String("."));
QString sourceNode = stringList.constFirst();
QString propertyName;
for (int i = 1; i < stringList.size(); ++i) {
propertyName += stringList.at(i);
if (i != stringList.size() - 1)
propertyName += QLatin1String(".");
}
if (propertyName.isEmpty())
std::swap(sourceNode, propertyName);
return {sourceNode, propertyName};
}
QStringList singletonsFromView(AbstractView *view) QStringList singletonsFromView(AbstractView *view)
{ {
RewriterView *rv = view->rewriterView(); RewriterView *rv = view->rewriterView();
@@ -264,6 +287,29 @@ QStringList singletonsFromView(AbstractView *view)
return out; return out;
} }
std::vector<PropertyMetaInfo> propertiesFromSingleton(const QString &name, AbstractView *view)
{
Model *model = view->model();
QTC_ASSERT(model, return {});
if (NodeMetaInfo metaInfo = model->metaInfo(name.toUtf8()); metaInfo.isValid())
return metaInfo.properties();
return {};
}
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node)
{
auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); };
auto byName = [](const AbstractProperty &a, const AbstractProperty &b) {
return a.name() < b.name();
};
QList<AbstractProperty> dynamicProperties = Utils::filtered(node.properties(), isDynamic);
Utils::sort(dynamicProperties, byName);
return dynamicProperties;
}
QStringList availableSources(AbstractView *view) QStringList availableSources(AbstractView *view)
{ {
QStringList sourceNodes; QStringList sourceNodes;
@@ -309,19 +355,17 @@ ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const Model
return {}; return {};
} }
QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view) QStringList availableSourceProperties(const QString &id,
const BindingProperty &targetProperty,
AbstractView *view)
{ {
const QString expression = bindingProperty.expression(); ModelNode modelNode = getNodeByIdOrParent(view, id, targetProperty.parentModelNode());
const QStringList stringlist = expression.split(QLatin1String("."));
const QString &id = stringlist.constFirst(); NodeMetaInfo targetType;
ModelNode modelNode = getNodeByIdOrParent(view, id, bindingProperty.parentModelNode()); if (targetProperty.isDynamic()) {
targetType = dynamicTypeMetaInfo(targetProperty);
NodeMetaInfo type; } else if (auto metaInfo = targetProperty.parentModelNode().metaInfo(); metaInfo.isValid()) {
if (bindingProperty.isDynamic()) { targetType = metaInfo.property(targetProperty.name()).propertyType();
type = dynamicTypeMetaInfo(bindingProperty);
} else if (auto metaInfo = bindingProperty.parentModelNode().metaInfo(); metaInfo.isValid()) {
type = metaInfo.property(bindingProperty.name()).propertyType();
} else } else
qWarning() << __FUNCTION__ << " no meta info for target node"; qWarning() << __FUNCTION__ << " no meta info for target node";
@@ -329,23 +373,19 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
if (!modelNode.isValid()) { if (!modelNode.isValid()) {
QStringList singletons = singletonsFromView(view); QStringList singletons = singletonsFromView(view);
if (singletons.contains(id)) { if (singletons.contains(id)) {
Model *model = view->model(); for (const auto &property : propertiesFromSingleton(id, view)) {
QTC_ASSERT(model, return {}); if (metaInfoIsCompatible(targetType, property))
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())); possibleProperties.push_back(QString::fromUtf8(property.name()));
} }
}
return possibleProperties; return possibleProperties;
} }
qWarning() << __FUNCTION__ << " invalid model node"; qWarning() << __FUNCTION__ << " invalid model node: " << id;
return {}; return {};
} }
auto isCompatible = [type](const AbstractProperty& other) { auto isCompatible = [targetType](const AbstractProperty &other) {
auto otherType = dynamicTypeMetaInfo(other); auto otherType = dynamicTypeMetaInfo(other);
return metaInfoIsCompatibleUnsafe(type, otherType); return metaInfoIsCompatibleUnsafe(targetType, otherType);
}; };
for (const VariantProperty &variantProperty : modelNode.variantProperties()) { for (const VariantProperty &variantProperty : modelNode.variantProperties()) {
@@ -361,7 +401,7 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
NodeMetaInfo metaInfo = modelNode.metaInfo(); NodeMetaInfo metaInfo = modelNode.metaInfo();
if (metaInfo.isValid()) { if (metaInfo.isValid()) {
for (const auto &property : metaInfo.properties()) { for (const auto &property : metaInfo.properties()) {
if (metaInfoIsCompatible(type, property) ) if (metaInfoIsCompatible(targetType, property))
possibleProperties.push_back(QString::fromUtf8(property.name())); possibleProperties.push_back(QString::fromUtf8(property.name()));
} }
} else { } else {
@@ -371,35 +411,4 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
return possibleProperties; return possibleProperties;
} }
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node)
{
auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); };
auto byName = [](const AbstractProperty &a, const AbstractProperty &b) {
return a.name() < b.name();
};
QList<AbstractProperty> dynamicProperties = Utils::filtered(node.properties(), isDynamic);
Utils::sort(dynamicProperties, byName);
return dynamicProperties;
}
std::pair<QString, QString> splitExpression(const QString &expression)
{
// ### Todo from original code (getExpressionStrings):
// We assume no expressions yet
const QStringList stringList = expression.split(QLatin1String("."));
QString sourceNode = stringList.constFirst();
QString propertyName;
for (int i = 1; i < stringList.size(); ++i) {
propertyName += stringList.at(i);
if (i != stringList.size() - 1)
propertyName += QLatin1String(".");
}
if (propertyName.isEmpty())
std::swap(sourceNode, propertyName);
return {sourceNode, propertyName};
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -25,6 +25,8 @@ QString idOrTypeName(const ModelNode &modelNode);
PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode); PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode);
NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property); NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty &property);
NodeMetaInfo dynamicTypeNameToNodeMetaInfo(const TypeName &typeName, Model *model);
QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName); QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName);
void convertVariantToBindingProperty(const VariantProperty &property, const QVariant &value); void convertVariantToBindingProperty(const VariantProperty &property, const QVariant &value);
void convertBindingToVariantProperty(const BindingProperty &property, const QVariant &value); void convertBindingToVariantProperty(const BindingProperty &property, const QVariant &value);
@@ -33,12 +35,16 @@ bool isBindingExpression(const QVariant& value);
bool isDynamicVariantPropertyType(const TypeName &type); bool isDynamicVariantPropertyType(const TypeName &type);
QVariant defaultValueForType(const TypeName &type); QVariant defaultValueForType(const TypeName &type);
QString defaultExpressionForType(const TypeName &type); QString defaultExpressionForType(const TypeName &type);
QStringList availableSources(AbstractView *view);
QStringList availableTargetProperties(const BindingProperty &bindingProperty);
QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view);
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node);
std::pair<QString, QString> splitExpression(const QString &expression); std::pair<QString, QString> splitExpression(const QString &expression);
QStringList singletonsFromView(AbstractView *view);
std::vector<PropertyMetaInfo> propertiesFromSingleton(const QString &name, AbstractView *view);
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node);
QStringList availableSources(AbstractView *view);
QStringList availableTargetProperties(const BindingProperty &bindingProperty);
QStringList availableSourceProperties(const QString &id,
const BindingProperty &targetProp,
AbstractView *view);
} // namespace QmlDesigner } // namespace QmlDesigner