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 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")
}

View File

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

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -5,6 +5,7 @@
#include "bindingmodelitem.h"
#include "connectionview.h"
#include "connectioneditorutils.h"
#include "modelfwd.h"
#include <bindingproperty.h>
#include <nodemetainfo.h>
@@ -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<void>(removeRow(*index));
@@ -167,7 +169,33 @@ void BindingModel::commitExpression(int row, const QString &expression)
return;
connectionView()->executeInTransaction(__FUNCTION__, [&bindingProperty, expression]() {
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,12 +318,12 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty()
void BindingModelBackendDelegate::expressionChanged() const
{
auto commit = [this]() {
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return);
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
QString expression;
if (sourceProperty.isEmpty())
expression = sourceNode;
@@ -297,6 +332,22 @@ void BindingModelBackendDelegate::expressionChanged() const
int row = model->currentIndex();
model->commitExpression(row, expression);
};
callLater(commit);
}
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

View File

@@ -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<int, QByteArray> roleNames() const override;
@@ -86,6 +87,7 @@ public:
private:
QString targetNode() const;
void expressionChanged() const;
void propertyNameChanged() const;
StudioQmlComboBoxBackend *property();
StudioQmlComboBoxBackend *sourceNode();

View File

@@ -16,6 +16,7 @@
#include <qmldesignertr.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QMessageBox>
#include <QRegularExpression>
@@ -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()) {

View File

@@ -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<AbstractProperty> dynamicPropertiesFromNode(const ModelNode &node);

View File

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