QmlDesigner: Add support for Behaviours

A Behavior will be added as a normal ModelNode to the default
property, but we store the property name in behaviorPropertyName.

The value of behaviorPropertyName cannot be changed after the
ModelNode was created, since I do not see any use case and it keeps
things simple.

Change-Id: I69ba1d4d706432cfbbd35b001238f623e6e0b4fd
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Thomas Hartmann
2022-07-13 12:07:58 +02:00
parent c505d6a72e
commit aeea22257c
12 changed files with 172 additions and 20 deletions

View File

@@ -112,12 +112,13 @@ public:
ModelNode createModelNode(const TypeName &typeName); ModelNode createModelNode(const TypeName &typeName);
ModelNode createModelNode(const TypeName &typeName, ModelNode createModelNode(const TypeName &typeName,
int majorVersion, int majorVersion,
int minorVersion, int minorVersion,
const PropertyListType &propertyList = PropertyListType(), const PropertyListType &propertyList = PropertyListType(),
const PropertyListType &auxPropertyList = PropertyListType(), const PropertyListType &auxPropertyList = PropertyListType(),
const QString &nodeSource = QString(), const QString &nodeSource = {},
ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource); ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource,
const QString &behaviorPropertyName = {});
ModelNode rootModelNode() const; ModelNode rootModelNode() const;
ModelNode rootModelNode(); ModelNode rootModelNode();

View File

@@ -238,6 +238,7 @@ public:
bool isComponent() const; bool isComponent() const;
bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
QIcon typeIcon() const; QIcon typeIcon() const;
QString behaviorPropertyName() const;
friend void swap(ModelNode &first, ModelNode &second) noexcept friend void swap(ModelNode &first, ModelNode &second) noexcept
{ {

View File

@@ -97,14 +97,15 @@ ModelNode AbstractView::createModelNode(const TypeName &typeName)
} }
ModelNode AbstractView::createModelNode(const TypeName &typeName, ModelNode AbstractView::createModelNode(const TypeName &typeName,
int majorVersion, int majorVersion,
int minorVersion, int minorVersion,
const QList<QPair<PropertyName, QVariant> > &propertyList, const QList<QPair<PropertyName, QVariant>> &propertyList,
const QList<QPair<PropertyName, QVariant> > &auxPropertyList, const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
const QString &nodeSource, const QString &nodeSource,
ModelNode::NodeSourceType nodeSourceType) ModelNode::NodeSourceType nodeSourceType,
const QString &behaviorPropertyName)
{ {
return ModelNode(model()->d->createNode(typeName, majorVersion, minorVersion, propertyList, auxPropertyList, nodeSource, nodeSourceType), model(), this); return ModelNode(model()->d->createNode(typeName, majorVersion, minorVersion, propertyList, auxPropertyList, nodeSource, nodeSourceType, behaviorPropertyName), model(), this);
} }

View File

@@ -389,5 +389,15 @@ void InternalNode::setNodeSourceType(int i)
m_nodeSourceType = i; m_nodeSourceType = i;
} }
QString InternalNode::behaviorPropertyName() const
{
return m_behaviorPropertyName;
}
void InternalNode::setBehaviorPropertyName(const QString &name)
{
m_behaviorPropertyName = name;
}
} }
} }

View File

@@ -125,6 +125,9 @@ public:
int nodeSourceType() const; int nodeSourceType() const;
void setNodeSourceType(int i); void setNodeSourceType(int i);
QString behaviorPropertyName() const;
void setBehaviorPropertyName(const QString &name);
protected: protected:
Pointer internalPointer() const; Pointer internalPointer() const;
void setInternalWeakPointer(const Pointer &pointer); void setInternalWeakPointer(const Pointer &pointer);
@@ -151,6 +154,8 @@ private:
QString m_nodeSource; QString m_nodeSource;
int m_nodeSourceType = 0; int m_nodeSourceType = 0;
QString m_behaviorPropertyName;
}; };
Utils::QHashValueType qHash(const InternalNodePointer& node); Utils::QHashValueType qHash(const InternalNodePointer& node);

View File

