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 <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2023-09-08 15:49:58 +02:00
parent 1d884898be
commit 2dda34f781
10 changed files with 172 additions and 98 deletions

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import HelperWidgets as HelperWidgets
PopupDialog { PopupDialog {
property alias backend: form.backend property alias backend: form.backend
@@ -15,7 +16,7 @@ PopupDialog {
text: qsTr("Owner") text: qsTr("Owner")
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("The owner of the property") tooltip: qsTr("The owner of the property")
} }

View File

@@ -64,10 +64,16 @@ Column {
onCurrentTypeIndexChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex onCurrentTypeIndexChanged: sourceProperty.currentIndex = sourceProperty.currentTypeIndex
} }
PopupLabel { StudioControls.TopLevelComboBox {
width: root.columnWidth id: name
text: backend.property.currentText 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
}
} }
} }

View File

@@ -4,6 +4,7 @@
import QtQuick import QtQuick
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import HelperWidgets 2.0 as HelperWidgets
PopupDialog { PopupDialog {
@@ -18,7 +19,7 @@ PopupDialog {
text: qsTr("Target") text: qsTr("Target")
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("Choose the target for the signal.") tooltip: qsTr("Choose the target for the signal.")
} }

View File

@@ -12,7 +12,7 @@ Text {
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
property alias tooltip: area.tooltip property alias tooltip: area.tooltip
ToolTipArea { HelperWidgets.ToolTipArea {
id: area id: area
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("missing") tooltip: qsTr("missing")

View File

@@ -3,6 +3,7 @@
import QtQuick import QtQuick
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import HelperWidgets as HelperWidgets
PopupDialog { PopupDialog {
property alias backend: form.backend property alias backend: form.backend
@@ -16,7 +17,7 @@ PopupDialog {
text: qsTr("Owner") text: qsTr("Owner")
font.pixelSize: StudioTheme.Values.myFontSize font.pixelSize: StudioTheme.Values.myFontSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
ToolTipArea { HelperWidgets.ToolTipArea {
anchors.fill: parent anchors.fill: parent
tooltip: qsTr("The owner of the property") tooltip: qsTr("The owner of the property")
} }

View File

@@ -5,6 +5,7 @@
#include "bindingmodelitem.h" #include "bindingmodelitem.h"
#include "connectionview.h" #include "connectionview.h"
#include "connectioneditorutils.h" #include "connectioneditorutils.h"
#include "modelfwd.h"
#include <bindingproperty.h> #include <bindingproperty.h>
#include <nodemetainfo.h> #include <nodemetainfo.h>
@@ -145,11 +146,12 @@ void BindingModel::updateItem(const BindingProperty &property)
item->updateProperty(property); item->updateProperty(property);
else else
appendRow(new BindingModelItem(property)); appendRow(new BindingModelItem(property));
m_delegate->update(currentProperty(), m_connectionView);
} }
void BindingModel::removeItem(const AbstractProperty &property) void BindingModel::removeItem(const AbstractProperty &property)
{ {
AbstractProperty current = currentProperty(); AbstractProperty current = currentProperty();
if (auto index = rowForProperty(property)) if (auto index = rowForProperty(property))
static_cast<void>(removeRow(*index)); static_cast<void>(removeRow(*index));
@@ -167,7 +169,33 @@ void BindingModel::commitExpression(int row, const QString &expression)
return; return;
connectionView()->executeInTransaction(__FUNCTION__, [&bindingProperty, expression]() { 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]() { connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() {
expressionChanged(); expressionChanged();
}); });
connect(&m_property, &StudioQmlComboBoxBackend::activated, this, [this]() {
propertyNameChanged();
});
} }
void BindingModelBackendDelegate::update(const BindingProperty &property, AbstractView *view) 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()); auto [sourceNodeName, sourcePropertyName] = splitExpression(property.expression());
QString targetName = QString::fromUtf8(property.name()); QStringList sourceNodes = {};
m_targetNode = idOrTypeName(property.parentModelNode()); if (!sourceNodeName.isEmpty())
sourceNodes = addName(availableSources(view), sourceNodeName);
auto modelNodes = addName(availableModelNodes(view), sourceNodeName); m_sourceNode.setModel(sourceNodes);
m_sourceNode.setModel(modelNodes);
m_sourceNode.setCurrentText(sourceNodeName); m_sourceNode.setCurrentText(sourceNodeName);
auto sourceproperties = addName(availableSourceProperties(property, view), sourcePropertyName); auto sourceproperties = addName(availableSourceProperties(property, view), sourcePropertyName);
m_sourceNodeProperty.setModel(sourceproperties); m_sourceNodeProperty.setModel(sourceproperties);
m_sourceNodeProperty.setCurrentText(sourcePropertyName); m_sourceNodeProperty.setCurrentText(sourcePropertyName);
QString targetName = QString::fromUtf8(property.name());
m_targetNode = idOrTypeName(property.parentModelNode());
auto targetProperties = addName(availableTargetProperties(property), targetName); auto targetProperties = addName(availableTargetProperties(property), targetName);
m_property.setModel(targetProperties); m_property.setModel(targetProperties);
m_property.setCurrentText(targetName); m_property.setCurrentText(targetName);
@@ -283,20 +318,36 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty()
void BindingModelBackendDelegate::expressionChanged() const void BindingModelBackendDelegate::expressionChanged() const
{ {
BindingModel *model = qobject_cast<BindingModel *>(parent()); auto commit = [this]() {
QTC_ASSERT(model, return); BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return);
const QString sourceNode = m_sourceNode.currentText(); const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText(); const QString sourceProperty = m_sourceNodeProperty.currentText();
QString expression;
if (sourceProperty.isEmpty())
expression = sourceNode;
else
expression = sourceNode + QLatin1String(".") + sourceProperty;
QString expression; int row = model->currentIndex();
if (sourceProperty.isEmpty()) model->commitExpression(row, expression);
expression = sourceNode; };
else
expression = sourceNode + QLatin1String(".") + sourceProperty;
int row = model->currentIndex(); callLater(commit);
model->commitExpression(row, expression); }
void BindingModelBackendDelegate::propertyNameChanged() const
{
auto commit = [this]() {
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return);
const PropertyName propertyName = m_property.currentText().toUtf8();
int row = model->currentIndex();
model->commitPropertyName(row, propertyName);
};
callLater(commit);
} }
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -49,6 +49,7 @@ public:
void removeItem(const AbstractProperty &property); void removeItem(const AbstractProperty &property);
void commitExpression(int row, const QString &expression); void commitExpression(int row, const QString &expression);
void commitPropertyName(int row, const PropertyName &name);
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@@ -86,6 +87,7 @@ public:
private: private:
QString targetNode() const; QString targetNode() const;
void expressionChanged() const; void expressionChanged() const;
void propertyNameChanged() const;
StudioQmlComboBoxBackend *property(); StudioQmlComboBoxBackend *property();
StudioQmlComboBoxBackend *sourceNode(); StudioQmlComboBoxBackend *sourceNode();

View File

@@ -16,6 +16,7 @@
#include <qmldesignertr.h> #include <qmldesignertr.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QMessageBox> #include <QMessageBox>
#include <QRegularExpression> #include <QRegularExpression>
@@ -82,6 +83,38 @@ NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property)
return { }; 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 typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName)
{ {
QVariant returnValue = variant; QVariant returnValue = variant;
@@ -217,7 +250,21 @@ QString defaultExpressionForType(const TypeName &type)
return expression; 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; QStringList sourceNodes;
for (const ModelNode &modelNode : view->allModelNodes()) { for (const ModelNode &modelNode : view->allModelNodes()) {
@@ -225,22 +272,7 @@ QStringList availableModelNodes(AbstractView *view)
sourceNodes.append(modelNode.id()); sourceNodes.append(modelNode.id());
} }
std::sort(sourceNodes.begin(), sourceNodes.end()); std::sort(sourceNodes.begin(), sourceNodes.end());
return sourceNodes; return singletonsFromView(view) + 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;
} }
QStringList availableTargetProperties(const BindingProperty &bindingProperty) QStringList availableTargetProperties(const BindingProperty &bindingProperty)
@@ -261,10 +293,9 @@ QStringList availableTargetProperties(const BindingProperty &bindingProperty)
writableProperties.push_back(QString::fromUtf8(property.name())); writableProperties.push_back(QString::fromUtf8(property.name()));
} }
return dynamicPropertyNamesFromNode(modelNode) + writableProperties; return writableProperties;
} }
return { };
return dynamicPropertyNamesFromNode(modelNode);
} }
ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const ModelNode &targetNode) ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const ModelNode &targetNode)
@@ -278,64 +309,13 @@ ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const Model
return {}; 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) QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view)
{ {
const QString expression = bindingProperty.expression(); const QString expression = bindingProperty.expression();
const QStringList stringlist = expression.split(QLatin1String(".")); const QStringList stringlist = expression.split(QLatin1String("."));
QStringList possibleProperties;
const QString &id = stringlist.constFirst(); const QString &id = stringlist.constFirst();
ModelNode modelNode = getNodeByIdOrParent(view, id, bindingProperty.parentModelNode()); 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; NodeMetaInfo type;
if (bindingProperty.isDynamic()) { if (bindingProperty.isDynamic()) {
@@ -345,6 +325,39 @@ QStringList availableSourceProperties(const BindingProperty &bindingProperty, Ab
} else } else
qWarning() << __FUNCTION__ << " no meta info for target node"; 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(); NodeMetaInfo metaInfo = modelNode.metaInfo();
if (metaInfo.isValid()) { if (metaInfo.isValid()) {
for (const auto &property : metaInfo.properties()) { for (const auto &property : metaInfo.properties()) {

View File

@@ -34,7 +34,7 @@ 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 availableModelNodes(AbstractView *view); QStringList availableSources(AbstractView *view);
QStringList availableTargetProperties(const BindingProperty &bindingProperty); QStringList availableTargetProperties(const BindingProperty &bindingProperty);
QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view); QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view);
QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node); QList<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node);

View File

@@ -168,7 +168,6 @@ void DynamicPropertiesModel::removeItem(const AbstractProperty &property)
static_cast<void>(removeRow(*index)); static_cast<void>(removeRow(*index));
setCurrentProperty(current); setCurrentProperty(current);
emit currentIndexChanged();
} }
QHash<int, QByteArray> DynamicPropertiesModel::roleNames() const QHash<int, QByteArray> DynamicPropertiesModel::roleNames() const