Support JS objects in ListModel via QVariantMap
authorGlenn Watson <glenn.watson@nokia.com>
Fri, 11 Nov 2011 00:47:33 +0000 (10:47 +1000)
committerQt by Nokia <qt-info@nokia.com>
Fri, 11 Nov 2011 07:20:53 +0000 (08:20 +0100)
Added support for the new listmodel implementation to store and
retrieve JS objects via QVariantMap. Storing JS objects in a
listmodel is significantly slower than storing native datatypes
at the moment (this may be improved in the future). Also note
that it's not currently possible to bind to fields within the JS
object.

Change-Id: I3b1a11ace7cdec754c1a2bb2b2d1b7edf561864d
Reviewed-by: Martin Jones <martin.jones@nokia.com>

src/declarative/util/qdeclarativelistmodel.cpp
src/declarative/util/qdeclarativelistmodel_p_p.h
tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp

index 2cc52fb..dceb004 100644 (file)
@@ -64,6 +64,17 @@ enum { MIN_LISTMODEL_UID = 1024 };
 
 QAtomicInt ListModel::uidCounter(MIN_LISTMODEL_UID);
 
+template <typename T>
+static bool isMemoryUsed(const char *mem)
+{
+    for (size_t i=0 ; i < sizeof(T) ; ++i) {
+        if (mem[i] != 0)
+            return true;
+    }
+
+    return false;
+}
+
 static QString roleTypeName(ListLayout::Role::DataType t)
 {
     QString result;
@@ -108,8 +119,8 @@ const ListLayout::Role &ListLayout::getRoleOrCreate(v8::Handle<v8::String> key,
 
 const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
 {
-    const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QDeclarativeGuard<QObject>) };
-    const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *) };
+    const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QDeclarativeGuard<QObject>), sizeof(QVariantMap) };
+    const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap) };
 
     Role *r = new Role;
     r->name = key;
@@ -401,7 +412,7 @@ ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &
     return e->getListProperty(role);
 }
 
-void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles)
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles, QV8Engine *eng)
 {
     ListElement *e = elements[elementIndex];
 
@@ -434,7 +445,7 @@ void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int>
             int arrayLength = subArray->Length();
             for (int j=0 ; j < arrayLength ; ++j) {
                 v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
-                subModel->append(subObject);
+                subModel->append(subObject, eng);
             }
 
             roleIndex = e->setListProperty(r, subModel);
@@ -448,6 +459,10 @@ void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int>
                 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
                 if (role.type == ListLayout::Role::QObject)
                     e->setQObjectProperty(role, o);
+            } else {
+                const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
+                if (role.type == ListLayout::Role::VariantMap)
+                    e->setVariantMapProperty(role, propertyValue->ToObject(), eng);
             }
         } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
             const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
@@ -464,7 +479,7 @@ void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QList<int>
     }
 }
 
-void ListModel::set(int elementIndex, v8::Handle<v8::Object> object)
+void ListModel::set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
 {
     ListElement *e = elements[elementIndex];
 
@@ -499,7 +514,7 @@ void ListModel::set(int elementIndex, v8::Handle<v8::Object> object)
                 int arrayLength = subArray->Length();
                 for (int j=0 ; j < arrayLength ; ++j) {
                     v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
-                    subModel->append(subObject);
+                    subModel->append(subObject, eng);
                 }
 
                 e->setListPropertyFast(r, subModel);
@@ -516,6 +531,10 @@ void ListModel::set(int elementIndex, v8::Handle<v8::Object> object)
                 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
                 if (r.type == ListLayout::Role::QObject)
                     e->setQObjectPropertyFast(r, o);
+            } else {
+                const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
+                if (role.type == ListLayout::Role::VariantMap)
+                    e->setVariantMapFast(role, propertyValue->ToObject(), eng);
             }
         } else if (propertyValue.IsEmpty() || propertyValue->IsUndefined() || propertyValue->IsNull()) {
             const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
@@ -543,16 +562,16 @@ void ListModel::remove(int index)
     updateCacheIndices();
 }
 
-void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object)
+void ListModel::insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng)
 {
     insertElement(elementIndex);
-    set(elementIndex, object);
+    set(elementIndex, object, eng);
 }
 
