diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 3c4eca04413..e41e93960c4 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 92884a9710e..d187332e614 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 310dcdcdab1..f3c757e81db 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 > &propertyList, - const QList > &auxPropertyList, - const QString &nodeSource, - ModelNode::NodeSourceType nodeSourceType) + int majorVersion, + int minorVersion, + const QList> &propertyList, + const QList> &auxPropertyList, + const QString &nodeSource, + 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); } diff --git a/src/plugins/qmldesigner/designercore/model/internalnode.cpp b/src/plugins/qmldesigner/designercore/model/internalnode.cpp index 532a156082a..37c1c21b406 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 8770763b4e3..d63fbed7445 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 772149a33d9..acea8e6169c 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> &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; 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 c6ed24f681f..7e81ee3cd51 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 > &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 2e31b1a824e..7d65d0495f6 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 3481724d7bb..4c9311a4cf5 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 2cddff7dfd0..d7e215fcd53 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(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(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(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 ef773427192..4813ac96f21 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::create("QtQuick.Rectangle")); + + QScopedPointer 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 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 5248763fef9..d55bb2a1961 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