diff --git a/src/plugins/qmldesigner/designercore/include/nodeabstractproperty.h b/src/plugins/qmldesigner/designercore/include/nodeabstractproperty.h index 07f75d07a61..fa0460f7714 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeabstractproperty.h +++ b/src/plugins/qmldesigner/designercore/include/nodeabstractproperty.h @@ -59,7 +59,7 @@ public: protected: NodeAbstractProperty(const PropertyName &propertyName, const Internal::InternalNodePointer &internalNode, Model *model, AbstractView *view); NodeAbstractProperty(const Internal::InternalNodeAbstractPropertyPointer &property, Model *model, AbstractView *view); - void reparentHere(const ModelNode &modelNode, bool isNodeList); + void reparentHere(const ModelNode &modelNode, bool isNodeList, const TypeName &typeName = TypeName()); }; diff --git a/src/plugins/qmldesigner/designercore/include/nodeproperty.h b/src/plugins/qmldesigner/designercore/include/nodeproperty.h index 6c90488ec65..40a5e2ec781 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeproperty.h +++ b/src/plugins/qmldesigner/designercore/include/nodeproperty.h @@ -43,6 +43,7 @@ public: ModelNode modelNode() const; void reparentHere(const ModelNode &modelNode); + void setDynamicTypeNameAndsetModelNode(const TypeName &typeName, const ModelNode &modelNode); NodeProperty(); protected: diff --git a/src/plugins/qmldesigner/designercore/model/internalnode.cpp b/src/plugins/qmldesigner/designercore/model/internalnode.cpp index 18b2b5d940a..7e11ab6c3fc 100644 --- a/src/plugins/qmldesigner/designercore/model/internalnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/internalnode.cpp @@ -269,9 +269,10 @@ void InternalNode::addVariantProperty(const PropertyName &name) m_namePropertyHash.insert(name, newProperty); } -void InternalNode::addNodeProperty(const PropertyName &name) +void InternalNode::addNodeProperty(const PropertyName &name, const TypeName &dynamicTypeName) { - InternalProperty::Pointer newProperty(InternalNodeProperty::create(name, internalPointer())); + InternalNodeProperty::Pointer newProperty(InternalNodeProperty::create(name, internalPointer())); + newProperty->setDynamicTypeName(dynamicTypeName); m_namePropertyHash.insert(name, newProperty); } diff --git a/src/plugins/qmldesigner/designercore/model/internalnode_p.h b/src/plugins/qmldesigner/designercore/model/internalnode_p.h index f34cf6c03eb..c19fca55a85 100644 --- a/src/plugins/qmldesigner/designercore/model/internalnode_p.h +++ b/src/plugins/qmldesigner/designercore/model/internalnode_p.h @@ -101,7 +101,7 @@ public: void addSignalHandlerProperty(const PropertyName &name); void addNodeListProperty(const PropertyName &name); void addVariantProperty(const PropertyName &name); - void addNodeProperty(const PropertyName &name); + void addNodeProperty(const PropertyName &name, const TypeName &dynamicTypeName); PropertyNameList propertyNameList() const; diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index 5470260c2d2..f93aef2f370 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1522,14 +1522,18 @@ void ModelPrivate::setDynamicBindingProperty(const InternalNodePointer &internal notifyBindingPropertiesChanged(QList() << bindingProperty, propertyChange); } -void ModelPrivate::reparentNode(const InternalNode::Pointer &newParentNode, const PropertyName &name, const InternalNode::Pointer &internalNodePointer, bool list) +void ModelPrivate::reparentNode(const InternalNode::Pointer &newParentNode, + const PropertyName &name, + const InternalNode::Pointer &internalNodePointer, + bool list, + const TypeName &dynamicTypeName) { AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges; if (!newParentNode->hasProperty(name)) { if (list) newParentNode->addNodeListProperty(name); else - newParentNode->addNodeProperty(name); + newParentNode->addNodeProperty(name, dynamicTypeName); propertyChange |= AbstractView::PropertiesAdded; } diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index a8f8eae9637..23b8c637d30 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -191,7 +191,10 @@ public: void setVariantProperty(const InternalNodePointer &internalNodePointer, const PropertyName &name, const QVariant &value); void setDynamicVariantProperty(const InternalNodePointer &internalNodePointer, const PropertyName &name, const TypeName &propertyType, const QVariant &value); void setDynamicBindingProperty(const InternalNodePointer &internalNodePointer, const PropertyName &name, const TypeName &dynamicPropertyType, const QString &expression); - void reparentNode(const InternalNodePointer &internalNodePointer, const PropertyName &name, const InternalNodePointer &internalNodeToBeAppended, bool list = true); + void reparentNode(const InternalNodePointer &internalNodePointer, + const PropertyName &name, + const InternalNodePointer &internalNodeToBeAppended, + bool list = true, const TypeName &dynamicTypeName = TypeName()); void changeNodeOrder(const InternalNodePointer &internalParentNode, const PropertyName &listPropertyName, int from, int to); void checkPropertyName(const PropertyName &propertyName); void clearParent(const InternalNodePointer &internalNodePointer); diff --git a/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp b/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp index a1c8696833a..488988bbeb3 100644 --- a/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp @@ -61,10 +61,12 @@ void NodeAbstractProperty::reparentHere(const ModelNode &modelNode) reparentHere(modelNode, parentModelNode().metaInfo().propertyIsListProperty(name()) || isDefaultProperty()); //we could use the metasystem instead? } -void NodeAbstractProperty::reparentHere(const ModelNode &modelNode, bool isNodeList) +void NodeAbstractProperty::reparentHere(const ModelNode &modelNode, bool isNodeList, const TypeName &dynamicTypeName) { - if (modelNode.hasParentProperty() && modelNode.parentProperty() == *this) + if (modelNode.hasParentProperty() && modelNode.parentProperty() == *this + && dynamicTypeName == modelNode.parentProperty().dynamicTypeName()) return; + Internal::WriteLocker locker(model()); if (!isValid()) throw InvalidModelNodeException(__LINE__, __FUNCTION__, __FILE__); @@ -78,19 +80,24 @@ void NodeAbstractProperty::reparentHere(const ModelNode &modelNode, bool isNode if (modelNode.isAncestorOf(parentModelNode())) throw InvalidReparentingException(__LINE__, __FUNCTION__, __FILE__); + /* This is currently not supported and not required. */ + /* Removing the property does work of course. */ + if (modelNode.hasParentProperty() && modelNode.parentProperty().isDynamic()) + throw InvalidReparentingException(__LINE__, __FUNCTION__, __FILE__); + if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isNodeAbstractProperty()) model()->d->removeProperty(internalNode()->property(name())); if (modelNode.hasParentProperty()) { Internal::InternalNodeAbstractProperty::Pointer oldParentProperty = modelNode.internalNode()->parentProperty(); - model()->d->reparentNode(internalNode(), name(), modelNode.internalNode(), isNodeList); + model()->d->reparentNode(internalNode(), name(), modelNode.internalNode(), isNodeList, dynamicTypeName); Q_ASSERT(!oldParentProperty.isNull()); } else { - model()->d->reparentNode(internalNode(), name(), modelNode.internalNode(), isNodeList); + model()->d->reparentNode(internalNode(), name(), modelNode.internalNode(), isNodeList, dynamicTypeName); } } diff --git a/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp b/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp index e9ca8b0cbb2..6d9bcfd1de3 100644 --- a/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp @@ -26,6 +26,7 @@ #include "nodeproperty.h" #include "invalidmodelnodeexception.h" #include "invalidargumentexception.h" +#include "invalidreparentingexception.h" #include "internalnode_p.h" #include "model.h" #include "model_p.h" @@ -82,4 +83,15 @@ void NodeProperty::reparentHere(const ModelNode &modelNode) NodeAbstractProperty::reparentHere(modelNode, false); } +void NodeProperty::setDynamicTypeNameAndsetModelNode(const TypeName &typeName, const ModelNode &modelNode) +{ + if (!modelNode.isValid()) + throw InvalidModelNodeException(__LINE__, __FUNCTION__, __FILE__); + + if (modelNode.hasParentProperty()) /* Not supported */ + throw InvalidReparentingException(__LINE__, __FUNCTION__, __FILE__); + + NodeAbstractProperty::reparentHere(modelNode, false, typeName); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index e227745c61f..5371cedfdd0 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -1087,11 +1087,14 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, if (context->isArrayProperty(propertyType, containingObject, name)) syncArrayProperty(modelProperty, QList() << member, context, differenceHandler); else - syncNodeProperty(modelProperty, binding, context, differenceHandler); + syncNodeProperty(modelProperty, binding, context, TypeName(), differenceHandler); modelPropertyNames.remove(astPropertyName.toUtf8()); } else { - qWarning() << "Skipping invalid node property" << astPropertyName + qWarning() << "Syncing unknown node property" << astPropertyName << "for node type" << modelNode.type(); + AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8()); + syncNodeProperty(modelProperty, binding, context, TypeName(), differenceHandler); + modelPropertyNames.remove(astPropertyName.toUtf8()); } } } else if (AST::UiScriptBinding *script = AST::cast(member)) { @@ -1117,7 +1120,13 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, const TypeName &astType = property->memberType.toUtf8(); AbstractProperty modelProperty = modelNode.property(astName.toUtf8()); - if (!property->statement || isLiteralValue(property->statement)) { + + if (property->binding) { + if (AST::UiObjectBinding *binding = AST::cast(property->binding)) + syncNodeProperty(modelProperty, binding, context, astType, differenceHandler); + else + qWarning() << "Arrays are not yet supported"; + } else if (!property->statement || isLiteralValue(property->statement)) { const QVariant variantValue = convertDynamicPropertyValueToVariant(astValue, QString::fromLatin1(astType)); syncVariantProperty(modelProperty, variantValue, astType, differenceHandler); } else { @@ -1306,8 +1315,10 @@ void TextToModelMerger::syncNodeId(ModelNode &modelNode, const QString &astObjec void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty, AST::UiObjectBinding *binding, ReadingContext *context, + const TypeName &dynamicPropertyType, DifferenceHandler &differenceHandler) { + QString typeNameString; QString dummy; int majorVersion; @@ -1316,12 +1327,13 @@ void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty, TypeName typeName = typeNameString.toUtf8(); + if (typeName.isEmpty()) { qWarning() << "Skipping node with unknown type" << toString(binding->qualifiedTypeNameId); return; } - if (modelProperty.isNodeProperty()) { + if (modelProperty.isNodeProperty() && dynamicPropertyType == modelProperty.dynamicTypeName()) { ModelNode nodePropertyNode = modelProperty.toNodeProperty().modelNode(); syncNode(nodePropertyNode, binding, context, differenceHandler); } else { @@ -1330,6 +1342,7 @@ void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty, majorVersion, minorVersion, binding, + dynamicPropertyType, context); } } @@ -1601,6 +1614,7 @@ void ModelValidator::shouldBeNodeProperty(AbstractProperty &modelProperty, int /*majorVersion*/, int /*minorVersion*/, AST::UiObjectMember * /*astNode*/, + const TypeName & /*dynamicPropertyType */, ReadingContext * /*context*/) { Q_UNUSED(modelProperty) @@ -1754,6 +1768,7 @@ void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty, int majorVersion, int minorVersion, AST::UiObjectMember *astNode, + const TypeName &dynamicPropertyType, ReadingContext *context) { ModelNode theNode = modelProperty.parentModelNode(); @@ -1769,7 +1784,10 @@ void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty, context, *this); - newNodeProperty.setModelNode(newNode); + if (dynamicPropertyType.isEmpty()) + newNodeProperty.setModelNode(newNode); + else + newNodeProperty.setDynamicTypeNameAndsetModelNode(dynamicPropertyType, newNode); if (propertyTakesComponent) m_merger->setupComponentDelayed(newNode, true); diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h index 63c4d4fd805..1c8285c1c04 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h @@ -89,6 +89,7 @@ public: void syncNodeProperty(AbstractProperty &modelProperty, QmlJS::AST::UiObjectBinding *binding, ReadingContext *context, + const TypeName &astType, DifferenceHandler &differenceHandler); void syncExpressionProperty(AbstractProperty &modelProperty, const QString &javascript, @@ -187,6 +188,7 @@ public: int majorVersion, int minorVersion, QmlJS::AST::UiObjectMember *astNode, + const TypeName &dynamicPropertyType, ReadingContext *context) = 0; virtual void modelNodeAbsentFromQml(ModelNode &modelNode) = 0; virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, @@ -238,7 +240,9 @@ public: int majorVersion, int minorVersion, QmlJS::AST::UiObjectMember *astNode, + const TypeName &dynamicPropertyType, ReadingContext *context); + virtual void modelNodeAbsentFromQml(ModelNode &modelNode); virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, ReadingContext *context, @@ -286,7 +290,9 @@ public: int majorVersion, int minorVersion, QmlJS::AST::UiObjectMember *astNode, + const TypeName &dynamicPropertyType, ReadingContext *context); + virtual void modelNodeAbsentFromQml(ModelNode &modelNode); virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, ReadingContext *context, diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index 2031d36aaa6..f8503d21106 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -1587,6 +1587,116 @@ void tst_TestCore::testModelNodeListProperty() QVERIFY(!rectChildren.isValid()); } +void tst_TestCore::testModelNodePropertyDynamic() +{ + // + // Test NodeListProperty API + // + QScopedPointer model(createModel("QtQuick.Rectangle", 2, 0)); + QVERIFY(model.data()); + + QScopedPointer view(new TestView(model.data())); + QVERIFY(view.data()); + model->attachView(view.data()); + + model->rewriterView()->setCheckSemanticErrors(false); //This test needs this + + ModelNode rootNode = view->rootModelNode(); + + // + // Item {} + // + NodeProperty nodeProperty = rootNode.nodeProperty("gradient1"); + QVERIFY(!rootNode.hasProperty("gradient1")); + QVERIFY(nodeProperty.isValid()); + QVERIFY(!nodeProperty.isNodeProperty()); + QVERIFY(nodeProperty.isEmpty()); + + ModelNode rectNode = view->createModelNode("QtQuick.Rectangle", 2, 0); + nodeProperty.reparentHere(rectNode); + + QVERIFY(rootNode.hasProperty("gradient1")); + QVERIFY(nodeProperty.isValid()); + QVERIFY(nodeProperty.isNodeProperty()); + QVERIFY(!nodeProperty.isEmpty()); + QVERIFY(!nodeProperty.isDynamic()); + + + NodeProperty dynamicNodeProperty = rootNode.nodeProperty("gradient2"); + QVERIFY(!rootNode.hasProperty("gradient2")); + QVERIFY(dynamicNodeProperty.isValid()); + QVERIFY(!dynamicNodeProperty.isNodeProperty()); + QVERIFY(dynamicNodeProperty.isEmpty()); + + ModelNode rectNode2 = view->createModelNode("QtQuick.Rectangle", 2, 0); + dynamicNodeProperty.setDynamicTypeNameAndsetModelNode("Gradient", rectNode2); + + QVERIFY(rootNode.hasProperty("gradient2")); + QVERIFY(dynamicNodeProperty.isValid()); + QVERIFY(dynamicNodeProperty.isNodeProperty()); + QVERIFY(!dynamicNodeProperty.isEmpty()); + QVERIFY(dynamicNodeProperty.isDynamic()); + + + rectNode2.setIdWithRefactoring("test"); + QCOMPARE(rectNode2.id(), QString("test")); + + rectNode2.variantProperty("x").setValue(10); + + QCOMPARE(rectNode2.variantProperty("x").value(), QVariant(10)); + + rootNode.removeProperty("gradient2"); + QVERIFY(!rootNode.hasProperty("gradient2")); + + QVERIFY(dynamicNodeProperty.isValid()); + QVERIFY(!rootNode.hasProperty("gradient2")); + + QVERIFY(!dynamicNodeProperty.isNodeProperty()); +} + +static QString dynmaicNodePropertySource = "import QtQuick 2.1\n" + "Rectangle {\n" + " property Gradient gradient1: Gradient {\n" + " id: pGradient\n" + " }\n" + " gradient: Gradient {\n" + " id: secondGradient\n" + " GradientStop { id: nOne; position: 0.0; color: \"blue\" }\n" + " GradientStop { id: nTwo; position: 1.0; color: \"lightsteelblue\" }\n" + " }\n" + "}"; + + +void tst_TestCore::testModelNodePropertyDynamicSource() +{ + QPlainTextEdit textEdit; + textEdit.setPlainText(dynmaicNodePropertySource); + NotIndentingTextEditModifier textModifier(&textEdit); + + QScopedPointer model(Model::create("QtQuick.Item", 2, 1)); + QVERIFY(model.data()); + + QScopedPointer testRewriterView(new TestRewriterView()); + testRewriterView->setTextModifier(&textModifier); + model->attachView(testRewriterView.data()); + + + QVERIFY(model.data()); + ModelNode rootModelNode(testRewriterView->rootModelNode()); + QVERIFY(rootModelNode.isValid()); + QCOMPARE(rootModelNode.directSubModelNodes().size(), 2); + + QVERIFY(rootModelNode.hasProperty("gradient")); + QVERIFY(rootModelNode.hasProperty("gradient1")); + + QVERIFY(rootModelNode.property("gradient").isNodeProperty()); + QVERIFY(rootModelNode.property("gradient1").isNodeProperty()); + QVERIFY(rootModelNode.property("gradient1").isDynamic()); + + rootModelNode.removeProperty("gradient1"); + QVERIFY(!rootModelNode.hasProperty("gradient1")); +} + void tst_TestCore::testBasicOperationsWithView() { QScopedPointer model(createModel("QtQuick.Item", 2, 0)); diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h index 4cbefcd912a..f846c1963ec 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h @@ -82,6 +82,8 @@ private slots: void testModelBasicOperations(); void testModelResolveIds(); void testModelNodeListProperty(); + void testModelNodePropertyDynamic(); + void testModelNodePropertyDynamicSource(); void testModelPropertyValueTypes(); void testModelNodeInHierarchy(); void testModelNodeIsAncestorOf();