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 <tim.jenssen@qt.io>
This commit is contained in:
Thomas Hartmann
2016-09-20 15:25:15 +02:00
parent 6c4b05fd54
commit 325f63c732
12 changed files with 180 additions and 16 deletions

View File

@@ -59,7 +59,7 @@ public:
protected: protected:
NodeAbstractProperty(const PropertyName &propertyName, const Internal::InternalNodePointer &internalNode, Model *model, AbstractView *view); NodeAbstractProperty(const PropertyName &propertyName, const Internal::InternalNodePointer &internalNode, Model *model, AbstractView *view);
NodeAbstractProperty(const Internal::InternalNodeAbstractPropertyPointer &property, 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());
}; };

View File

@@ -43,6 +43,7 @@ public:
ModelNode modelNode() const; ModelNode modelNode() const;
void reparentHere(const ModelNode &modelNode); void reparentHere(const ModelNode &modelNode);
void setDynamicTypeNameAndsetModelNode(const TypeName &typeName, const ModelNode &modelNode);
NodeProperty(); NodeProperty();
protected: protected:

View File

@@ -269,9 +269,10 @@ void InternalNode::addVariantProperty(const PropertyName &name)
m_namePropertyHash.insert(name, newProperty); 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); m_namePropertyHash.insert(name, newProperty);
} }

View File

@@ -101,7 +101,7 @@ public:
void addSignalHandlerProperty(const PropertyName &name); void addSignalHandlerProperty(const PropertyName &name);
void addNodeListProperty(const PropertyName &name); void addNodeListProperty(const PropertyName &name);
void addVariantProperty(const PropertyName &name); void addVariantProperty(const PropertyName &name);
void addNodeProperty(const PropertyName &name); void addNodeProperty(const PropertyName &name, const TypeName &dynamicTypeName);
PropertyNameList propertyNameList() const; PropertyNameList propertyNameList() const;

View File

@@ -1522,14 +1522,18 @@ void ModelPrivate::setDynamicBindingProperty(const InternalNodePointer &internal
notifyBindingPropertiesChanged(QList<InternalBindingPropertyPointer>() << bindingProperty, propertyChange); notifyBindingPropertiesChanged(QList<InternalBindingPropertyPointer>() << 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; AbstractView::PropertyChangeFlags propertyChange = AbstractView::NoAdditionalChanges;
if (!newParentNode->hasProperty(name)) { if (!newParentNode->hasProperty(name)) {
if (list) if (list)
newParentNode->addNodeListProperty(name); newParentNode->addNodeListProperty(name);
else else
newParentNode->addNodeProperty(name); newParentNode->addNodeProperty(name, dynamicTypeName);
propertyChange |= AbstractView::PropertiesAdded; propertyChange |= AbstractView::PropertiesAdded;
} }

View File

@@ -191,7 +191,10 @@ public:
void setVariantProperty(const InternalNodePointer &internalNodePointer, const PropertyName &name, const QVariant &value); 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 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 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 changeNodeOrder(const InternalNodePointer &internalParentNode, const PropertyName &listPropertyName, int from, int to);
void checkPropertyName(const PropertyName &propertyName); void checkPropertyName(const PropertyName &propertyName);
void clearParent(const InternalNodePointer &internalNodePointer); void clearParent(const InternalNodePointer &internalNodePointer);

View File

@@ -61,10 +61,12 @@ void NodeAbstractProperty::reparentHere(const ModelNode &modelNode)
reparentHere(modelNode, parentModelNode().metaInfo().propertyIsListProperty(name()) || isDefaultProperty()); //we could use the metasystem instead? 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; return;
Internal::WriteLocker locker(model()); Internal::WriteLocker locker(model());
if (!isValid()) if (!isValid())
throw InvalidModelNodeException(__LINE__, __FUNCTION__, __FILE__); throw InvalidModelNodeException(__LINE__, __FUNCTION__, __FILE__);
@@ -78,19 +80,24 @@ void NodeAbstractProperty::reparentHere(const ModelNode &modelNode, bool isNode
if (modelNode.isAncestorOf(parentModelNode())) if (modelNode.isAncestorOf(parentModelNode()))
throw InvalidReparentingException(__LINE__, __FUNCTION__, __FILE__); 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()) if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isNodeAbstractProperty())
model()->d->removeProperty(internalNode()->property(name())); model()->d->removeProperty(internalNode()->property(name()));
if (modelNode.hasParentProperty()) { if (modelNode.hasParentProperty()) {
Internal::InternalNodeAbstractProperty::Pointer oldParentProperty = modelNode.internalNode()->parentProperty(); 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()); Q_ASSERT(!oldParentProperty.isNull());
} else { } else {
model()->d->reparentNode(internalNode(), name(), modelNode.internalNode(), isNodeList); model()->d->reparentNode(internalNode(), name(), modelNode.internalNode(), isNodeList, dynamicTypeName);
} }
} }

