From 14e787b7e5abc1d665ba0e0229f60525283b1e38 Mon Sep 17 00:00:00 2001 From: Warwick Allison Date: Wed, 9 Sep 2009 14:12:45 +1000 Subject: Modifying ListModel from JS: work for structured data too. --- examples/declarative/listview/dynamic.qml | 48 ++++++++++++++++++-- src/declarative/util/qmllistmodel.cpp | 75 ++++++++++++++++++++++--------- src/declarative/util/qmllistmodel.h | 8 ++-- 3 files changed, 104 insertions(+), 27 deletions(-) diff --git a/examples/declarative/listview/dynamic.qml b/examples/declarative/listview/dynamic.qml index 58ce4b4..5111cec 100644 --- a/examples/declarative/listview/dynamic.qml +++ b/examples/declarative/listview/dynamic.qml @@ -2,42 +2,74 @@ import Qt 4.6 Item { width: 300 - height: 300 + height: 500 ListModel { id: FruitModel ListElement { name: "Apple" cost: 2.45 + attributes: [ + ListElement { description: "Core" }, + ListElement { description: "Deciduous" } + ] } ListElement { name: "Banana" cost: 1.95 + attributes: [ + ListElement { description: "Tropical" }, + ListElement { description: "Seedless" } + ] } ListElement { name: "Cumquat" cost: 3.25 + types: [ "Small", "Smaller" ] + attributes: [ + ListElement { description: "Citrus" } + ] } ListElement { name: "Durian" cost: 9.95 + attributes: [ + ListElement { description: "Tropical" }, + ListElement { description: "Smelly" } + ] } ListElement { name: "Elderberry" cost: 0.05 + attributes: [ + ListElement { description: "Berry" } + ] } ListElement { name: "Fig" cost: 0.25 + attributes: [ + ListElement { description: "Flower" } + ] } } Component { id: FruitDelegate Item { - width: parent.width; height: 35 - Text { font.pixelSize: 24; text: name } + width: parent.width; height: 55 + Text { id: Label; font.pixelSize: 24; text: name } Text { font.pixelSize: 24; text: '$'+Number(cost).toFixed(2); anchors.right: ItemButtons.left } + Row { + anchors.top: Label.bottom + spacing: 5 + Repeater { + model: attributes + Component { + Text { text: description } + } + } + } Row { id: ItemButtons anchors.right: parent.right @@ -79,7 +111,15 @@ Item { anchors.bottom: parent.bottom id: Buttons Image { source: "content/pics/add.png" - MouseRegion { anchors.fill: parent; onClicked: FruitModel.append({"name":"Pizza", "cost":5.95}) } + MouseRegion { anchors.fill: parent; + onClicked: { + FruitModel.append({ + "name":"Pizza", + "cost":5.95, + "attributes":[{"description": "Supreme"},{"description": "Margarita"}] + }) + } + } } Image { source: "content/pics/add.png" MouseRegion { anchors.fill: parent; onClicked: FruitModel.insert(0,{"name":"Pizza", "cost":5.95}) } diff --git a/src/declarative/util/qmllistmodel.cpp b/src/declarative/util/qmllistmodel.cpp index a5ae60f..dc6b02b 100644 --- a/src/declarative/util/qmllistmodel.cpp +++ b/src/declarative/util/qmllistmodel.cpp @@ -47,6 +47,7 @@ #include "qmlopenmetaobject.h" #include #include "qmllistmodel.h" +#include Q_DECLARE_METATYPE(QListModelInterface *) @@ -68,6 +69,8 @@ struct ListModelData ListInstruction *instructions() const { return (ListInstruction *)((char *)this + sizeof(ListModelData)); } }; +static void dump(ModelNode *node, int ind); + /*! \qmlclass ListModel \brief The ListModel element defines a free-form list data source. @@ -140,7 +143,7 @@ struct ListModelData name: "Banana" cost: 1.95 attributes: [ - ListElement { description: "Tropical" } + ListElement { description: "Tropical" }, ListElement { description: "Seedless" } ] } @@ -238,6 +241,42 @@ struct ModelNode return objectCache; } + void setListValue(const QScriptValue& valuelist) { + QScriptValueIterator it(valuelist); + values.clear(); + while (it.hasNext()) { + it.next(); + ModelNode *value = new ModelNode; + QScriptValue v = it.value(); + if (v.isArray()) { + value->setListValue(v); + } else if (v.isObject()) { + value->setObjectValue(v); + } else { + value->values << v.toVariant(); + } + values.append(qVariantFromValue(value)); + + } + } + + void setObjectValue(const QScriptValue& valuemap) { + QScriptValueIterator it(valuemap); + while (it.hasNext()) { + it.next(); + ModelNode *value = new ModelNode; + QScriptValue v = it.value(); + if (v.isArray()) { + value->setListValue(v); + } else if (v.isObject()) { + value->setObjectValue(v); + } else { + value->values << v.toVariant(); + } + properties.insert(it.name(),value); + } + } + void setProperty(const QString& prop, const QVariant& val) { QHash::const_iterator it = properties.find(prop); if (it != properties.end()) { @@ -429,7 +468,7 @@ void QmlListModel::remove(int index) \sa set() append() */ -void QmlListModel::insert(int index, const QVariantMap& valuemap) +void QmlListModel::insert(int index, const QScriptValue& valuemap) { if (!_root) _root = new ModelNode; @@ -438,12 +477,7 @@ void QmlListModel::insert(int index, const QVariantMap& valuemap) return; } ModelNode *mn = new ModelNode; - for (QVariantMap::const_iterator it=valuemap.begin(); it!=valuemap.end(); ++it) { - addRole(it.key()); - ModelNode *value = new ModelNode; - value->values << it.value(); - mn->properties.insert(it.key(),value); - } + mn->setObjectValue(valuemap); _root->values.insert(index,qVariantFromValue(mn)); emit itemsInserted(index,1); } @@ -506,17 +540,16 @@ void QmlListModel::move(int from, int to, int n) \sa set() remove() */ -void QmlListModel::append(const QVariantMap& valuemap) +void QmlListModel::append(const QScriptValue& valuemap) { + if (!valuemap.isObject()) { + qWarning("ListModel::append: value is not an object"); + return; + } if (!_root) _root = new ModelNode; ModelNode *mn = new ModelNode; - for (QVariantMap::const_iterator it=valuemap.begin(); it!=valuemap.end(); ++it) { - addRole(it.key()); - ModelNode *value = new ModelNode; - value->values << it.value(); - mn->properties.insert(it.key(),value); - } + mn->setObjectValue(valuemap); _root->values << qVariantFromValue(mn); emit itemsInserted(count()-1,1); } @@ -536,7 +569,7 @@ void QmlListModel::append(const QVariantMap& valuemap) \sa append() */ -void QmlListModel::set(int index, const QVariantMap& valuemap) +void QmlListModel::set(int index, const QScriptValue& valuemap) { if (!_root) _root = new ModelNode; @@ -548,12 +581,14 @@ void QmlListModel::set(int index, const QVariantMap& valuemap) else { ModelNode *node = qvariant_cast(_root->values.at(index)); QList roles; - for (QVariantMap::const_iterator it=valuemap.begin(); it!=valuemap.end(); ++it) { - node->setProperty(it.key(),it.value()); - int r = roleStrings.indexOf(it.key()); + node->setObjectValue(valuemap); + QScriptValueIterator it(valuemap); + while (it.hasNext()) { + it.next(); + int r = roleStrings.indexOf(it.name()); if (r<0) { r = roleStrings.count(); - roleStrings << it.key(); + roleStrings << it.name(); } roles.append(r); } diff --git a/src/declarative/util/qmllistmodel.h b/src/declarative/util/qmllistmodel.h index 8bef347..7bb94cf 100644 --- a/src/declarative/util/qmllistmodel.h +++ b/src/declarative/util/qmllistmodel.h @@ -50,6 +50,8 @@ #include #include #include +#include + QT_BEGIN_HEADER @@ -74,9 +76,9 @@ public: Q_INVOKABLE void clear(); Q_INVOKABLE void remove(int index); - Q_INVOKABLE void append(const QVariantMap& valuemap); - Q_INVOKABLE void insert(int index, const QVariantMap& valuemap); - Q_INVOKABLE void set(int index, const QVariantMap& valuemap); + Q_INVOKABLE void append(const QScriptValue&); + Q_INVOKABLE void insert(int index, const QScriptValue&); + Q_INVOKABLE void set(int index, const QScriptValue&); Q_INVOKABLE void set(int index, const QString& property, const QVariant& value); Q_INVOKABLE void move(int from, int to, int count); -- cgit v0.12 From ed8e89eaddca1c508151ef5913f0d383d3071b99 Mon Sep 17 00:00:00 2001 From: Warwick Allison Date: Wed, 9 Sep 2009 14:43:26 +1000 Subject: Fix destruction of ModelNodes (fix crash-on-exit). --- src/declarative/util/qmllistmodel.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/declarative/util/qmllistmodel.cpp b/src/declarative/util/qmllistmodel.cpp index 82942f6..8e5ea9c 100644 --- a/src/declarative/util/qmllistmodel.cpp +++ b/src/declarative/util/qmllistmodel.cpp @@ -200,7 +200,7 @@ class ModelObject : public QObject { Q_OBJECT public: - ModelObject(ModelNode *); + ModelObject(); void setValue(const QByteArray &name, const QVariant &val) { @@ -208,8 +208,6 @@ public: } private: - ModelNode *_node; - bool _haveProperties; QmlOpenMetaObject *_mo; }; @@ -231,7 +229,7 @@ struct ModelNode ModelObject *object() { if (!objectCache) { - objectCache = new ModelObject(this); + objectCache = new ModelObject(); QHash::iterator it; for (it = properties.begin(); it != properties.end(); ++it) { if (!(*it)->values.isEmpty()) @@ -294,8 +292,8 @@ struct ModelNode ModelObject *objectCache; }; -ModelObject::ModelObject(ModelNode *node) -: _node(node), _haveProperties(false), _mo(new QmlOpenMetaObject(this)) +ModelObject::ModelObject() +: _mo(new QmlOpenMetaObject(this)) { } @@ -841,7 +839,8 @@ ModelNode::~ModelNode() ModelNode *node = qvariant_cast(values.at(ii)); if (node) { delete node; node = 0; } } - if (modelCache) { delete modelCache; modelCache = 0; } + if (modelCache) { modelCache->_root = 0/* ==this */; delete modelCache; modelCache = 0; } + if (objectCache) { delete objectCache; } } QT_END_NAMESPACE -- cgit v0.12 From ccccc25802bdd0af806c3a1aaa83759b9e6706de Mon Sep 17 00:00:00 2001 From: Warwick Allison Date: Wed, 9 Sep 2009 15:46:47 +1000 Subject: clearer example --- examples/declarative/listview/dynamic.qml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/declarative/listview/dynamic.qml b/examples/declarative/listview/dynamic.qml index 5111cec..f615c24 100644 --- a/examples/declarative/listview/dynamic.qml +++ b/examples/declarative/listview/dynamic.qml @@ -1,7 +1,7 @@ import Qt 4.6 Item { - width: 300 + width: 320 height: 500 ListModel { @@ -114,15 +114,23 @@ Item { MouseRegion { anchors.fill: parent; onClicked: { FruitModel.append({ - "name":"Pizza", + "name":"Pizza Margarita", "cost":5.95, - "attributes":[{"description": "Supreme"},{"description": "Margarita"}] + "attributes":[{"description": "Cheese"},{"description": "Tomato"}] }) } } } Image { source: "content/pics/add.png" - MouseRegion { anchors.fill: parent; onClicked: FruitModel.insert(0,{"name":"Pizza", "cost":5.95}) } + MouseRegion { anchors.fill: parent; + onClicked: { + FruitModel.insert(0,{ + "name":"Pizza Supreme", + "cost":9.95, + "attributes":[{"description": "Cheese"},{"description": "Tomato"},{"description": "The Works"}] + }) + } + } } Image { source: "content/pics/trash.png" MouseRegion { anchors.fill: parent; onClicked: FruitModel.clear() } -- cgit v0.12 From 4c0abed6af10a2722ad3f1fd352caa3fe673468e Mon Sep 17 00:00:00 2001 From: Warwick Allison Date: Wed, 9 Sep 2009 15:47:08 +1000 Subject: Don't allow sparse-setting, since not efficient anyway. --- src/declarative/util/qmllistmodel.cpp | 38 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/declarative/util/qmllistmodel.cpp b/src/declarative/util/qmllistmodel.cpp index 8e5ea9c..f988d81 100644 --- a/src/declarative/util/qmllistmodel.cpp +++ b/src/declarative/util/qmllistmodel.cpp @@ -461,8 +461,8 @@ void QmlListModel::remove(int index) FruitModel.insert(2, {"cost": 5.95, "name":"Pizza"}) \endcode - If \a index is not in the list, sufficient empty items are - added to the list. + The \a index must be to an existing item in the list, or one past + the end of the list (equivalent to append). \sa set() append() */ @@ -471,7 +471,8 @@ void QmlListModel::insert(int index, const QScriptValue& valuemap) if (!_root) _root = new ModelNode; if (index >= _root->values.count()) { - set(index,valuemap); + if (index == _root->values.count()) + append(valuemap); return; } ModelNode *mn = new ModelNode; @@ -562,8 +563,7 @@ void QmlListModel::append(const QScriptValue& valuemap) FruitModel.set(3, {"cost": 5.95, "name":"Pizza"}) \endcode - If \a index is not in the list, sufficient empty items are - added to the list. + The \a index must be an element in the list. \sa append() */ @@ -571,9 +571,10 @@ void QmlListModel::set(int index, const QScriptValue& valuemap) { if (!_root) _root = new ModelNode; - int initialcount = _root->values.count(); - while (index > _root->values.count()) - _root->values.append(qVariantFromValue(new ModelNode)); + if ( index >= _root->values.count()) { + qWarning() << "ListModel::set index out of range:" << index; + return; + } if (index == _root->values.count()) append(valuemap); else { @@ -590,11 +591,7 @@ void QmlListModel::set(int index, const QScriptValue& valuemap) } roles.append(r); } - if (initialcount < index) { - emit itemsInserted(initialcount,index-initialcount+1); - } else { - emit itemsChanged(index,1,roles); - } + emit itemsChanged(index,1,roles); } } @@ -607,8 +604,7 @@ void QmlListModel::set(int index, const QScriptValue& valuemap) FruitModel.set(3, "cost", 5.95) \endcode - If \a index is not in the list, sufficient empty items are - added to the list. + The \a index must be an element in the list. \sa append() */ @@ -616,9 +612,10 @@ void QmlListModel::set(int index, const QString& property, const QVariant& value { if (!_root) _root = new ModelNode; - int initialcount = _root->values.count(); - while (index >= _root->values.count()) - _root->values.append(qVariantFromValue(new ModelNode)); + if ( index >= _root->values.count()) { + qWarning() << "ListModel::set index out of range:" << index; + return; + } ModelNode *node = qvariant_cast(_root->values.at(index)); int r = roleStrings.indexOf(property); if (r<0) { @@ -630,10 +627,7 @@ void QmlListModel::set(int index, const QString& property, const QVariant& value if (node) node->setProperty(property,value); - if (initialcount < index) - emit itemsInserted(initialcount,index-initialcount+1); - else - emit itemsChanged(index,1,roles); + emit itemsChanged(index,1,roles); } -- cgit v0.12 From 25bed69b3c643943dac195227d3f3fadc441516c Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Wed, 9 Sep 2009 17:48:13 +1000 Subject: Autotest for property aliases --- tests/auto/declarative/qmlparser/Alias.qml | 8 +++ tests/auto/declarative/qmlparser/alias.1.qml | 8 +++ tests/auto/declarative/qmlparser/alias.2.qml | 8 +++ tests/auto/declarative/qmlparser/alias.3.qml | 10 +++ tests/auto/declarative/qmlparser/testtypes.h | 8 ++- tests/auto/declarative/qmlparser/tst_qmlparser.cpp | 71 ++++++++++++++++++++++ 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 tests/auto/declarative/qmlparser/Alias.qml create mode 100644 tests/auto/declarative/qmlparser/alias.1.qml create mode 100644 tests/auto/declarative/qmlparser/alias.2.qml create mode 100644 tests/auto/declarative/qmlparser/alias.3.qml diff --git a/tests/auto/declarative/qmlparser/Alias.qml b/tests/auto/declarative/qmlparser/Alias.qml new file mode 100644 index 0000000..8264e0d --- /dev/null +++ b/tests/auto/declarative/qmlparser/Alias.qml @@ -0,0 +1,8 @@ +import Qt 4.6 + +Object { + id: Root + property int value: 1892 + property alias aliasValue: Root.value +} + diff --git a/tests/auto/declarative/qmlparser/alias.1.qml b/tests/auto/declarative/qmlparser/alias.1.qml new file mode 100644 index 0000000..492d99a --- /dev/null +++ b/tests/auto/declarative/qmlparser/alias.1.qml @@ -0,0 +1,8 @@ +import Test 1.0 +import Qt 4.6 + +Object { + id: Root + property int value: 10 + property alias valueAlias: Root.value +} diff --git a/tests/auto/declarative/qmlparser/alias.2.qml b/tests/auto/declarative/qmlparser/alias.2.qml new file mode 100644 index 0000000..aa4d103 --- /dev/null +++ b/tests/auto/declarative/qmlparser/alias.2.qml @@ -0,0 +1,8 @@ +import Test 1.0 + +MyQmlObject { + id: Root + property alias aliasObject: Root.qmlobjectProperty + + qmlobjectProperty: MyQmlObject { value : 10 } +} diff --git a/tests/auto/declarative/qmlparser/alias.3.qml b/tests/auto/declarative/qmlparser/alias.3.qml new file mode 100644 index 0000000..e25fbae --- /dev/null +++ b/tests/auto/declarative/qmlparser/alias.3.qml @@ -0,0 +1,10 @@ +import Qt 4.6 + +Object { + property var other + other: Alias { id: MyAliasObject } + + property alias value: MyAliasObject.aliasValue + property alias value2: MyAliasObject.value +} + diff --git a/tests/auto/declarative/qmlparser/testtypes.h b/tests/auto/declarative/qmlparser/testtypes.h index 3b5d3ae..e3e9aae 100644 --- a/tests/auto/declarative/qmlparser/testtypes.h +++ b/tests/auto/declarative/qmlparser/testtypes.h @@ -59,9 +59,11 @@ class MyQmlObject : public QObject, public MyInterface, public QmlParserStatus Q_PROPERTY(MyInterface *interfaceProperty READ interface WRITE setInterface) Q_PROPERTY(int onLiteralSignal READ onLiteralSignal WRITE setOnLiteralSignal); Q_PROPERTY(MyCustomVariantType customType READ customType WRITE setCustomType); + Q_PROPERTY(MyQmlObject *qmlobjectProperty READ qmlobject WRITE setQmlobject) + Q_INTERFACES(MyInterface QmlParserStatus) public: - MyQmlObject() : m_value(-1), m_interface(0) { qRegisterMetaType("MyCustomVariantType"); } + MyQmlObject() : m_value(-1), m_interface(0), m_qmlobject(0) { qRegisterMetaType("MyCustomVariantType"); } int value() const { return m_value; } void setValue(int v) { m_value = v; } @@ -88,6 +90,9 @@ public: int onLiteralSignal() const { return m_value; } void setOnLiteralSignal(int v) { m_value = v; } + MyQmlObject *qmlobject() const { return m_qmlobject; } + void setQmlobject(MyQmlObject *o) { m_qmlobject = o; } + MyCustomVariantType customType() const { return m_custom; } void setCustomType(const MyCustomVariantType &v) { m_custom = v; } public slots: @@ -100,6 +105,7 @@ private: friend class tst_qmlparser; int m_value; MyInterface *m_interface; + MyQmlObject *m_qmlobject; MyCustomVariantType m_custom; }; QML_DECLARE_TYPE(MyQmlObject); diff --git a/tests/auto/declarative/qmlparser/tst_qmlparser.cpp b/tests/auto/declarative/qmlparser/tst_qmlparser.cpp index e3735e7..93666aa 100644 --- a/tests/auto/declarative/qmlparser/tst_qmlparser.cpp +++ b/tests/auto/declarative/qmlparser/tst_qmlparser.cpp @@ -48,6 +48,7 @@ private slots: void customVariantTypes(); void valueTypes(); void cppnamespace(); + void aliasProperties(); void importsBuiltin_data(); void importsBuiltin(); @@ -517,6 +518,76 @@ void tst_qmlparser::cppnamespace() delete object; } +void tst_qmlparser::aliasProperties() +{ + // Simple "int" alias + { + QmlComponent component(&engine, TEST_FILE("alias.1.qml")); + VERIFY_ERRORS(0); + QObject *object = component.create(); + QVERIFY(object != 0); + + // Read through alias + QCOMPARE(object->property("valueAlias").toInt(), 10); + object->setProperty("value", QVariant(13)); + QCOMPARE(object->property("valueAlias").toInt(), 13); + + // Write throught alias + object->setProperty("valueAlias", QVariant(19)); + QCOMPARE(object->property("valueAlias").toInt(), 19); + QCOMPARE(object->property("value").toInt(), 19); + + delete object; + } + + // Complex object alias + { + QmlComponent component(&engine, TEST_FILE("alias.2.qml")); + VERIFY_ERRORS(0); + QObject *object = component.create(); + QVERIFY(object != 0); + + // Read through alias + MyQmlObject *v = + qvariant_cast(object->property("aliasObject")); + QVERIFY(v != 0); + QCOMPARE(v->value(), 10); + + // Write through alias + MyQmlObject *v2 = new MyQmlObject(); + v2->setParent(object); + object->setProperty("aliasObject", qVariantFromValue(v2)); + MyQmlObject *v3 = + qvariant_cast(object->property("aliasObject")); + QVERIFY(v3 != 0); + QCOMPARE(v3, v2); + + delete object; + } + + // Nested aliases + { + QmlComponent component(&engine, TEST_FILE("alias.3.qml")); + VERIFY_ERRORS(0); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("value").toInt(), 1892); + QCOMPARE(object->property("value2").toInt(), 1892); + + object->setProperty("value", QVariant(1313)); + QCOMPARE(object->property("value").toInt(), 1313); + QCOMPARE(object->property("value2").toInt(), 1313); + + object->setProperty("value2", QVariant(8080)); + QCOMPARE(object->property("value").toInt(), 8080); + QCOMPARE(object->property("value2").toInt(), 8080); + + delete object; + } + +} + class TestType : public QObject { Q_OBJECT public: -- cgit v0.12