From 325f63c732cc1d1a0882f275cc5b2803c9a0331b Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Tue, 20 Sep 2016 15:25:15 +0200 Subject: [PATCH] QmlDesigner: Implementing dynamic node properties The model now supports dynamic node properties. e.g.: Item { property Item item: Item { } } In this case Property::dynamicpropertyType() is Item and Property::isDynamic() is true. To create such a property I added: NodeProperty::setDynamicTypeNameAndsetModelNode(). It is not supported to reparent nodes in an out of dynamic node properties. The model throws an exception in this case. This is currently not required on the application level and not supported by the rewriter. Change-Id: Ie05325663c481d8583dc45bee38b559c190fbb30 Reviewed-by: Tim Jenssen --- .../include/nodeabstractproperty.h | 2 +- .../designercore/include/nodeproperty.h | 1 + .../designercore/model/internalnode.cpp | 5 +- .../designercore/model/internalnode_p.h | 2 +- .../qmldesigner/designercore/model/model.cpp | 8 +- .../qmldesigner/designercore/model/model_p.h | 5 +- .../model/nodeabstractproperty.cpp | 15 ++- .../designercore/model/nodeproperty.cpp | 12 ++ .../designercore/model/texttomodelmerger.cpp | 28 ++++- .../designercore/model/texttomodelmerger.h | 6 + .../qmldesigner/coretests/tst_testcore.cpp | 110 ++++++++++++++++++ .../qml/qmldesigner/coretests/tst_testcore.h | 2 + 12 files changed, 180 insertions(+), 16 deletions(-) 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();