View File

@@ -26,6 +26,7 @@
#include "nodeproperty.h" #include "nodeproperty.h"
#include "invalidmodelnodeexception.h" #include "invalidmodelnodeexception.h"
#include "invalidargumentexception.h" #include "invalidargumentexception.h"
#include "invalidreparentingexception.h"
#include "internalnode_p.h" #include "internalnode_p.h"
#include "model.h" #include "model.h"
#include "model_p.h" #include "model_p.h"
@@ -82,4 +83,15 @@ void NodeProperty::reparentHere(const ModelNode &modelNode)
NodeAbstractProperty::reparentHere(modelNode, false); 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 } // namespace QmlDesigner

View File

@@ -1087,11 +1087,14 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
if (context->isArrayProperty(propertyType, containingObject, name)) if (context->isArrayProperty(propertyType, containingObject, name))
syncArrayProperty(modelProperty, QList<AST::UiObjectMember*>() << member, context, differenceHandler); syncArrayProperty(modelProperty, QList<AST::UiObjectMember*>() << member, context, differenceHandler);
else else
syncNodeProperty(modelProperty, binding, context, differenceHandler); syncNodeProperty(modelProperty, binding, context, TypeName(), differenceHandler);
modelPropertyNames.remove(astPropertyName.toUtf8()); modelPropertyNames.remove(astPropertyName.toUtf8());
} else { } else {
qWarning() << "Skipping invalid node property" << astPropertyName qWarning() << "Syncing unknown node property" << astPropertyName
<< "for node type" << modelNode.type(); << "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<AST::UiScriptBinding *>(member)) { } else if (AST::UiScriptBinding *script = AST::cast<AST::UiScriptBinding *>(member)) {
@@ -1117,7 +1120,13 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
const TypeName &astType = property->memberType.toUtf8(); const TypeName &astType = property->memberType.toUtf8();
AbstractProperty modelProperty = modelNode.property(astName.toUtf8()); AbstractProperty modelProperty = modelNode.property(astName.toUtf8());
if (!property->statement || isLiteralValue(property->statement)) {
if (property->binding) {
if (AST::UiObjectBinding *binding = AST::cast<AST::UiObjectBinding *>(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)); const QVariant variantValue = convertDynamicPropertyValueToVariant(astValue, QString::fromLatin1(astType));
syncVariantProperty(modelProperty, variantValue, astType, differenceHandler); syncVariantProperty(modelProperty, variantValue, astType, differenceHandler);
} else { } else {
@@ -1306,8 +1315,10 @@ void TextToModelMerger::syncNodeId(ModelNode &modelNode, const QString &astObjec
void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty, void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty,
AST::UiObjectBinding *binding, AST::UiObjectBinding *binding,
ReadingContext *context, ReadingContext *context,
const TypeName &dynamicPropertyType,
DifferenceHandler &differenceHandler) DifferenceHandler &differenceHandler)
{ {
QString typeNameString; QString typeNameString;
QString dummy; QString dummy;
int majorVersion; int majorVersion;
@@ -1316,12 +1327,13 @@ void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty,
TypeName typeName = typeNameString.toUtf8(); TypeName typeName = typeNameString.toUtf8();
if (typeName.isEmpty()) { if (typeName.isEmpty()) {
qWarning() << "Skipping node with unknown type" << toString(binding->qualifiedTypeNameId); qWarning() << "Skipping node with unknown type" << toString(binding->qualifiedTypeNameId);
return; return;
} }
if (modelProperty.isNodeProperty()) { if (modelProperty.isNodeProperty() && dynamicPropertyType == modelProperty.dynamicTypeName()) {
ModelNode nodePropertyNode = modelProperty.toNodeProperty().modelNode(); ModelNode nodePropertyNode = modelProperty.toNodeProperty().modelNode();
syncNode(nodePropertyNode, binding, context, differenceHandler); syncNode(nodePropertyNode, binding, context, differenceHandler);
} else { } else {
@@ -1330,6 +1342,7 @@ void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty,
majorVersion, majorVersion,
minorVersion, minorVersion,
binding, binding,
dynamicPropertyType,
context); context);
} }
} }
@@ -1601,6 +1614,7 @@ void ModelValidator::shouldBeNodeProperty(AbstractProperty &modelProperty,
int /*majorVersion*/, int /*majorVersion*/,
int /*minorVersion*/, int /*minorVersion*/,
AST::UiObjectMember * /*astNode*/, AST::UiObjectMember * /*astNode*/,
const TypeName & /*dynamicPropertyType */,
ReadingContext * /*context*/) ReadingContext * /*context*/)
{ {
Q_UNUSED(modelProperty) Q_UNUSED(modelProperty)
@@ -1754,6 +1768,7 @@ void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty,
int majorVersion, int majorVersion,
int minorVersion, int minorVersion,
AST::UiObjectMember *astNode, AST::UiObjectMember *astNode,
const TypeName &dynamicPropertyType,
ReadingContext *context) ReadingContext *context)
{ {
ModelNode theNode = modelProperty.parentModelNode(); ModelNode theNode = modelProperty.parentModelNode();
@@ -1769,7 +1784,10 @@ void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty,
context, context,
*this); *this);
if (dynamicPropertyType.isEmpty())
newNodeProperty.setModelNode(newNode); newNodeProperty.setModelNode(newNode);
else
newNodeProperty.setDynamicTypeNameAndsetModelNode(dynamicPropertyType, newNode);
if (propertyTakesComponent) if (propertyTakesComponent)
m_merger->setupComponentDelayed(newNode, true); m_merger->setupComponentDelayed(newNode, true);

