summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/declarative/util/qdeclarativelistmodel.cpp293
-rw-r--r--src/declarative/util/qdeclarativelistmodel_p.h6
-rw-r--r--src/declarative/util/qdeclarativelistmodel_p_p.h121
-rw-r--r--src/declarative/util/qdeclarativelistmodelworkeragent.cpp25
-rw-r--r--src/declarative/util/qdeclarativelistmodelworkeragent_p.h4
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp307
6 files changed, 698 insertions, 58 deletions
diff --git a/src/declarative/util/qdeclarativelistmodel.cpp b/src/declarative/util/qdeclarativelistmodel.cpp
index 0be9122..68e97b0 100644
--- a/src/declarative/util/qdeclarativelistmodel.cpp
+++ b/src/declarative/util/qdeclarativelistmodel.cpp
@@ -231,6 +231,10 @@ QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *orig,
m_flat->m_roles = orig->m_flat->m_roles;
m_flat->m_strings = orig->m_flat->m_strings;
m_flat->m_values = orig->m_flat->m_values;
+
+ m_flat->m_nodeData.reserve(m_flat->m_values.count());
+ for (int i=0; i<m_flat->m_values.count(); i++)
+ m_flat->m_nodeData << 0;
}
}
@@ -267,6 +271,10 @@ bool QDeclarativeListModel::flatten()
flat->m_strings.insert(s, roles[i]);
}
+ flat->m_nodeData.reserve(flat->m_values.count());
+ for (int i=0; i<flat->m_values.count(); i++)
+ flat->m_nodeData << 0;
+
m_flat = flat;
delete m_nested;
m_nested = 0;
@@ -425,7 +433,7 @@ void QDeclarativeListModel::move(int from, int to, int n)
{
if (n==0 || from==to)
return;
- if (from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0) {
+ if (!canMove(from, to, n)) {
qmlInfo(this) << tr("move: out of range");
return;
}
@@ -476,11 +484,15 @@ void QDeclarativeListModel::append(const QScriptValue& valuemap)
/*!
\qmlmethod object ListModel::get(int index)
- Returns the item at \a index in the list model.
+ Returns the item at \a index in the list model. This allows the item
+ data to be accessed or modified from JavaScript:
\code
- fruitModel.append({"cost": 5.95, "name":"Jackfruit"})
- fruitModel.get(0).cost
+ Component.onCompleted: {
+ fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
+ console.log(fruitModel.get(0).cost);
+ fruitModel.get(0).cost = 10.95;
+ }
\endcode
The \a index must be an element in the list.
@@ -495,6 +507,9 @@ void QDeclarativeListModel::append(const QScriptValue& valuemap)
fruitModel.get(0).attributes.get(1).value; // == "green"
\endcode
+ \warning The returned object is not guaranteed to remain valid. It
+ should not be used in \l{Property Binding}{property bindings}.
+
\sa append()
*/
QScriptValue QDeclarativeListModel::get(int index) const
@@ -736,7 +751,7 @@ void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &
{
QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
- ModelNode *root = new ModelNode;
+ ModelNode *root = new ModelNode(rv->m_nested);
rv->m_nested->_root = root;
QStack<ModelNode *> nodes;
nodes << root;
@@ -753,7 +768,7 @@ void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &
case ListInstruction::Push:
{
ModelNode *n = nodes.top();
- ModelNode *n2 = new ModelNode;
+ ModelNode *n2 = new ModelNode(rv->m_nested);
n->values << qVariantFromValue(n2);
nodes.push(n2);
if (processingSet)
@@ -792,7 +807,7 @@ void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &
case ListInstruction::Set:
{
ModelNode *n = nodes.top();
- ModelNode *n2 = new ModelNode;
+ ModelNode *n2 = new ModelNode(rv->m_nested);
n->properties.insert(QString::fromUtf8(data + instr.dataIdx), n2);
nodes.push(n2);
processingSet = true;
@@ -800,6 +815,13 @@ void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &
break;
}
}
+
+ ModelNode *rootNode = rv->m_nested->_root;
+ for (int i=0; i<rootNode->values.count(); ++i) {
+ ModelNode *node = qvariant_cast<ModelNode *>(rootNode->values[i]);
+ node->listIndex = i;
+ node->updateListIndexes();
+ }
}
bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
@@ -814,6 +836,7 @@ bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
return false;
}
+
/*!
\qmlclass ListElement QDeclarativeListElement
\ingroup qml-working-with-data
@@ -857,12 +880,13 @@ bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
*/
FlatListModel::FlatListModel(QDeclarativeListModel *base)
- : m_scriptEngine(0), m_listModel(base), m_parentAgent(0)
+ : m_scriptEngine(0), m_listModel(base), m_scriptClass(0), m_parentAgent(0)
{
}
FlatListModel::~FlatListModel()
{
+ qDeleteAll(m_nodeData);
}
QHash<int,QVariant> FlatListModel::data(int index, const QList<int> &roles) const
@@ -906,11 +930,15 @@ int FlatListModel::count() const
void FlatListModel::clear()
{
m_values.clear();
+
+ qDeleteAll(m_nodeData);
+ m_nodeData.clear();
}
void FlatListModel::remove(int index)
{
m_values.removeAt(index);
+ removedNode(index);
}
bool FlatListModel::append(const QScriptValue &value)
@@ -927,6 +955,8 @@ bool FlatListModel::insert(int index, const QScriptValue &value)
return false;
m_values.insert(index, row);
+ insertedNode(index);
+
return true;
}
@@ -940,13 +970,17 @@ QScriptValue FlatListModel::get(int index) const
if (index < 0 || index >= m_values.count())
return scriptEngine->undefinedValue();
- QScriptValue rv = scriptEngine->newObject();
+ FlatListModel *that = const_cast<FlatListModel*>(this);
+ if (!m_scriptClass)
+ that->m_scriptClass = new FlatListScriptClass(that, scriptEngine);
- QHash<int, QVariant> row = m_values.at(index);
- for (QHash<int, QVariant>::ConstIterator iter = row.begin(); iter != row.end(); ++iter)
- rv.setProperty(m_roles.value(iter.key()), qScriptValueFromValue(scriptEngine, iter.value()));
+ FlatNodeData *data = m_nodeData.value(index);
+ if (!data) {
+ data = new FlatNodeData(index);
+ that->m_nodeData.replace(index, data);
+ }
- return rv;
+ return QScriptDeclarativeClass::newObject(scriptEngine, m_scriptClass, new FlatNodeObjectData(data));
}
void FlatListModel::set(int index, const QScriptValue &value, QList<int> *roles)
@@ -979,6 +1013,7 @@ void FlatListModel::setProperty(int index, const QString& property, const QVaria
void FlatListModel::move(int from, int to, int n)
{
qdeclarativelistmodel_move<QList<QHash<int, QVariant> > >(from, to, n, &m_values);
+ moveNodes(from, to, n);
}
bool FlatListModel::addValue(const QScriptValue &value, QHash<int, QVariant> *row, QList<int> *roles)
@@ -1008,6 +1043,139 @@ bool FlatListModel::addValue(const QScriptValue &value, QHash<int, QVariant> *ro
return true;
}
+void FlatListModel::insertedNode(int index)
+{
+ if (index >= 0 && index <= m_values.count()) {
+ m_nodeData.insert(index, 0);
+
+ for (int i=index + 1; i<m_nodeData.count(); i++) {
+ if (m_nodeData[i])
+ m_nodeData[i]->index = i;
+ }
+ }
+}
+
+void FlatListModel::removedNode(int index)
+{
+ if (index >= 0 && index < m_nodeData.count()) {
+ delete m_nodeData.takeAt(index);
+
+ for (int i=index; i<m_nodeData.count(); i++) {
+ if (m_nodeData[i])
+ m_nodeData[i]->index = i;
+ }
+ }
+}
+
+void FlatListModel::moveNodes(int from, int to, int n)
+{
+ if (!m_listModel->canMove(from, to, n))
+ return;
+
+ qdeclarativelistmodel_move<QList<FlatNodeData *> >(from, to, n, &m_nodeData);
+
+ for (int i=from; i<from + (to-from); i++) {
+ if (m_nodeData[i])
+ m_nodeData[i]->index = i;
+ }
+}
+
+
+
+FlatNodeData::~FlatNodeData()
+{
+ for (QSet<FlatNodeObjectData *>::Iterator iter = objects.begin(); iter != objects.end(); ++iter) {
+ FlatNodeObjectData *data = *iter;
+ data->nodeData = 0;
+ }
+}
+
+void FlatNodeData::addData(FlatNodeObjectData *data)
+{
+ objects.insert(data);
+}
+
+void FlatNodeData::removeData(FlatNodeObjectData *data)
+{
+ objects.remove(data);
+}
+
+
+FlatListScriptClass::FlatListScriptClass(FlatListModel *model, QScriptEngine *seng)
+ : QScriptDeclarativeClass(seng),
+ m_model(model)
+{
+}
+
+QScriptDeclarativeClass::Value FlatListScriptClass::property(Object *obj, const Identifier &name)
+{
+ FlatNodeObjectData *objData = static_cast<FlatNodeObjectData*>(obj);
+ if (!objData->nodeData) // item at this index has been deleted
+ return QScriptDeclarativeClass::Value(engine(), engine()->undefinedValue());
+
+ int index = objData->nodeData->index;
+ QString propName = toString(name);
+ int role = m_model->m_strings.value(propName, -1);
+
+ if (role >= 0 && index >=0 ) {
+ const QHash<int, QVariant> &row = m_model->m_values[index];
+ QScriptValue sv = engine()->toScriptValue<QVariant>(row[role]);
+ return QScriptDeclarativeClass::Value(engine(), sv);
+ }
+
+ return QScriptDeclarativeClass::Value(engine(), engine()->undefinedValue());
+}
+
+void FlatListScriptClass::setProperty(Object *obj, const Identifier &name, const QScriptValue &value)
+{
+ if (!value.isVariant() && !value.isRegExp() && !value.isDate() && value.isObject()) {
+ qmlInfo(m_model->m_listModel) << "Cannot add nested list values when modifying or after modification from a worker script";
+ return;
+ }
+
+ FlatNodeObjectData *objData = static_cast<FlatNodeObjectData*>(obj);
+ if (!objData->nodeData) // item at this index has been deleted
+ return;
+
+ int index = objData->nodeData->index;
+ QString propName = toString(name);
+
+ int role = m_model->m_strings.value(propName, -1);
+ if (role >= 0 && index >= 0) {
+ QHash<int, QVariant> &row = m_model->m_values[index];
+ row[role] = value.toVariant();
+
+ if (m_model->m_parentAgent) {
+ // This is the list in the worker thread, so tell the agent to
+ // emit itemsChanged() later
+ m_model->m_parentAgent->changedData(index, 1);
+ } else {
+ // This is the list in the main thread, so emit itemsChanged()
+ QList<int> roles;
+ roles << role;
+ emit m_model->m_listModel->itemsChanged(index, 1, roles);
+ }
+ }
+}
+
+QScriptClass::QueryFlags FlatListScriptClass::queryProperty(Object *, const Identifier &, QScriptClass::QueryFlags)
+{
+ return (QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess);
+}
+
+bool FlatListScriptClass::compare(Object *obj1, Object *obj2)
+{
+ FlatNodeObjectData *data1 = static_cast<FlatNodeObjectData*>(obj1);
+ FlatNodeObjectData *data2 = static_cast<FlatNodeObjectData*>(obj2);
+
+ if (!data1->nodeData || !data2->nodeData)
+ return false;
+
+ return data1->nodeData->index == data2->nodeData->index;
+}
+
+
+
NestedListModel::NestedListModel(QDeclarativeListModel *base)
: _root(0), m_ownsRoot(false), m_listModel(base), _rolesOk(false)
{
@@ -1133,11 +1301,12 @@ void NestedListModel::remove(int index)
bool NestedListModel::insert(int index, const QScriptValue& valuemap)
{
if (!_root) {
- _root = new ModelNode;
+ _root = new ModelNode(this);
m_ownsRoot = true;
}
- ModelNode *mn = new ModelNode;
+ ModelNode *mn = new ModelNode(this);
+ mn->listIndex = index;
mn->setObjectValue(valuemap);
_root->values.insert(index,qVariantFromValue(mn));
return true;
@@ -1153,12 +1322,11 @@ void NestedListModel::move(int from, int to, int n)
bool NestedListModel::append(const QScriptValue& valuemap)
{
if (!_root) {
- _root = new ModelNode;
+ _root = new ModelNode(this);
m_ownsRoot = true;
}
- ModelNode *mn = new ModelNode;
- mn->setObjectValue(valuemap);
- _root->values << qVariantFromValue(mn);
+
+ insert(count(), valuemap);
return true;
}
@@ -1253,8 +1421,8 @@ QString NestedListModel::toString(int role) const
}
-ModelNode::ModelNode()
-: modelCache(0), objectCache(0), isArray(false)
+ModelNode::ModelNode(NestedListModel *model)
+: modelCache(0), objectCache(0), isArray(false), m_model(model), listIndex(-1)
{
}
@@ -1278,18 +1446,18 @@ void ModelNode::clear()
properties.clear();
}
-void ModelNode::setObjectValue(const QScriptValue& valuemap) {
+void ModelNode::setObjectValue(const QScriptValue& valuemap, bool writeToCache) {
QScriptValueIterator it(valuemap);
while (it.hasNext()) {
it.next();
- ModelNode *value = new ModelNode;
+ ModelNode *value = new ModelNode(m_model);
QScriptValue v = it.value();
if (v.isArray()) {
value->isArray = true;
value->setListValue(v);
} else {
value->values << v.toVariant();
- if (objectCache)
+ if (writeToCache && objectCache)
objectCache->setValue(it.name().toUtf8(), value->values.last());
}
if (properties.contains(it.name()))
@@ -1302,14 +1470,16 @@ void ModelNode::setListValue(const QScriptValue& valuelist) {
values.clear();
int size = valuelist.property(QLatin1String("length")).toInt32();
for (int i=0; i<size; i++) {
- ModelNode *value = new ModelNode;
+ ModelNode *value = new ModelNode(m_model);
QScriptValue v = valuelist.property(i);
if (v.isArray()) {
value->isArray = true;
value->setListValue(v);
} else if (v.isObject()) {
+ value->listIndex = i;
value->setObjectValue(v);
} else {
+ value->listIndex = i;
value->values << v.toVariant();
}
values.append(qVariantFromValue(value));
@@ -1321,7 +1491,7 @@ void ModelNode::setProperty(const QString& prop, const QVariant& val) {
if (it != properties.end()) {
(*it)->values[0] = val;
} else {
- ModelNode *n = new ModelNode;
+ ModelNode *n = new ModelNode(m_model);
n->values << val;
properties.insert(prop,n);
}
@@ -1329,6 +1499,40 @@ void ModelNode::setProperty(const QString& prop, const QVariant& val) {
objectCache->setValue(prop.toUtf8(), val);
}
+void ModelNode::updateListIndexes()
+{
+ for (QHash<QString, ModelNode *>::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) {
+ ModelNode *node = iter.value();
+ if (node->isArray) {
+ for (int i=0; i<node->values.count(); ++i) {
+ ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(i));
+ if (subNode)
+ subNode->listIndex = i;
+ }
+ }
+ node->updateListIndexes();
+ }
+}
+
+/*
+ Need to call this to emit itemsChanged() for modifications outside of set()
+ and setProperty(), i.e. if an item returned from get() is modified
+*/
+void ModelNode::changedProperty(const QString &name) const
+{
+ if (listIndex < 0)
+ return;
+
+ m_model->checkRoles();
+ QList<int> roles;
+ int role = m_model->roleStrings.indexOf(name);
+ if (role < 0)
+ roles = m_model->roles();
+ else
+ roles << role;
+ emit m_model->m_listModel->itemsChanged(listIndex, 1, roles);
+}
+
void ModelNode::dump(ModelNode *node, int ind)
{
QByteArray indentBa(ind * 4, ' ');
@@ -1350,16 +1554,47 @@ void ModelNode::dump(ModelNode *node, int ind)
}
}
-ModelObject::ModelObject()
-: _mo(new QDeclarativeOpenMetaObject(this))
+ModelObject::ModelObject(ModelNode *node, NestedListModel *model, QScriptEngine *seng)
+ : m_model(model),
+ m_node(node),
+ m_meta(new ModelNodeMetaObject(seng, this))
{
}
void ModelObject::setValue(const QByteArray &name, const QVariant &val)
{
- _mo->setValue(name, val);
+ m_meta->setValue(name, val);
setProperty(name.constData(), val);
}
+void ModelObject::setNodeUpdatesEnabled(bool enable)
+{
+ m_meta->m_enabled = enable;
+}
+
+
+ModelNodeMetaObject::ModelNodeMetaObject(QScriptEngine *seng, ModelObject *object)
+ : QDeclarativeOpenMetaObject(object),
+ m_enabled(false),
+ m_seng(seng),
+ m_obj(object)
+{
+}
+
+void ModelNodeMetaObject::propertyWritten(int index)
+{
+ if (!m_enabled)
+ return;
+
+ QString propName = QString::fromUtf8(name(index));
+ QVariant value = operator[](index);
+
+ QScriptValue sv = m_seng->newObject();
+ sv.setProperty(propName, m_seng->newVariant(value));
+ m_obj->m_node->setObjectValue(sv, false);
+
+ m_obj->m_node->changedProperty(propName);
+}
+
QT_END_NAMESPACE
diff --git a/src/declarative/util/qdeclarativelistmodel_p.h b/src/declarative/util/qdeclarativelistmodel_p.h
index 0a5e3ff..fe42ef6 100644
--- a/src/declarative/util/qdeclarativelistmodel_p.h
+++ b/src/declarative/util/qdeclarativelistmodel_p.h
@@ -63,6 +63,7 @@ class FlatListModel;
class NestedListModel;
class QDeclarativeListModelWorkerAgent;
struct ModelNode;
+class FlatListScriptClass;
class Q_DECLARATIVE_EXPORT QDeclarativeListModel : public QListModelInterface
{
Q_OBJECT
@@ -96,15 +97,18 @@ Q_SIGNALS:
private:
friend class QDeclarativeListModelParser;
friend class QDeclarativeListModelWorkerAgent;
+ friend class FlatListModel;
+ friend class FlatListScriptClass;
friend struct ModelNode;
// Constructs a flat list model for a worker agent
QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent);
- QDeclarativeListModel(bool workerCopy, QObject *parent=0);
bool flatten();
bool inWorkerThread() const;
+ inline bool canMove(int from, int to, int n) const { return !(from+n > count() || to+n > count() || from < 0 || to < 0 || n < 0); }
+
QDeclarativeListModelWorkerAgent *m_agent;
NestedListModel *m_nested;
FlatListModel *m_flat;
diff --git a/src/declarative/util/qdeclarativelistmodel_p_p.h b/src/declarative/util/qdeclarativelistmodel_p_p.h
index 1e9513e..acf4f3e 100644
--- a/src/declarative/util/qdeclarativelistmodel_p_p.h
+++ b/src/declarative/util/qdeclarativelistmodel_p_p.h
@@ -54,9 +54,11 @@
//
#include "private/qdeclarativelistmodel_p.h"
-
-#include "qdeclarative.h"
#include "private/qdeclarativeengine_p.h"
+#include "private/qdeclarativeopenmetaobject_p.h"
+#include "qdeclarative.h"
+
+#include <private/qscriptdeclarativeclass_p.h>
QT_BEGIN_HEADER
@@ -68,6 +70,8 @@ class QDeclarativeOpenMetaObject;
class QScriptEngine;
class QDeclarativeListModelWorkerAgent;
struct ModelNode;
+class FlatListScriptClass;
+class FlatNodeData;
class FlatListModel
{
@@ -94,8 +98,13 @@ public:
private:
friend class QDeclarativeListModelWorkerAgent;
friend class QDeclarativeListModel;
+ friend class FlatListScriptClass;
+ friend class FlatNodeData;
bool addValue(const QScriptValue &value, QHash<int, QVariant> *row, QList<int> *roles);
+ void insertedNode(int index);
+ void removedNode(int index);
+ void moveNodes(int from, int to, int n);
QScriptEngine *m_scriptEngine;
QHash<int, QString> m_roles;
@@ -103,9 +112,68 @@ private:
QList<QHash<int, QVariant> > m_values;
QDeclarativeListModel *m_listModel;
+ FlatListScriptClass *m_scriptClass;
+ QList<FlatNodeData *> m_nodeData;
QDeclarativeListModelWorkerAgent *m_parentAgent;
};
+
+/*
+ Created when get() is called on a FlatListModel. This allows changes to the
+ object returned by get() to be tracked, and passed onto the model.
+*/
+class FlatListScriptClass : public QScriptDeclarativeClass
+{
+public:
+ FlatListScriptClass(FlatListModel *model, QScriptEngine *seng);
+
+ Value property(Object *, const Identifier &);
+ void setProperty(Object *, const Identifier &name, const QScriptValue &);
+ QScriptClass::QueryFlags queryProperty(Object *, const Identifier &, QScriptClass::QueryFlags flags);
+ bool compare(Object *, Object *);
+
+private:
+ FlatListModel *m_model;
+};
+
+/*
+ FlatNodeData and FlatNodeObjectData allow objects returned by get() to still
+ point to the correct list index if move(), insert() or remove() are called.
+*/
+struct FlatNodeObjectData;
+class FlatNodeData
+{
+public:
+ FlatNodeData(int i)
+ : index(i) {}
+
+ ~FlatNodeData();
+
+ void addData(FlatNodeObjectData *data);
+ void removeData(FlatNodeObjectData *data);
+
+ int index;
+
+private:
+ QSet<FlatNodeObjectData*> objects;
+};
+
+struct FlatNodeObjectData : public QScriptDeclarativeClass::Object
+{
+ FlatNodeObjectData(FlatNodeData *data) : nodeData(data) {
+ nodeData->addData(this);
+ }
+
+ ~FlatNodeObjectData() {
+ if (nodeData)
+ nodeData->removeData(this);
+ }
+
+ FlatNodeData *nodeData;
+};
+
+
+
class NestedListModel
{
public:
@@ -136,25 +204,50 @@ public:
QDeclarativeListModel *m_listModel;
private:
+ friend struct ModelNode;
mutable QStringList roleStrings;
mutable bool _rolesOk;
};
+class ModelNodeMetaObject;
class ModelObject : public QObject
{
Q_OBJECT
public:
- ModelObject();
+ ModelObject(ModelNode *node, NestedListModel *model, QScriptEngine *seng);
void setValue(const QByteArray &name, const QVariant &val);
+ void setNodeUpdatesEnabled(bool enable);
+
+ NestedListModel *m_model;
+ ModelNode *m_node;
private:
- QDeclarativeOpenMetaObject *_mo;
+ ModelNodeMetaObject *m_meta;
};
+class ModelNodeMetaObject : public QDeclarativeOpenMetaObject
+{
+public:
+ ModelNodeMetaObject(QScriptEngine *seng, ModelObject *object);
+
+ bool m_enabled;
+
+protected:
+ void propertyWritten(int index);
+
+private:
+ QScriptEngine *m_seng;
+ ModelObject *m_obj;
+};
+
+
+/*
+ A ModelNode is created for each item in a NestedListModel.
+*/
struct ModelNode
{
- ModelNode();
+ ModelNode(NestedListModel *model);
~ModelNode();
QList<QVariant> values;
@@ -167,30 +260,44 @@ struct ModelNode
modelCache = new QDeclarativeListModel;
QDeclarativeEngine::setContextForObject(modelCache,QDeclarativeEngine::contextForObject(model->m_listModel));
modelCache->m_nested->_root = this; // ListModel defaults to nestable model
+
+ for (int i=0; i<values.count(); ++i) {
+ ModelNode *subNode = qvariant_cast<ModelNode *>(values.at(i));
+ if (subNode)
+ subNode->m_model = modelCache->m_nested;
+ }
}
return modelCache;
}
ModelObject *object(const NestedListModel *model) {
if (!objectCache) {
- objectCache = new ModelObject();
+ objectCache = new ModelObject(this,
+ const_cast<NestedListModel*>(model),
+ QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(model->m_listModel)));
QHash<QString, ModelNode *>::iterator it;
for (it = properties.begin(); it != properties.end(); ++it) {
objectCache->setValue(it.key().toUtf8(), model->valueForNode(*it));
}
+ objectCache->setNodeUpdatesEnabled(true);
}
return objectCache;
}
- void setObjectValue(const QScriptValue& valuemap);
+ void setObjectValue(const QScriptValue& valuemap, bool writeToCache = true);
void setListValue(const QScriptValue& valuelist);
void setProperty(const QString& prop, const QVariant& val);
+ void changedProperty(const QString &name) const;
+ void updateListIndexes();
static void dump(ModelNode *node, int ind);
QDeclarativeListModel *modelCache;
ModelObject *objectCache;
bool isArray;
+
+ NestedListModel *m_model;
+ int listIndex; // only used for top-level nodes within a list
};
diff --git a/src/declarative/util/qdeclarativelistmodelworkeragent.cpp b/src/declarative/util/qdeclarativelistmodelworkeragent.cpp
index 658584e..6804d4a 100644
--- a/src/declarative/util/qdeclarativelistmodelworkeragent.cpp
+++ b/src/declarative/util/qdeclarativelistmodelworkeragent.cpp
@@ -86,7 +86,7 @@ QDeclarativeListModelWorkerAgent::QDeclarativeListModelWorkerAgent(QDeclarativeL
: m_engine(0),
m_ref(1),
m_orig(model),
- m_copy(new QDeclarativeListModel(m_orig, this))
+ m_copy(new QDeclarativeListModel(model, this))
{
}
@@ -194,6 +194,11 @@ void QDeclarativeListModelWorkerAgent::sync()
mutex.unlock();
}
+void QDeclarativeListModelWorkerAgent::changedData(int index, int count)
+{
+ data.changedChange(index, count);
+}
+
bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
{
if (e->type() == QEvent::User) {
@@ -216,6 +221,24 @@ bool QDeclarativeListModelWorkerAgent::event(QEvent *e)
orig->m_strings = copy->m_strings;
orig->m_values = copy->m_values;
+ // update the orig->m_nodeData list
+ for (int ii = 0; ii < changes.count(); ++ii) {
+ const Change &change = changes.at(ii);
+ switch (change.type) {
+ case Change::Inserted:
+ orig->insertedNode(change.index);
+ break;
+ case Change::Removed:
+ orig->removedNode(change.index);
+ break;
+ case Change::Moved:
+ orig->moveNodes(change.index, change.to, change.count);
+ break;
+ case Change::Changed:
+ break;
+ }
+ }
+
syncDone.wakeAll();
locker.unlock();
diff --git a/src/declarative/util/qdeclarativelistmodelworkeragent_p.h b/src/declarative/util/qdeclarativelistmodelworkeragent_p.h
index 01da374..10c3bca 100644
--- a/src/declarative/util/qdeclarativelistmodelworkeragent_p.h
+++ b/src/declarative/util/qdeclarativelistmodelworkeragent_p.h
@@ -67,6 +67,7 @@ QT_BEGIN_NAMESPACE
QT_MODULE(Declarative)
class QDeclarativeListModel;
+class FlatListScriptClass;
class QDeclarativeListModelWorkerAgent : public QObject
{
@@ -115,6 +116,7 @@ protected:
private:
friend class QDeclarativeWorkerScriptEnginePrivate;
+ friend class FlatListScriptClass;
QScriptEngine *m_engine;
struct Change {
@@ -141,6 +143,8 @@ private:
QDeclarativeListModel *list;
};
+ void changedData(int index, int count);
+
QAtomicInt m_ref;
QDeclarativeListModel *m_orig;
QDeclarativeListModel *m_copy;
diff --git a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
index 10805b4..70ec416 100644
--- a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
+++ b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
@@ -49,6 +49,7 @@
#include <QtCore/qtimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qtranslator.h>
+#include <QSignalSpy>
#include "../../../shared/util.h"
@@ -57,6 +58,8 @@
#define SRCDIR "."
#endif
+Q_DECLARE_METATYPE(QList<int>)
+
class tst_qdeclarativelistmodel : public QObject
{
Q_OBJECT
@@ -64,6 +67,7 @@ public:
tst_qdeclarativelistmodel() {}
private:
+ int roleFromName(const QDeclarativeListModel *model, const QString &roleName);
QScriptValue nestedListValue(QScriptEngine *eng) const;
QDeclarativeItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
void waitForWorker(QDeclarativeItem *item);
@@ -78,6 +82,8 @@ private slots:
void dynamic();
void dynamic_worker_data();
void dynamic_worker();
+ void dynamic_worker_sync_data();
+ void dynamic_worker_sync();
void convertNestedToFlat_fail();
void convertNestedToFlat_fail_data();
void convertNestedToFlat_ok();
@@ -86,7 +92,23 @@ private slots:
void error_data();
void error();
void set();
+ void get();
+ void get_data();
+ void get_worker();
+ void get_worker_data();
+ void get_nested();
+ void get_nested_data();
};
+int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
+{
+ QList<int> roles = model->roles();
+ for (int i=0; i<roles.count(); i++) {
+ if (model->toString(roles[i]) == roleName)
+ return roles[i];
+ }
+ Q_ASSERT(false);
+ return -1;
+}
QScriptValue tst_qdeclarativelistmodel::nestedListValue(QScriptEngine *eng) const
{
@@ -196,6 +218,10 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "";
QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "";
+ QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "";
+
+ QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "";
+ QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "";
QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "";
QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "";
@@ -310,8 +336,12 @@ void tst_qdeclarativelistmodel::dynamic_worker()
QFETCH(int, result);
QFETCH(QString, warning);
+ if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
+ return;
+
// This is same as dynamic() except it applies the test to a ListModel called
- // from a WorkerScript (i.e. testing the internal NestedListModel class)
+ // from a WorkerScript (i.e. testing the internal FlatListModel that is created
+ // by the WorkerListModelAgent)
QDeclarativeListModel model;
QDeclarativeEngine eng;
@@ -330,27 +360,62 @@ void tst_qdeclarativelistmodel::dynamic_worker()
if (!warning.isEmpty())
QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
- if (operations.count() == 1) {
- // test count(), get() return the correct default values in the worker list model
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
- Q_ARG(QVariant, operations)));
- waitForWorker(item);
- QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
- } else {
- // execute a set of commands on the worker list model, then check the
- // changes are reflected in the list model in the main thread
- if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add nested list values when modifying or after modification from a worker script");
-
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
- Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
- waitForWorker(item);
-
- QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
- if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
- QCOMPARE(e.evaluate().toInt(), result);
+ QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
+ Q_ARG(QVariant, operations)));
+ waitForWorker(item);
+ QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
+
+ delete item;
+ qApp->processEvents();
+}
+
+
+
+void tst_qdeclarativelistmodel::dynamic_worker_sync_data()
+{
+ dynamic_data();
+}
+
+void tst_qdeclarativelistmodel::dynamic_worker_sync()
+{
+ QFETCH(QString, script);
+ QFETCH(int, result);
+ QFETCH(QString, warning);
+
+ // This is the same as dynamic_worker() except that it executes a set of list operations
+ // from the worker script, calls sync(), and tests the changes are reflected in the
+ // list in the main thread
+
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
+ QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
+ script = script.mid(1, script.length() - 2);
+ QVariantList operations;
+ foreach (const QString &s, script.split(';')) {
+ if (!s.isEmpty())
+ operations << s;
}
+ if (!warning.isEmpty())
+ QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
+
+ // execute a set of commands on the worker list model, then check the
+ // changes are reflected in the list model in the main thread
+ if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
+ QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add nested list values when modifying or after modification from a worker script");
+
+ QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
+ Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
+ waitForWorker(item);
+
+ QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
+ if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
+ QCOMPARE(e.evaluate().toInt(), result);
+
delete item;
qApp->processEvents();
}
@@ -595,6 +660,9 @@ void tst_qdeclarativelistmodel::error()
}
}
+/*
+ Test model changes from set() are available to the view
+*/
void tst_qdeclarativelistmodel::set()
{
QDeclarativeEngine engine;
@@ -618,6 +686,205 @@ void tst_qdeclarativelistmodel::set()
QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
}
+/*
+ Test model changes on values returned by get() are available to the view
+*/
+void tst_qdeclarativelistmodel::get()
+{
+ QFETCH(QString, expression);
+ QFETCH(int, index);
+ QFETCH(QString, roleName);
+ QFETCH(QVariant, roleValue);
+
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng);
+ component.setData(
+ "import Qt 4.7\n"
+ "ListModel { \n"
+ "ListElement { roleA: 100 }\n"
+ "ListElement { roleA: 200; roleB: 400 } \n"
+ "ListElement { roleA: 200; roleB: 400 } \n"
+ "}", QUrl());
+ QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
+ int role = roleFromName(model, roleName);
+
+ QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
+ QDeclarativeExpression expr(eng.rootContext(), model, expression);
+ expr.evaluate();
+ QVERIFY(!expr.hasError());
+
+ QCOMPARE(model->data(index, role), roleValue);
+ QCOMPARE(spy.count(), 1);
+
+ QList<QVariant> spyResult = spy.takeFirst();
+ QCOMPARE(spyResult.at(0).toInt(), index);
+ QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
+ QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
+}
+
+void tst_qdeclarativelistmodel::get_data()
+{
+ QTest::addColumn<QString>("expression");
+ QTest::addColumn<int>("index");
+ QTest::addColumn<QString>("roleName");
+ QTest::addColumn<QVariant>("roleValue");
+
+ QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
+ QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
+
+ QVariantMap map;
+ map["zzz"] = 123;
+ QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
+
+ QVariantList list;
+ map.clear(); map["a"] = 50; map["b"] = 500;
+ list << map;
+ map.clear(); map["c"] = 1000;
+ list << map;
+ QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
+}
+
+void tst_qdeclarativelistmodel::get_worker()
+{
+ QFETCH(QString, expression);
+ QFETCH(int, index);
+ QFETCH(QString, roleName);
+ QFETCH(QVariant, roleValue);
+
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
+ QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+ QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&eng);
+
+ // Add some values like get() test
+ QScriptValue sv = seng->newObject();
+ sv.setProperty(QLatin1String("roleA"), seng->newVariant(QVariant::fromValue(100)));
+ model.append(sv);
+ sv = seng->newObject();
+ sv.setProperty(QLatin1String("roleA"), seng->newVariant(QVariant::fromValue(200)));
+ sv.setProperty(QLatin1String("roleB"), seng->newVariant(QVariant::fromValue(400)));
+ model.append(sv);
+ model.append(sv);
+ int role = roleFromName(&model, roleName);
+
+ const char *warning = "<Unknown File>: QML ListModel: Cannot add nested list values when modifying or after modification from a worker script";
+ if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
+ QTest::ignoreMessage(QtWarningMsg, warning);
+ QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
+
+ // in the worker thread, change the model data and call sync()
+ QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
+ Q_ARG(QVariant, QStringList(expression))));
+ waitForWorker(item);
+
+ // see if we receive the model changes in the main thread's model
+ if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
+ QVERIFY(model.data(index, role) != roleValue);
+ QCOMPARE(spy.count(), 0);
+ } else {
+ QCOMPARE(model.data(index, role), roleValue);
+ QCOMPARE(spy.count(), 1);
+
+ QList<QVariant> spyResult = spy.takeFirst();
+ QCOMPARE(spyResult.at(0).toInt(), index);
+ QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
+ QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
+ }
+}
+
+void tst_qdeclarativelistmodel::get_worker_data()
+{
+ get_data();
+}
+
+/*
+ Test that the tests run in get() also work for nested list data
+*/
+void tst_qdeclarativelistmodel::get_nested()
+{
+ QFETCH(QString, expression);
+ QFETCH(int, index);
+ QFETCH(QString, roleName);
+ QFETCH(QVariant, roleValue);
+
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng);
+ component.setData(
+ "import Qt 4.7\n"
+ "ListModel { \n"
+ "ListElement {\n"
+ "listRoleA: [\n"
+ "ListElement { roleA: 100 },\n"
+ "ListElement { roleA: 200; roleB: 400 },\n"
+ "ListElement { roleA: 200; roleB: 400 } \n"
+ "]\n"
+ "}\n"
+ "ListElement {\n"
+ "listRoleA: [\n"
+ "ListElement { roleA: 100 },\n"
+ "ListElement { roleA: 200; roleB: 400 },\n"
+ "ListElement { roleA: 200; roleB: 400 } \n"
+ "]\n"
+ "listRoleB: [\n"
+ "ListElement { roleA: 100 },\n"
+ "ListElement { roleA: 200; roleB: 400 },\n"
+ "ListElement { roleA: 200; roleB: 400 } \n"
+ "]\n"
+ "listRoleC: [\n"
+ "ListElement { roleA: 100 },\n"
+ "ListElement { roleA: 200; roleB: 400 },\n"
+ "ListElement { roleA: 200; roleB: 400 } \n"
+ "]\n"
+ "}\n"
+ "}", QUrl());
+ QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
+ QVERIFY(component.errorString().isEmpty());
+ QDeclarativeListModel *childModel;
+
+ // Test setting the inner list data for:
+ // get(0).listRoleA
+ // get(1).listRoleA
+ // get(1).listRoleB
+ // get(1).listRoleC
+
+ QList<QPair<int, QString> > testData;
+ testData << qMakePair(0, QString("listRoleA"));
+ testData << qMakePair(1, QString("listRoleA"));
+ testData << qMakePair(1, QString("listRoleB"));
+ testData << qMakePair(1, QString("listRoleC"));
+
+ for (int i=0; i<testData.count(); i++) {
+ int outerListIndex = testData[i].first;
+ QString outerListRoleName = testData[i].second;
+ int outerListRole = roleFromName(model, outerListRoleName);
+
+ childModel = qobject_cast<QDeclarativeListModel*>(model->data(outerListIndex, outerListRole).value<QObject*>());
+ QVERIFY(childModel);
+
+ QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
+ QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
+
+ QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
+ expr.evaluate();
+ QVERIFY(!expr.hasError());
+
+ int role = roleFromName(childModel, roleName);
+ QCOMPARE(childModel->data(index, role), roleValue);
+ QCOMPARE(spy.count(), 1);
+
+ QList<QVariant> spyResult = spy.takeFirst();
+ QCOMPARE(spyResult.at(0).toInt(), index);
+ QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
+ QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
+ }
+}
+
+void tst_qdeclarativelistmodel::get_nested_data()
+{
+ get_data();
+}
QTEST_MAIN(tst_qdeclarativelistmodel)