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:
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());
};

View File

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

View File

@@ -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);
}

View File

@@ -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;

View File

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

View File

@@ -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);

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?
}
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);
}
}

View File

@@ -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

View File

@@ -1087,11 +1087,14 @@ void TextToModelMerger::syncNode(ModelNode &modelNode,
if (context->isArrayProperty(propertyType, containingObject, name))
syncArrayProperty(modelProperty, QList<AST::UiObjectMember*>() << 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<AST::UiScriptBinding *>(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<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));
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);
if (dynamicPropertyType.isEmpty())
newNodeProperty.setModelNode(newNode);
else
newNodeProperty.setDynamicTypeNameAndsetModelNode(dynamicPropertyType, newNode);
if (propertyTakesComponent)
m_merger->setupComponentDelayed(newNode, true);

View File

@@ -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,

View File

@@ -1587,6 +1587,116 @@ void tst_TestCore::testModelNodeListProperty()
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()
{
QScopedPointer<Model> model(createModel("QtQuick.Item", 2, 0));

View File

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