View File

@@ -89,6 +89,7 @@ public:
void syncNodeProperty(AbstractProperty &modelProperty, void syncNodeProperty(AbstractProperty &modelProperty,
QmlJS::AST::UiObjectBinding *binding, QmlJS::AST::UiObjectBinding *binding,
ReadingContext *context, ReadingContext *context,
const TypeName &astType,
DifferenceHandler &differenceHandler); DifferenceHandler &differenceHandler);
void syncExpressionProperty(AbstractProperty &modelProperty, void syncExpressionProperty(AbstractProperty &modelProperty,
const QString &javascript, const QString &javascript,
@@ -187,6 +188,7 @@ public:
int majorVersion, int majorVersion,
int minorVersion, int minorVersion,
QmlJS::AST::UiObjectMember *astNode, QmlJS::AST::UiObjectMember *astNode,
const TypeName &dynamicPropertyType,
ReadingContext *context) = 0; ReadingContext *context) = 0;
virtual void modelNodeAbsentFromQml(ModelNode &modelNode) = 0; virtual void modelNodeAbsentFromQml(ModelNode &modelNode) = 0;
virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty,
@@ -238,7 +240,9 @@ public:
int majorVersion, int majorVersion,
int minorVersion, int minorVersion,
QmlJS::AST::UiObjectMember *astNode, QmlJS::AST::UiObjectMember *astNode,
const TypeName &dynamicPropertyType,
ReadingContext *context); ReadingContext *context);
virtual void modelNodeAbsentFromQml(ModelNode &modelNode); virtual void modelNodeAbsentFromQml(ModelNode &modelNode);
virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty,
ReadingContext *context, ReadingContext *context,
@@ -286,7 +290,9 @@ public:
int majorVersion, int majorVersion,
int minorVersion, int minorVersion,
QmlJS::AST::UiObjectMember *astNode, QmlJS::AST::UiObjectMember *astNode,
const TypeName &dynamicPropertyType,
ReadingContext *context); ReadingContext *context);
virtual void modelNodeAbsentFromQml(ModelNode &modelNode); virtual void modelNodeAbsentFromQml(ModelNode &modelNode);
virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty, virtual ModelNode listPropertyMissingModelNode(NodeListProperty &modelProperty,
ReadingContext *context, ReadingContext *context,

View File

@@ -1587,6 +1587,116 @@ void tst_TestCore::testModelNodeListProperty()
QVERIFY(!rectChildren.isValid()); QVERIFY(!rectChildren.isValid());
} }
void tst_TestCore::testModelNodePropertyDynamic()
{
//
// Test NodeListProperty API
//
QScopedPointer<Model> model(createModel("QtQuick.Rectangle", 2, 0));
QVERIFY(model.data());
QScopedPointer<TestView> 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(Model::create("QtQuick.Item", 2, 1));
QVERIFY(model.data());
QScopedPointer<TestRewriterView> 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() void tst_TestCore::testBasicOperationsWithView()
{ {
QScopedPointer<Model> model(createModel("QtQuick.Item", 2, 0)); QScopedPointer<Model> model(createModel("QtQuick.Item", 2, 0));

View File

@@ -82,6 +82,8 @@ private slots:
void testModelBasicOperations(); void testModelBasicOperations();
void testModelResolveIds(); void testModelResolveIds();
void testModelNodeListProperty(); void testModelNodeListProperty();
void testModelNodePropertyDynamic();
void testModelNodePropertyDynamicSource();
void testModelPropertyValueTypes(); void testModelPropertyValueTypes();
void testModelNodeInHierarchy(); void testModelNodeInHierarchy();
void testModelNodeIsAncestorOf(); void testModelNodeIsAncestorOf();