-int ListModel::append(v8::Handle<v8::Object> object)
+int ListModel::append(v8::Handle<v8::Object> object, QV8Engine *eng)
 {
     int elementIndex = appendElement();
-    set(elementIndex, object);
+    set(elementIndex, object, eng);
     return elementIndex;
 }
 
@@ -577,7 +596,7 @@ int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const Q
     return roleIndex;
 }
 
-int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data)
+int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng)
 {
     int roleIndex = -1;
 
@@ -585,7 +604,7 @@ int ListModel::setExistingProperty(int elementIndex, const QString &key, v8::Han
         ListElement *e = elements[elementIndex];
         const ListLayout::Role *r = m_layout->getExistingRole(key);
         if (r)
-            roleIndex = e->setJsProperty(*r, data);
+            roleIndex = e->setJsProperty(*r, data, eng);
     }
 
     return roleIndex;
@@ -622,6 +641,17 @@ QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
     return o->data();
 }
 
+QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
+{
+    QVariantMap *map = 0;
+
+    char *mem = getPropertyMemory(role);
+    if (isMemoryUsed<QVariantMap>(mem))
+        map = reinterpret_cast<QVariantMap *>(mem);
+
+    return map;
+}
+
 QDeclarativeGuard<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
 {
     char *mem = getPropertyMemory(role);
@@ -699,6 +729,14 @@ QVariant ListElement::getProperty(const ListLayout::Role &role, const QDeclarati
                     data = QVariant::fromValue(object);
             }
             break;
+        case ListLayout::Role::VariantMap:
+            {
+                if (isMemoryUsed<QVariantMap>(mem)) {
+                    QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
+                    data = *map;
+                }
+            }
+            break;
         default:
             break;
     }
@@ -807,6 +845,43 @@ int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
     return roleIndex;
 }
 
+int ListElement::setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
+{
+    int roleIndex = -1;
+
+    if (role.type == ListLayout::Role::VariantMap) {
+        char *mem = getPropertyMemory(role);
+        if (isMemoryUsed<QVariantMap>(mem)) {
+            QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
+            map->~QMap();
+        }
+        new (mem) QVariantMap(eng->variantMapFromJS(o));
+        roleIndex = role.index;
+    }
+
+    return roleIndex;
+}
+
+int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
+{
+    int roleIndex = -1;
+
+    if (role.type == ListLayout::Role::VariantMap) {
+        char *mem = getPropertyMemory(role);
+        if (isMemoryUsed<QVariantMap>(mem)) {
+            QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
+            map->~QMap();
+        }
+        if (m)
+            new (mem) QVariantMap(*m);
+        else
+            new (mem) QVariantMap;
+        roleIndex = role.index;
+    }
+
+    return roleIndex;
+}
+
 void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
 {
     char *mem = getPropertyMemory(role);
@@ -840,6 +915,13 @@ void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m
     *value = m;
 }
 
+void ListElement::setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng)
+{
+    char *mem = getPropertyMemory(role);
+    QVariantMap *map = new (mem) QVariantMap;
+    *map = eng->variantMapFromJS(o);
+}
+
 void ListElement::clearProperty(const ListLayout::Role &role)
 {
     switch (role.type) {
@@ -858,6 +940,9 @@ void ListElement::clearProperty(const ListLayout::Role &role)
     case ListLayout::Role::QObject:
         setQObjectProperty(role, 0);
         break;
+    case ListLayout::Role::VariantMap:
+        setVariantMapProperty(role, 0);
+        break;
     default:
         break;
     }
@@ -918,6 +1003,12 @@ void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *tar
                     QVariant v = src->getProperty(srcRole, 0, 0);
                     target->setVariantProperty(targetRole, v);
                 }
+            case ListLayout::Role::VariantMap:
+                {
+                    QVariantMap *map = src->getVariantMapProperty(srcRole);
+                    target->setVariantMapProperty(targetRole, map);
+                }
+                break;
             default:
                 break;
         }
@@ -955,6 +1046,13 @@ void ListElement::destroy(ListLayout *layout)
                             guard->~QDeclarativeGuard();
                     }
                     break;
+                case ListLayout::Role::VariantMap:
+                    {
+                        QVariantMap *map = getVariantMapProperty(r);
+                        if (map)
+                            map->~QMap();
+                    }
+                    break;
                 default:
                     // other types don't need explicit cleanup.
                     break;
