forked from qt-creator/qt-creator
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:
@@ -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());
|
||||
};
|
||||
|
||||
|
||||
|
@@ -43,6 +43,7 @@ public:
|
||||
ModelNode modelNode() const;
|
||||
|
||||
void reparentHere(const ModelNode &modelNode);
|
||||
void setDynamicTypeNameAndsetModelNode(const TypeName &typeName, const ModelNode &modelNode);
|
||||
|
||||
NodeProperty();
|
||||
protected:
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
||||
newNodeProperty.setModelNode(newNode);
|
||||
if (dynamicPropertyType.isEmpty())
|
||||
newNodeProperty.setModelNode(newNode);
|
||||
else
|
||||
newNodeProperty.setDynamicTypeNameAndsetModelNode(dynamicPropertyType, newNode);
|
||||
|
||||
if (propertyTakesComponent)
|
||||
m_merger->setupComponentDelayed(newNode, true);
|
||||
|
@@ -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,
|
||||
|
@@ -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));
|
||||
|
@@ -82,6 +82,8 @@ private slots:
|
||||
void testModelBasicOperations();
|
||||
void testModelResolveIds();
|
||||
void testModelNodeListProperty();
|
||||
void testModelNodePropertyDynamic();
|
||||
void testModelNodePropertyDynamicSource();
|
||||
void testModelPropertyValueTypes();
|
||||
void testModelNodeInHierarchy();
|
||||
void testModelNodeIsAncestorOf();
|
||||
|
Reference in New Issue
Block a user