diff options
author | Thomas Hartmann <thomas.hartmann@qt.io> | 2022-07-13 12:07:58 +0200 |
---|---|---|
committer | Thomas Hartmann <thomas.hartmann@qt.io> | 2022-07-13 15:33:07 +0000 |
commit | aeea22257c32ac06c71a9183c5964372fd7d01eb (patch) | |
tree | 0685133d14187823abd07673a7cf0c58f3ba69b4 | |
parent | c505d6a72e80114521f23105738f814585e36356 (diff) |
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>
12 files changed, 173 insertions, 21 deletions
diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 3c4eca0441..e41e93960c 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -112,12 +112,13 @@ public: ModelNode createModelNode(const TypeName &typeName); ModelNode createModelNode(const TypeName &typeName, - int majorVersion, - int minorVersion, - const PropertyListType &propertyList = PropertyListType(), - const PropertyListType &auxPropertyList = PropertyListType(), - const QString &nodeSource = QString(), - ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource); + int majorVersion, + int minorVersion, + const PropertyListType &propertyList = PropertyListType(), + const PropertyListType &auxPropertyList = PropertyListType(), + const QString &nodeSource = {}, + ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource, + const QString &behaviorPropertyName = {}); ModelNode rootModelNode() const; ModelNode rootModelNode(); diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 92884a9710..d187332e61 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -238,6 +238,7 @@ public: bool isComponent() const; bool isSubclassOf(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; QIcon typeIcon() const; + QString behaviorPropertyName() const; friend void swap(ModelNode &first, ModelNode &second) noexcept { diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 310dcdcdab..f3c757e81d 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -97,14 +97,15 @@ ModelNode AbstractView::createModelNode(const TypeName &typeName) } ModelNode AbstractView::createModelNode(const TypeName &typeName, - int majorVersion, - int minorVersion, - const QList<QPair<PropertyName, QVariant> > &propertyList, - const QList<QPair<PropertyName, QVariant> > &auxPropertyList, - const QString &nodeSource, - ModelNode::NodeSourceType nodeSourceType) -{ - return ModelNode(model()->d->createNode(typeName, majorVersion, minorVersion, propertyList, auxPropertyList, nodeSource, nodeSourceType), model(), this); + int majorVersion, + int minorVersion, + const QList<QPair<PropertyName, QVariant>> &propertyList, + const QList<QPair<PropertyName, QVariant>> &auxPropertyList, + const QString &nodeSource, + ModelNode::NodeSourceType nodeSourceType, + const QString &behaviorPropertyName) +{ + return ModelNode(model()->d->createNode(typeName, majorVersion, minorVersion, propertyList, auxPropertyList, nodeSource, nodeSourceType, behaviorPropertyName), model(), this); } diff --git a/src/plugins/qmldesigner/designercore/model/internalnode.cpp b/src/plugins/qmldesigner/designercore/model/internalnode.cpp index 532a156082..37c1c21b40 100644 --- a/src/plugins/qmldesigner/designercore/model/internalnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/internalnode.cpp @@ -389,5 +389,15 @@ void InternalNode::setNodeSourceType(int i) m_nodeSourceType = i; } +QString InternalNode::behaviorPropertyName() const +{ + return m_behaviorPropertyName; +} + +void InternalNode::setBehaviorPropertyName(const QString &name) +{ + m_behaviorPropertyName = name; +} + } } diff --git a/src/plugins/qmldesigner/designercore/model/internalnode_p.h b/src/plugins/qmldesigner/designercore/model/internalnode_p.h index 8770763b4e..d63fbed744 100644 --- a/src/plugins/qmldesigner/designercore/model/internalnode_p.h +++ b/src/plugins/qmldesigner/designercore/model/internalnode_p.h @@ -125,6 +125,9 @@ public: int nodeSourceType() const; void setNodeSourceType(int i); + QString behaviorPropertyName() const; + void setBehaviorPropertyName(const QString &name); + protected: Pointer internalPointer() const; void setInternalWeakPointer(const Pointer &pointer); @@ -151,6 +154,8 @@ private: QString m_nodeSource; int m_nodeSourceType = 0; + + QString m_behaviorPropertyName; }; Utils::QHashValueType qHash(const InternalNodePointer& node); diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index 772149a33d..acea8e6169 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -97,9 +97,11 @@ ModelPrivate::ModelPrivate(Model *model) 0, PropertyListType(), PropertyListType(), - QString(), + {}, ModelNode::NodeWithoutSource, + {}, true); + m_currentStateNode = m_rootInternalNode; m_currentTimelineNode = m_rootInternalNode; } @@ -250,6 +252,7 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName, const QList<QPair<PropertyName, QVariant>> &auxPropertyList, const QString &nodeSource, ModelNode::NodeSourceType nodeSourceType, + const QString &behaviorPropertyName, bool isRootNode) { if (typeName.isEmpty()) @@ -263,6 +266,8 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName, InternalNodePointer newNode = InternalNode::create(typeName, majorVersion, minorVersion, internalId); newNode->setNodeSourceType(nodeSourceType); + newNode->setBehaviorPropertyName(behaviorPropertyName); + using PropertyPair = QPair<PropertyName, QVariant>; for (const PropertyPair &propertyPair : propertyList) { diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index c6ed24f681..7e81ee3cd5 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -103,6 +103,7 @@ public: const QList<QPair<PropertyName, QVariant> > &auxPropertyList, const QString &nodeSource, ModelNode::NodeSourceType nodeSourceType, + const QString &behaviorPropertyName, bool isRootNode = false); diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index 2e31b1a824..7d65d0495f 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -1436,4 +1436,12 @@ QIcon ModelNode::typeIcon() const return QIcon(QStringLiteral(":/ItemLibrary/images/item-invalid-icon.png")); } +QString ModelNode::behaviorPropertyName() const +{ + if (m_internalNode.isNull()) + return {}; + + return m_internalNode->behaviorPropertyName(); +} + } diff --git a/src/plugins/qmldesigner/designercore/model/qmltextgenerator.cpp b/src/plugins/qmldesigner/designercore/model/qmltextgenerator.cpp index 3481724d7b..4c9311a4cf 100644 --- a/src/plugins/qmldesigner/designercore/model/qmltextgenerator.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmltextgenerator.cpp @@ -218,6 +218,10 @@ QString QmlTextGenerator::toQml(const ModelNode &node, int indentDepth) const result = alias + '.'; result += type; + if (!node.behaviorPropertyName().isEmpty()) { + result += " on " + node.behaviorPropertyName(); + } + result += QStringLiteral(" {\n"); const int propertyIndentDepth = indentDepth + m_tabSettings.m_indentSize; diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 2cddff7dfd..d7e215fcd5 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -1183,6 +1183,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, ReadingContext *context, 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::UiObjectInitializer *astInitializer = initializerOfObject(astNode); @@ -1219,10 +1228,10 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model()); - - if (modelNode.type() != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then. - || modelNode.majorVersion() != majorVersion - || modelNode.minorVersion() != minorVersion) { + if (modelNode.type() + != typeName //If there is no valid parentProperty //the node has just been created. The type is correct then. + || modelNode.majorVersion() != majorVersion || modelNode.minorVersion() != minorVersion + || modelNode.behaviorPropertyName() != onTokenProperty) { const bool isRootNode = m_rewriterView->rootModelNode() == modelNode; differenceHandler.typeDiffers(isRootNode, modelNode, typeName, majorVersion, minorVersion, @@ -1289,7 +1298,8 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, } else if (auto binding = AST::cast<AST::UiObjectBinding *>(member)) { const QString astPropertyName = toString(binding->qualifiedId); if (binding->hasOnToken) { - // skip value sources + // Store Behaviours in the default property + defaultPropertyItems.append(member); } else { const Value *propertyType = nullptr; const ObjectValue *containingObject = nullptr; @@ -1685,6 +1695,13 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName, { 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); @@ -1716,7 +1733,8 @@ ModelNode TextToModelMerger::createModelNode(const TypeName &typeName, PropertyListType(), PropertyListType(), nodeSource, - nodeSourceType); + nodeSourceType, + onTokenProperty); syncNode(newNode, astNode, context, differenceHandler); return newNode; diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index ef77342719..4813ac96f2 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -1248,6 +1248,103 @@ void tst_TestCore::testRewriterReparentToNewNode() 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() { const QLatin1String qmlString("\n" diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h index 5248763fef..d55bb2a196 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h @@ -145,6 +145,7 @@ private slots: void testRewriterUnicodeChars(); void testRewriterTransactionAddingAfterReparenting(); void testRewriterReparentToNewNode(); + void testRewriterBehaivours(); // // unit tests QmlModelNodeFacade/QmlModelState |