@@ -993,7 +1091,7 @@ int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant
     return roleIndex;
 }
 
-int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d)
+int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng)
 {
     // Check if this key exists yet
     int roleIndex = -1;
@@ -1013,7 +1111,7 @@ int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Valu
         int arrayLength = subArray->Length();
         for (int j=0 ; j < arrayLength ; ++j) {
             v8::Handle<v8::Object> subObject = subArray->Get(j)->ToObject();
-            subModel->append(subObject);
+            subModel->append(subObject, eng);
         }
         roleIndex = setListProperty(role, subModel);
     } else if (d->IsBoolean()) {
@@ -1023,6 +1121,8 @@ int ListElement::setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Valu
         if (role.type == ListLayout::Role::QObject && r && r->resourceType() == QV8ObjectResource::QObjectType) {
             QObject *o = QV8QObjectWrapper::toQObject(r);
             roleIndex = setQObjectProperty(role, o);
+        } else if (role.type == ListLayout::Role::VariantMap) {
+            roleIndex = setVariantMapProperty(role, d->ToObject(), eng);
         }
     } else if (d.IsEmpty() || d->IsUndefined() || d->IsNull()) {
         clearProperty(role);
@@ -1085,7 +1185,7 @@ void ModelNodeMetaObject::propertyWritten(int index)
 
     v8::Handle<v8::Value> v = eng->fromVariant(value);
 
-    int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v);
+    int roleIndex = m_obj->m_model->m_listModel->setExistingProperty(m_obj->m_elementIndex, propName, v, eng);
     if (roleIndex != -1) {
         QList<int> roles;
         roles << roleIndex;
@@ -1408,13 +1508,13 @@ void QDeclarativeListModel::insert(QDeclarativeV8Function *args)
             int objectArrayLength = objectArray->Length();
             for (int i=0 ; i < objectArrayLength ; ++i) {
                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
-                m_listModel->insert(index+i, argObject);
+                m_listModel->insert(index+i, argObject, args->engine());
             }
             emitItemsInserted(index, objectArrayLength);
         } else if (arg1->IsObject()) {
             v8::Handle<v8::Object> argObject = arg1->ToObject();
 
-            m_listModel->insert(index, argObject);
+            m_listModel->insert(index, argObject, args->engine());
             emitItemsInserted(index, 1);
         } else {
             qmlInfo(this) << tr("insert: value is not an object");
@@ -1474,13 +1574,13 @@ void QDeclarativeListModel::append(QDeclarativeV8Function *args)
             int index = m_listModel->elementCount();
             for (int i=0 ; i < objectArrayLength ; ++i) {
                 v8::Handle<v8::Object> argObject = objectArray->Get(i)->ToObject();
-                m_listModel->append(argObject);
+                m_listModel->append(argObject, args->engine());
             }
             emitItemsInserted(index, objectArrayLength);
         } else if (arg->IsObject()) {
             v8::Handle<v8::Object> argObject = arg->ToObject();
 
-            int index = m_listModel->append(argObject);
+            int index = m_listModel->append(argObject, args->engine());
             emitItemsInserted(index, 1);
 
         } else {
@@ -1568,12 +1668,12 @@ void QDeclarativeListModel::set(int index, const QDeclarativeV8Handle &handle)
     v8::Handle<v8::Object> object = valuemap->ToObject();
 
     if (index == count()) {
-        m_listModel->insert(index, object);
+        m_listModel->insert(index, object, engine());
         emitItemsInserted(index, 1);
     } else {
 
         QList<int> roles;
-        m_listModel->set(index, object, &roles);
+        m_listModel->set(index, object, &roles, engine());
 
         if (roles.count())
             emitItemsChanged(index, 1, roles);
index 24e8724..89e8e5e 100644 (file)
@@ -139,6 +139,7 @@ public:
             Bool,
             List,
             QObject,
+            VariantMap,
 
             MaxDataType
         };
@@ -193,19 +194,22 @@ private:
 
     int setVariantProperty(const ListLayout::Role &role, const QVariant &d);
 
-    int setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d);
+    int setJsProperty(const ListLayout::Role &role, v8::Handle<v8::Value> d, QV8Engine *eng);
 
     int setStringProperty(const ListLayout::Role &role, const QString &s);
     int setDoubleProperty(const ListLayout::Role &role, double n);
     int setBoolProperty(const ListLayout::Role &role, bool b);
     int setListProperty(const ListLayout::Role &role, ListModel *m);
     int setQObjectProperty(const ListLayout::Role &role, QObject *o);
