From b03ef971b02044e1a0782614623eab5161f56ebb Mon Sep 17 00:00:00 2001 From: Erik Verbruggen <erik.verbruggen@nokia.com> Date: Thu, 22 Apr 2010 17:16:37 +0200 Subject: [PATCH] Fixed reading of grouped properties. Task-number: BAUHAUS-620 --- .../core/model/texttomodelmerger.cpp | 193 +++++++++++------- .../core/model/texttomodelmerger.h | 13 ++ .../qml/qmldesigner/coretests/testcore.cpp | 32 +++ .../auto/qml/qmldesigner/coretests/testcore.h | 1 + 4 files changed, 169 insertions(+), 70 deletions(-) diff --git a/src/plugins/qmldesigner/core/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/core/model/texttomodelmerger.cpp index 1e578b09bac..947cce394e4 100644 --- a/src/plugins/qmldesigner/core/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/core/model/texttomodelmerger.cpp @@ -62,7 +62,7 @@ static inline QString stripQuotes(const QString &str) return str; } -static inline QString descape(const QString &value) +static inline QString deEscape(const QString &value) { QString result = value; @@ -157,7 +157,7 @@ static inline int propertyType(const QString &typeName) static inline QVariant convertDynamicPropertyValueToVariant(const QString &astValue, const QString &astType) { - const QString cleanedValue = descape(stripQuotes(astValue.trimmed())); + const QString cleanedValue = deEscape(stripQuotes(astValue.trimmed())); if (astType.isEmpty()) return QString(); @@ -228,7 +228,7 @@ public: /// When something is changed here, also change Check::checkScopeObjectMember in /// qmljscheck.cpp /// ### Maybe put this into the context as a helper method. - bool lookupProperty(const UiQualifiedId *id, const Interpreter::Value **property = 0, const Interpreter::ObjectValue **parentObject = 0, QString *name = 0) + bool lookupProperty(const QString &prefix, const UiQualifiedId *id, const Interpreter::Value **property = 0, const Interpreter::ObjectValue **parentObject = 0, QString *name = 0) { QList<const Interpreter::ObjectValue *> scopeObjects = m_context->scopeChain().qmlScopeObjects; if (scopeObjects.isEmpty()) @@ -240,7 +240,12 @@ public: if (! id->name) // possible after error recovery return false; - QString propertyName = id->name->asString(); + QString propertyName; + if (prefix.isEmpty()) + propertyName = id->name->asString(); + else + propertyName = prefix; + if (name) *name = propertyName; @@ -279,7 +284,9 @@ public: // member lookup const UiQualifiedId *idPart = id; - while (idPart->next) { + if (prefix.isEmpty()) + idPart = idPart->next; + for (; idPart; idPart = idPart->next) { objectValue = Interpreter::value_cast<const Interpreter::ObjectValue *>(value); if (! objectValue) { // if (idPart->name) @@ -290,13 +297,12 @@ public: if (parentObject) *parentObject = objectValue; - if (! idPart->next->name) { + if (! idPart->name) { // somebody typed "id." and error recovery still gave us a valid tree, // so just bail out here. return false; } - idPart = idPart->next; propertyName = idPart->name->asString(); if (name) *name = propertyName; @@ -335,14 +341,14 @@ public: return false; } - QVariant convertToVariant(const QString &astValue, UiQualifiedId *propertyId) + QVariant convertToVariant(const QString &astValue, const QString &propertyPrefix, UiQualifiedId *propertyId) { - const QString cleanedValue = descape(stripQuotes(astValue.trimmed())); + const QString cleanedValue = deEscape(stripQuotes(astValue.trimmed())); const Interpreter::Value *property = 0; const Interpreter::ObjectValue *containingObject = 0; QString name; - if (!lookupProperty(propertyId, &property, &containingObject, &name)) { - qWarning() << "Unknown property" << flatten(propertyId) + if (!lookupProperty(propertyPrefix, propertyId, &property, &containingObject, &name)) { + qWarning() << "Unknown property" << propertyPrefix + QLatin1Char('.') + flatten(propertyId) << "on line" << propertyId->identifierToken.startLine << "column" << propertyId->identifierToken.startColumn; return QVariant(cleanedValue); @@ -384,7 +390,7 @@ public: return v; } - QVariant convertToEnum(Statement *rhs, UiQualifiedId *propertyId) + QVariant convertToEnum(Statement *rhs, const QString &propertyPrefix, UiQualifiedId *propertyId) { ExpressionStatement *eStmt = cast<ExpressionStatement *>(rhs); if (!eStmt || !eStmt->expression) @@ -392,7 +398,7 @@ public: const Interpreter::ObjectValue *containingObject = 0; QString name; - if (!lookupProperty(propertyId, 0, &containingObject, &name)) { + if (!lookupProperty(propertyPrefix, propertyId, 0, &containingObject, &name)) { return QVariant(); } @@ -640,7 +646,7 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, if (UiArrayBinding *array = cast<UiArrayBinding *>(member)) { const QString astPropertyName = flatten(array->qualifiedId); - if (typeName == QLatin1String("Qt/PropertyChanges") || context->lookupProperty(array->qualifiedId)) { + if (typeName == QLatin1String("Qt/PropertyChanges") || context->lookupProperty(QString(), array->qualifiedId)) { AbstractProperty modelProperty = modelNode.property(astPropertyName); QList<UiObjectMember *> arrayMembers; for (UiArrayMemberList *iter = array->members; iter; iter = iter->next) @@ -653,8 +659,19 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, qWarning() << "Skipping invalid array property" << astPropertyName << "for node type" << modelNode.type(); } - } else if (cast<UiObjectDefinition *>(member)) { - defaultPropertyItems.append(member); + } else if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) { + const QString name = def->qualifiedTypeNameId->name->asString(); + if (name.isEmpty() || !name.at(0).isUpper()) { + QStringList props = syncGroupedProperties(modelNode, + name, + def->initializer->members, + context, + differenceHandler); + foreach (const QString &prop, props) + modelPropertyNames.remove(prop); + } else { + defaultPropertyItems.append(member); + } } else if (UiObjectBinding *binding = cast<UiObjectBinding *>(member)) { const QString astPropertyName = flatten(binding->qualifiedId); if (binding->hasOnToken) { @@ -663,7 +680,7 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, const Interpreter::Value *propertyType = 0; const Interpreter::ObjectValue *containingObject = 0; QString name; - if (context->lookupProperty(binding->qualifiedId, &propertyType, &containingObject, &name) || typeName == QLatin1String("Qt/PropertyChanges")) { + if (context->lookupProperty(QString(), binding->qualifiedId, &propertyType, &containingObject, &name) || typeName == QLatin1String("Qt/PropertyChanges")) { AbstractProperty modelProperty = modelNode.property(astPropertyName); if (context->isArrayProperty(propertyType, containingObject, name)) { syncArrayProperty(modelProperty, QList<QmlJS::AST::UiObjectMember*>() << member, context, differenceHandler); @@ -677,59 +694,7 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, } } } else if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) { - const QString astPropertyName = flatten(script->qualifiedId); - QString astValue; - if (script->statement) { - astValue = textAt(context->doc(), - script->statement->firstSourceLocation(), - script->statement->lastSourceLocation()); - astValue = astValue.trimmed(); - if (astValue.endsWith(QLatin1Char(';'))) - astValue = astValue.left(astValue.length() - 1); - astValue = astValue.trimmed(); - } - - if (astPropertyName == QLatin1String("id")) { - syncNodeId(modelNode, astValue, differenceHandler); - } else if (isSignalPropertyName(astPropertyName)) { - // skip signals - } else if (isLiteralValue(script)) { - if (typeName == QLatin1String("Qt/PropertyChanges")) { - AbstractProperty modelProperty = modelNode.property(astPropertyName); - const QVariant variantValue(descape(stripQuotes(astValue))); - syncVariantProperty(modelProperty, variantValue, QString(), differenceHandler); - modelPropertyNames.remove(astPropertyName); - } else { - const QVariant variantValue = context->convertToVariant(astValue, script->qualifiedId); - if (variantValue.isValid()) { - AbstractProperty modelProperty = modelNode.property(astPropertyName); - syncVariantProperty(modelProperty, variantValue, QString(), differenceHandler); - modelPropertyNames.remove(astPropertyName); - } else { - qWarning() << "Skipping invalid variant property" << astPropertyName - << "for node type" << modelNode.type(); - } - } - } else { - // First see if it is a qualified enum: - const QVariant enumValue = context->convertToEnum(script->statement, script->qualifiedId); - if (enumValue.isValid()) { - const QString astPropertyName = flatten(script->qualifiedId); - AbstractProperty modelProperty = modelNode.property(astPropertyName); - syncVariantProperty(modelProperty, enumValue, QString(), differenceHandler); - modelPropertyNames.remove(astPropertyName); - } else { - // apparently not, so: - if (typeName == QLatin1String("Qt/PropertyChanges") || context->lookupProperty(script->qualifiedId)) { - AbstractProperty modelProperty = modelNode.property(astPropertyName); - syncExpressionProperty(modelProperty, astValue, differenceHandler); - modelPropertyNames.remove(astPropertyName); - } else { - qWarning() << "Skipping invalid expression property" << astPropertyName - << "for node type" << modelNode.type(); - } - } - } + modelPropertyNames.remove(syncScriptBinding(modelNode, QString(), script, context, differenceHandler)); } else if (UiPublicMember *property = cast<UiPublicMember *>(member)) { if (property->type == UiPublicMember::Signal) continue; // QML designer doesn't support this yet. @@ -786,6 +751,73 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, context->leaveScope(); } +QString TextToModelMerger::syncScriptBinding(ModelNode &modelNode, + const QString &prefix, + UiScriptBinding *script, + ReadingContext *context, + DifferenceHandler &differenceHandler) +{ + QString astPropertyName = flatten(script->qualifiedId); + if (!prefix.isEmpty()) + astPropertyName.prepend(prefix + QLatin1Char('.')); + + QString astValue; + if (script->statement) { + astValue = textAt(context->doc(), + script->statement->firstSourceLocation(), + script->statement->lastSourceLocation()); + astValue = astValue.trimmed(); + if (astValue.endsWith(QLatin1Char(';'))) + astValue = astValue.left(astValue.length() - 1); + astValue = astValue.trimmed(); + } + + if (astPropertyName == QLatin1String("id")) { + syncNodeId(modelNode, astValue, differenceHandler); + return astPropertyName; + } + + if (isSignalPropertyName(astPropertyName)) + return QString(); + + if (isLiteralValue(script)) { + if (modelNode.type() == QLatin1String("Qt/PropertyChanges")) { + AbstractProperty modelProperty = modelNode.property(astPropertyName); + const QVariant variantValue(deEscape(stripQuotes(astValue))); + syncVariantProperty(modelProperty, variantValue, QString(), differenceHandler); + return astPropertyName; + } else { + const QVariant variantValue = context->convertToVariant(astValue, prefix, script->qualifiedId); + if (variantValue.isValid()) { + AbstractProperty modelProperty = modelNode.property(astPropertyName); + syncVariantProperty(modelProperty, variantValue, QString(), differenceHandler); + return astPropertyName; + } else { + qWarning() << "Skipping invalid variant property" << astPropertyName + << "for node type" << modelNode.type(); + return QString(); + } + } + } + + const QVariant enumValue = context->convertToEnum(script->statement, prefix, script->qualifiedId); + if (enumValue.isValid()) { // It is a qualified enum: + AbstractProperty modelProperty = modelNode.property(astPropertyName); + syncVariantProperty(modelProperty, enumValue, QString(), differenceHandler); + return astPropertyName; + } else { // Not an enum, so: + if (modelNode.type() == QLatin1String("Qt/PropertyChanges") || context->lookupProperty(prefix, script->qualifiedId)) { + AbstractProperty modelProperty = modelNode.property(astPropertyName); + syncExpressionProperty(modelProperty, astValue, differenceHandler); + return astPropertyName; + } else { + qWarning() << "Skipping invalid expression property" << astPropertyName + << "for node type" << modelNode.type(); + return QString(); + } + } +} + void TextToModelMerger::syncNodeId(ModelNode &modelNode, const QString &astObjectId, DifferenceHandler &differenceHandler) { @@ -928,6 +960,27 @@ ModelNode TextToModelMerger::createModelNode(const QString &typeName, return newNode; } +QStringList TextToModelMerger::syncGroupedProperties(ModelNode &modelNode, + const QString &name, + UiObjectMemberList *members, + ReadingContext *context, + DifferenceHandler &differenceHandler) +{ + QStringList props; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + + if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) { + const QString prop = syncScriptBinding(modelNode, name, script, context, differenceHandler); + if (!prop.isEmpty()) + props.append(prop); + } + } + + return props; +} + void ModelValidator::modelMissesImport(const Import &import) { Q_ASSERT(m_merger->view()->model()->imports().contains(import)); diff --git a/src/plugins/qmldesigner/core/model/texttomodelmerger.h b/src/plugins/qmldesigner/core/model/texttomodelmerger.h index 14fdf03b67d..9924ea56038 100644 --- a/src/plugins/qmldesigner/core/model/texttomodelmerger.h +++ b/src/plugins/qmldesigner/core/model/texttomodelmerger.h @@ -34,8 +34,11 @@ #include "import.h" #include "nodelistproperty.h" #include "modelnode.h" + #include <qmljs/qmljsdocument.h> +#include <QtCore/QStringList> + namespace QmlDesigner { class CORESHARED_EXPORT RewriterView; @@ -68,6 +71,11 @@ public: QmlJS::AST::UiObjectMember *astNode, ReadingContext *context, DifferenceHandler &differenceHandler); + QString syncScriptBinding(ModelNode &modelNode, + const QString &prefix, + QmlJS::AST::UiScriptBinding *script, + ReadingContext *context, + DifferenceHandler &differenceHandler); void syncNodeId(ModelNode &modelNode, const QString &astObjectId, DifferenceHandler &differenceHandler); void syncNodeProperty(AbstractProperty &modelProperty, @@ -95,6 +103,11 @@ public: QmlJS::AST::UiObjectMember *astNode, ReadingContext *context, DifferenceHandler &differenceHandler); + QStringList syncGroupedProperties(ModelNode &modelNode, + const QString &name, + QmlJS::AST::UiObjectMemberList *members, + ReadingContext *context, + DifferenceHandler &differenceHandler); private: void setupComponent(const ModelNode &node); diff --git a/tests/auto/qml/qmldesigner/coretests/testcore.cpp b/tests/auto/qml/qmldesigner/coretests/testcore.cpp index 023be2559b1..8f57c67ad8d 100644 --- a/tests/auto/qml/qmldesigner/coretests/testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/testcore.cpp @@ -535,6 +535,38 @@ void TestCore::testRewriterDynamicProperties() // QVERIFY(compareTree(testRewriterView1->rootModelNode(), testRewriterView2->rootModelNode())); } +void TestCore::testRewriterGroupedProperties() +{ + const QLatin1String qmlString("\n" + "import Qt 4.6\n" + "\n" + "Text {\n" + " font {\n" + " pointSize: 10\n" + " underline: true\n" + " }\n" + "}"); + + QPlainTextEdit textEdit1; + textEdit1.setPlainText(qmlString); + NotIndentingTextEditModifier modifier1(&textEdit1); + + QScopedPointer<Model> model1(Model::create("Qt/Text")); + + QScopedPointer<TestRewriterView> testRewriterView1(new TestRewriterView()); + testRewriterView1->setTextModifier(&modifier1); + model1->attachView(testRewriterView1.data()); + + QVERIFY(testRewriterView1->errors().isEmpty()); + + // + // text2model + // + ModelNode rootModelNode = testRewriterView1->rootModelNode(); + QCOMPARE(rootModelNode.property(QLatin1String("font.pointSize")).toVariantProperty().value().toDouble(), 10.0); + QCOMPARE(rootModelNode.property(QLatin1String("font.underline")).toVariantProperty().value().toBool(), true); +} + void TestCore::loadSubItems() { QFile file(QString(QTCREATORDIR) + "/tests/auto/qml/qmldesigner/data/fx/topitem.qml"); diff --git a/tests/auto/qml/qmldesigner/coretests/testcore.h b/tests/auto/qml/qmldesigner/coretests/testcore.h index 7f2a4bb18a2..cb1b73660a5 100644 --- a/tests/auto/qml/qmldesigner/coretests/testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/testcore.h @@ -119,6 +119,7 @@ private slots: void testRewriterNodeSliding(); void testRewriterExceptionHandling(); void testRewriterDynamicProperties(); + void testRewriterGroupedProperties(); // -- GitLab