@@ -97,9 +97,11 @@ ModelPrivate::ModelPrivate(Model *model)
0, 0,
PropertyListType(), PropertyListType(),
PropertyListType(), PropertyListType(),
QString(), {},
ModelNode::NodeWithoutSource, ModelNode::NodeWithoutSource,
{},
true); true);
m_currentStateNode = m_rootInternalNode; m_currentStateNode = m_rootInternalNode;
m_currentTimelineNode = m_rootInternalNode; m_currentTimelineNode = m_rootInternalNode;
} }
@@ -250,6 +252,7 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName,
const QList<QPair<PropertyName, QVariant>> &auxPropertyList, const QList<QPair<PropertyName, QVariant>> &auxPropertyList,
const QString &nodeSource, const QString &nodeSource,
ModelNode::NodeSourceType nodeSourceType, ModelNode::NodeSourceType nodeSourceType,
const QString &behaviorPropertyName,
bool isRootNode) bool isRootNode)
{ {
if (typeName.isEmpty()) if (typeName.isEmpty())
@@ -263,6 +266,8 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName,
InternalNodePointer newNode = InternalNode::create(typeName, majorVersion, minorVersion, internalId); InternalNodePointer newNode = InternalNode::create(typeName, majorVersion, minorVersion, internalId);
newNode->setNodeSourceType(nodeSourceType); newNode->setNodeSourceType(nodeSourceType);
newNode->setBehaviorPropertyName(behaviorPropertyName);
using PropertyPair = QPair<PropertyName, QVariant>; using PropertyPair = QPair<PropertyName, QVariant>;
for (const PropertyPair &propertyPair : propertyList) { for (const PropertyPair &propertyPair : propertyList) {

View File

@@ -103,6 +103,7 @@ public:
const QList<QPair<PropertyName, QVariant> > &auxPropertyList, const QList<QPair<PropertyName, QVariant> > &auxPropertyList,
const QString &nodeSource, const QString &nodeSource,
ModelNode::NodeSourceType nodeSourceType, ModelNode::NodeSourceType nodeSourceType,
const QString &behaviorPropertyName,
bool isRootNode = false); bool isRootNode = false);

View File

@@ -1436,4 +1436,12 @@ QIcon ModelNode::typeIcon() const
return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png")); return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png"));
} }
QString ModelNode::behaviorPropertyName() const
{
if (m_internalNode.isNull())
return {};
return m_internalNode->behaviorPropertyName();
}
} }

View File

@@ -218,6 +218,10 @@ QString QmlTextGenerator::toQml(const ModelNode &node, int indentDepth) const
result = alias + '.'; result = alias + '.';
result += type; result += type;
if (!node.behaviorPropertyName().isEmpty()) {
result += " on " + node.behaviorPropertyName();
}
result += QStringLiteral(" {\n"); result += QStringLiteral(" {\n");
const int propertyIndentDepth = indentDepth + m_tabSettings.m_indentSize; const int propertyIndentDepth = indentDepth + m_tabSettings.m_indentSize;

View File