+    int setVariantMapProperty(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng);
+    int setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m);
 
     void setStringPropertyFast(const ListLayout::Role &role, const QString &s);
     void setDoublePropertyFast(const ListLayout::Role &role, double n);
     void setBoolPropertyFast(const ListLayout::Role &role, bool b);
     void setQObjectPropertyFast(const ListLayout::Role &role, QObject *o);
     void setListPropertyFast(const ListLayout::Role &role, ListModel *m);
+    void setVariantMapFast(const ListLayout::Role &role, v8::Handle<v8::Object> o, QV8Engine *eng);
 
     void clearProperty(const ListLayout::Role &role);
 
@@ -214,6 +218,7 @@ private:
     QString *getStringProperty(const ListLayout::Role &role);
     QObject *getQObjectProperty(const ListLayout::Role &role);
     QDeclarativeGuard<QObject> *getGuardProperty(const ListLayout::Role &role);
+    QVariantMap *getVariantMapProperty(const ListLayout::Role &role);
 
     inline char *getPropertyMemory(const ListLayout::Role &role);
 
@@ -238,7 +243,7 @@ public:
     void destroy();
 
     int setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data);
-    int setExistingProperty(int uid, const QString &key, v8::Handle<v8::Value> data);
+    int setExistingProperty(int uid, const QString &key, v8::Handle<v8::Value> data, QV8Engine *eng);
 
     QVariant getProperty(int elementIndex, int roleIndex, const QDeclarativeListModel *owner, QV8Engine *eng);
     ListModel *getListProperty(int elementIndex, const ListLayout::Role &role);
@@ -263,11 +268,11 @@ public:
         return elements.count();
     }
 
-    void set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles);
-    void set(int elementIndex, v8::Handle<v8::Object> object);
+    void set(int elementIndex, v8::Handle<v8::Object> object, QList<int> *roles, QV8Engine *eng);
+    void set(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng);
 
-    int append(v8::Handle<v8::Object> object);
-    void insert(int elementIndex, v8::Handle<v8::Object> object);
+    int append(v8::Handle<v8::Object> object, QV8Engine *eng);
+    void insert(int elementIndex, v8::Handle<v8::Object> object, QV8Engine *eng);
 
     void clear();
     void remove(int index);
index 6c26581..e5e1209 100644 (file)
@@ -510,6 +510,16 @@ void tst_qdeclarativelistmodel::dynamic_data()
     QTest::newRow("qobject2") << "{append({'a':dummyItem0});get(0).a == dummyItem0;}" << 1 << "";
     QTest::newRow("qobject3") << "{append({'a':dummyItem0});append({'b':1});}" << 0 << "";
 
+    // JS objects
+    QTest::newRow("js1") << "{append({'foo':{'prop':1}});count}" << 1 << "";
+    QTest::newRow("js2") << "{append({'foo':{'prop':27}});get(0).foo.prop}" << 27 << "";
+    QTest::newRow("js3") << "{append({'foo':{'prop':27}});append({'bar':1});count}" << 2 << "";
+    QTest::newRow("js4") << "{append({'foo':{'prop':27}});append({'bar':1});set(0, {'foo':{'prop':28}});get(0).foo.prop}" << 28 << "";
+    QTest::newRow("js5") << "{append({'foo':{'prop':27}});append({'bar':1});set(1, {'foo':{'prop':33}});get(1).foo.prop}" << 33 << "";
+    QTest::newRow("js6") << "{append({'foo':{'prop':27}});clear();count}" << 0 << "";
+    QTest::newRow("js7") << "{append({'foo':{'prop':27}});set(0, {'foo':null});count}" << 1 << "";
+    QTest::newRow("js8") << "{append({'foo':{'prop':27}});set(0, {'foo':{'prop2':31}});get(0).foo.prop2}" << 31 << "";
+
     // Nested models
     QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
     QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";