@@ -1183,6 +1183,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
ReadingContext *context, ReadingContext *context,
DifferenceHandler &differenceHandler) DifferenceHandler &differenceHandler)
{ {
auto binding = AST::cast<AST::UiObjectBinding *>(astNode);
const bool hasOnToken = binding && binding->hasOnToken;
QString onTokenProperty;
if (hasOnToken)
onTokenProperty = toString(binding->qualifiedId);
AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode); AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
AST::UiObjectInitializer *astInitializer = initializerOfObject(astNode); AST::UiObjectInitializer *astInitializer = initializerOfObject(astNode);
@@ -1219,10 +1228,10 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model()); bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
if (modelNode.type()
if (modelNode.type() != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then. != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then.
|| modelNode.majorVersion() != majorVersion || modelNode.majorVersion() != majorVersion || modelNode.minorVersion() != minorVersion
|| modelNode.minorVersion() != minorVersion) { || modelNode.behaviorPropertyName() != onTokenProperty) {
const bool isRootNode = m_rewriterView->rootModelNode() == modelNode; const bool isRootNode = m_rewriterView->rootModelNode() == modelNode;
differenceHandler.typeDiffers(isRootNode, modelNode, typeName, differenceHandler.typeDiffers(isRootNode, modelNode, typeName,
majorVersion, minorVersion, majorVersion, minorVersion,
@@ -1289,7 +1298,8 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
} else if (auto binding = AST::cast<AST::UiObjectBinding *>(member)) { } else if (auto binding = AST::cast<AST::UiObjectBinding *>(member)) {
const QString astPropertyName = toString(binding->qualifiedId); const QString astPropertyName = toString(binding->qualifiedId);
if (binding->hasOnToken) { if (binding->hasOnToken) {
// skip value sources // Store Behaviours in the default property
defaultPropertyItems.append(member);
} else { } else {
const Value *propertyType = nullptr; const Value *propertyType = nullptr;
const ObjectValue *containingObject = nullptr; const ObjectValue *containingObject = nullptr;
@@ -1685,6 +1695,13 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
{ {
QString nodeSource; QString nodeSource;
auto binding = AST::cast<AST::UiObjectBinding *>(astNode);
const bool hasOnToken = binding && binding->hasOnToken;
QString onTokenProperty;
if (hasOnToken)
onTokenProperty = toString(binding->qualifiedId);
AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode); AST::UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
@@ -1716,7 +1733,8 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
PropertyListType(), PropertyListType(),
PropertyListType(), PropertyListType(),
nodeSource, nodeSource,
nodeSourceType); nodeSourceType,
onTokenProperty);
syncNode(newNode, astNode, context, differenceHandler); syncNode(newNode, astNode, context, differenceHandler);
return newNode; return newNode;

View File

@@ -1248,6 +1248,103 @@ void tst_TestCore::testRewriterReparentToNewNode()
QCOMPARE(testRewriterView->allModelNodes().count(), 8); QCOMPARE(testRewriterView->allModelNodes().count(), 8);
} }
void tst_TestCore::testRewriterBehaivours()
{
const QLatin1String qmlString("\n"
"import QtQuick 2.0\n"
"\n"
"Item {\n"
" Item {}\n"
" Item {}\n"
" Behavior on width {\n"
" NumberAnimation { duration: 1000 }\n"
" }\n"
" Item {}\n"
"}\n");
QPlainTextEdit textEdit;
textEdit.setPlainText(qmlString);
NotIndentingTextEditModifier modifier(&textEdit);
QScopedPointer<Model> model(Model::create("QtQuick.Rectangle"));
QScopedPointer<TestRewriterView> testRewriterView(new TestRewriterView(0, RewriterView::Amend));
testRewriterView->setTextModifier(&modifier);
model->attachView(testRewriterView.data());
QVERIFY(testRewriterView->errors().isEmpty());
ModelNode rootModelNode = testRewriterView->rootModelNode();
QVERIFY(rootModelNode.isValid());
const QList<ModelNode> children = rootModelNode.directSubModelNodes();
QCOMPARE(children.count(), 4);
ModelNode behavior;
for (const ModelNode &child : children) {
if (child.type() == "QtQuick.Behavior")
behavior = child;
}
QVERIFY(behavior.isValid());
QVERIFY(!behavior.behaviorPropertyName().isEmpty());
QCOMPARE(behavior.behaviorPropertyName(), "width");
QVERIFY(!behavior.directSubModelNodes().isEmpty());
ModelNode animation = behavior.directSubModelNodes().first();
QVERIFY(animation.isValid());
NodeMetaInfo metaInfo = behavior.metaInfo();
QVERIFY(metaInfo.isValid());
ModelNode newBehavior = testRewriterView->createModelNode("QtQuick.Behavior",
metaInfo.majorVersion(),
metaInfo.minorVersion(),
{},
{},
{},
ModelNode::NodeWithoutSource,
"height");
rootModelNode.defaultNodeListProperty().reparentHere(newBehavior);
QCOMPARE(newBehavior.behaviorPropertyName(), "height");
metaInfo = animation.metaInfo();
QVERIFY(metaInfo.isValid());
ModelNode newAnimation = testRewriterView->createModelNode(metaInfo.typeName(),
metaInfo.majorVersion(),
metaInfo.minorVersion());
newBehavior.defaultNodeListProperty().reparentHere(newAnimation);
newAnimation.variantProperty("duration").setValue(500);
const QLatin1String expextedQmlCode(
"\nimport QtQuick 2.0\n\n"
"Item {\n Item {}\n Item {}\n"
" Behavior on width {\n "
" NumberAnimation { duration: 1000 }\n"
" }\n"
" Item {}\n\n"
" Behavior on height {\n"
" NumberAnimation {\n"
" duration: 500\n"
" }\n }\n}\n");
QCOMPARE(textEdit.toPlainText(), expextedQmlCode);
newBehavior.destroy();
QVERIFY(!newBehavior.isValid());
}
void tst_TestCore::testRewriterForGradientMagic() void tst_TestCore::testRewriterForGradientMagic()
{ {
const QLatin1String qmlString("\n" const QLatin1String qmlString("\n"

View File

@@ -145,6 +145,7 @@ private slots:
void testRewriterUnicodeChars(); void testRewriterUnicodeChars();
void testRewriterTransactionAddingAfterReparenting(); void testRewriterTransactionAddingAfterReparenting();
void testRewriterReparentToNewNode(); void testRewriterReparentToNewNode();
void testRewriterBehaivours();
// //
// unit tests QmlModelNodeFacade/QmlModelState // unit tests QmlModelNodeFacade/QmlModelState