summaryrefslogtreecommitdiffstats
path: root/src/declarative/util/qmllistmodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/declarative/util/qmllistmodel.cpp')
-rw-r--r--src/declarative/util/qmllistmodel.cpp334
1 files changed, 316 insertions, 18 deletions
diff --git a/src/declarative/util/qmllistmodel.cpp b/src/declarative/util/qmllistmodel.cpp
index 0d9ea94..69bed25 100644
--- a/src/declarative/util/qmllistmodel.cpp
+++ b/src/declarative/util/qmllistmodel.cpp
@@ -47,6 +47,7 @@
#include "qmlopenmetaobject.h"
#include <qmlcontext.h>
#include "qmllistmodel.h"
+#include <QtScript/qscriptvalueiterator.h>
Q_DECLARE_METATYPE(QListModelInterface *)
@@ -68,11 +69,15 @@ 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.
- The ListModel is a simple hierarchy of elements containing data roles.
+ The ListModel is a simple hierarchy of elements containing data roles. The contents can
+ be defined dynamically, or explicitly in QML:
+
For example:
\code
@@ -138,7 +143,7 @@ struct ListModelData
name: "Banana"
cost: 1.95
attributes: [
- ListElement { description: "Tropical" }
+ ListElement { description: "Tropical" },
ListElement { description: "Seedless" }
]
}
@@ -166,13 +171,36 @@ struct ListModelData
}
\endcode
+ The content of a ListModel may be created and modified using the clear(),
+ append(), and set() methods. For example:
+
+ \code
+ Component {
+ id: FruitDelegate
+ Item {
+ width: 200; height: 50
+ Text { text: name }
+ Text { text: '$'+cost; anchors.right: parent.right }
+
+ // Double the price when clicked.
+ MouseRegion {
+ anchors.fill: parent
+ onClicked: FruitModel.set(index, "cost", cost*2)
+ }
+ }
+ }
+ \endcode
+
+ When creating content dynamically, note that the set of available properties cannot be changed
+ except by first clearing the model - whatever properties are first added are then the
+ only permitted properties in the model.
*/
class ModelObject : public QObject
{
Q_OBJECT
public:
- ModelObject(ModelNode *);
+ ModelObject();
void setValue(const QByteArray &name, const QVariant &val)
{
@@ -180,8 +208,6 @@ public:
}
private:
- ModelNode *_node;
- bool _haveProperties;
QmlOpenMetaObject *_mo;
};
@@ -189,7 +215,6 @@ struct ModelNode
{
ModelNode();
~ModelNode();
- QString className;
QList<QVariant> values;
QHash<QString, ModelNode *> properties;
@@ -204,7 +229,7 @@ struct ModelNode
ModelObject *object() {
if (!objectCache) {
- objectCache = new ModelObject(this);
+ objectCache = new ModelObject();
QHash<QString, ModelNode *>::iterator it;
for (it = properties.begin(); it != properties.end(); ++it) {
if (!(*it)->values.isEmpty())
@@ -214,12 +239,61 @@ 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<QString, ModelNode *>::const_iterator it = properties.find(prop);
+ if (it != properties.end()) {
+ (*it)->values[0] = val;
+ } else {
+ ModelNode *n = new ModelNode;
+ n->values << val;
+ properties.insert(prop,n);
+ }
+ if (objectCache)
+ objectCache->setValue(prop.toLatin1(), val);
+ }
+
QmlListModel *modelCache;
ModelObject *objectCache;
};
-ModelObject::ModelObject(ModelNode *node)
-: _node(node), _haveProperties(false), _mo(new QmlOpenMetaObject(this))
+ModelObject::ModelObject()
+: _mo(new QmlOpenMetaObject(this))
{
}
@@ -235,7 +309,7 @@ QmlListModel::~QmlListModel()
void QmlListModel::checkRoles() const
{
- if (_rolesOk)
+ if (_rolesOk || !_root)
return;
for (int ii = 0; ii < _root->values.count(); ++ii) {
@@ -341,10 +415,229 @@ int QmlListModel::count() const
return _root->values.count();
}
+/*!
+ \qmlmethod ListModel::clear()
+
+ Deletes all content from the model. The properties are cleared such that
+ different properties may be set on subsequent additions.
+
+ \sa append() remove()
+*/
+void QmlListModel::clear()
+{
+ int cleared = count();
+ _rolesOk = false;
+ delete _root;
+ _root = 0;
+ roleStrings.clear();
+ emit itemsRemoved(0,cleared);
+}
+
+/*!
+ \qmlmethod ListModel::remove(int index)
+
+ Deletes the content at \a index from the model.
+
+ \sa clear()
+*/
+void QmlListModel::remove(int index)
+{
+ if (_root) {
+ ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
+ _root->values.removeAt(index);
+ if (node)
+ delete node;
+ emit itemsRemoved(index,1);
+ }
+}
+
+/*!
+ \qmlmethod ListModel::insert(index,dict)
+
+ Adds a new item to the list model at position \a index, with the
+ values in \a dict.
+
+ \code
+ FruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
+ \endcode
+
+ 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()
+*/
+void QmlListModel::insert(int index, const QScriptValue& valuemap)
+{
+ if (!_root)
+ _root = new ModelNode;
+ if (index >= _root->values.count()) {
+ if (index == _root->values.count())
+ append(valuemap);
+ return;
+ }
+ ModelNode *mn = new ModelNode;
+ mn->setObjectValue(valuemap);
+ _root->values.insert(index,qVariantFromValue(mn));
+ emit itemsInserted(index,1);
+}
+
+/*!
+ \qmlmethod ListModel::move(from,to,n)
+
+ Moves \a n items \a from one position \a to another.
+
+ The from and to ranges must exist; for example, to move the first 3 items
+ to the end of the list:
+
+ \code
+ FruitModel.move(0,FruitModel.count-3,3)
+ \endcode
+
+ \sa append()
+*/
+void QmlListModel::move(int from, int to, int n)
+{
+ if (from+n > count() || to+n > count() || n==0 || from==to || from < 0 || to < 0)
+ return;
+ int origfrom=from; // preserve actual move, so any animations are correct
+ int origto=to;
+ int orign=n;
+ if (from > to) {
+ // Only move forwards - flip if backwards moving
+ int tfrom = from;
+ int tto = to;
+ from = tto;
+ to = tto+n;
+ n = tfrom-tto;
+ }
+ if (n==1) {
+ _root->values.move(from,to);
+ } else {
+ QList<QVariant> replaced;
+ int i=0;
+ QVariantList::const_iterator it=_root->values.begin(); it += from+n;
+ for (; i<to-from; ++i,++it)
+ replaced.append(*it);
+ i=0;
+ it=_root->values.begin(); it += from;
+ for (; i<n; ++i,++it)
+ replaced.append(*it);
+ QVariantList::const_iterator f=replaced.begin();
+ QVariantList::iterator t=_root->values.begin(); t += from;
+ for (; f != replaced.end(); ++f, ++t)
+ *t = *f;
+ }
+ emit itemsMoved(origfrom,origto,orign);
+}
+
+/*!
+ \qmlmethod ListModel::append(dict)
+
+ Adds a new item to the end of the list model, with the
+ values in \a dict.
+
+ \code
+ FruitModel.append({"cost": 5.95, "name":"Pizza"})
+ \endcode
+
+ \sa set() remove()
+*/
+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;
+ mn->setObjectValue(valuemap);
+ _root->values << qVariantFromValue(mn);
+ emit itemsInserted(count()-1,1);
+}
+
+/*!
+ \qmlmethod ListModel::set(index,dict)
+
+ Changes the item at \a index in the list model to the
+ values in \a dict.
+
+ \code
+ FruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
+ \endcode
+
+ The \a index must be an element in the list.
+
+ \sa append()
+*/
+void QmlListModel::set(int index, const QScriptValue& valuemap)
+{
+ if (!_root)
+ _root = new ModelNode;
+ if ( index >= _root->values.count()) {
+ qWarning() << "ListModel::set index out of range:" << index;
+ return;
+ }
+ if (index == _root->values.count())
+ append(valuemap);
+ else {
+ ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
+ QList<int> roles;
+ 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.name();
+ }
+ roles.append(r);
+ }
+ emit itemsChanged(index,1,roles);
+ }
+}
+
+/*!
+ \qmlmethod ListModel::set(index,property,value)
+
+ Changes the \a property of the item at \a index in the list model to \a value.
+
+ \code
+ FruitModel.set(3, "cost", 5.95)
+ \endcode
+
+ The \a index must be an element in the list.
+
+ \sa append()
+*/
+void QmlListModel::set(int index, const QString& property, const QVariant& value)
+{
+ if (!_root)
+ _root = new ModelNode;
+ if ( index >= _root->values.count()) {
+ qWarning() << "ListModel::set index out of range:" << index;
+ return;
+ }
+ ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
+ int r = roleStrings.indexOf(property);
+ if (r<0) {
+ r = roleStrings.count();
+ roleStrings << property;
+ }
+ QList<int> roles;
+ roles.append(r);
+
+ if (node)
+ node->setProperty(property,value);
+ emit itemsChanged(index,1,roles);
+}
+
+
class QmlListModelParser : public QmlCustomParser
{
public:
- QByteArray compile(const QList<QmlCustomParserProperty> &, bool *ok);
+ QByteArray compile(const QList<QmlCustomParserProperty> &);
bool compileProperty(const QmlCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data);
void setCustomData(QObject *, const QByteArray &);
};
@@ -369,8 +662,14 @@ bool QmlListModelParser::compileProperty(const QmlCustomParserProperty &prop, QL
QList<QmlCustomParserProperty> props = node.properties();
for(int jj = 0; jj < props.count(); ++jj) {
const QmlCustomParserProperty &nodeProp = props.at(jj);
- if(nodeProp.name() == "")
+ if (nodeProp.name() == "") {
+ error(nodeProp, QLatin1String("Cannot use default property in ListModel"));
return false;
+ }
+ if (nodeProp.name() == "id") {
+ error(nodeProp, QLatin1String("Cannot use reserved \"id\" property in ListModel"));
+ return false;
+ }
ListInstruction li;
int ref = data.count();
@@ -416,21 +715,19 @@ bool QmlListModelParser::compileProperty(const QmlCustomParserProperty &prop, QL
return true;
}
-QByteArray QmlListModelParser::compile(const QList<QmlCustomParserProperty> &customProps, bool *ok)
+QByteArray QmlListModelParser::compile(const QList<QmlCustomParserProperty> &customProps)
{
- *ok = true;
QList<ListInstruction> instr;
QByteArray data;
for(int ii = 0; ii < customProps.count(); ++ii) {
const QmlCustomParserProperty &prop = customProps.at(ii);
if(prop.name() != "") { // isn't default property
- *ok = false;
+ error(prop, QLatin1String("Cannot use default property"));
return QByteArray();
}
if(!compileProperty(prop, instr, data)) {
- *ok = false;
return QByteArray();
}
}
@@ -518,7 +815,7 @@ static void dump(ModelNode *node, int ind)
for (int ii = 0; ii < node->values.count(); ++ii) {
ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii));
if (subNode) {
- qWarning().nospace() << indent << "Sub-node " << ii << ": class " << subNode->className;
+ qWarning().nospace() << indent << "Sub-node " << ii;
dump(subNode, ind + 1);
} else {
qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString();
@@ -543,7 +840,8 @@ ModelNode::~ModelNode()
ModelNode *node = qvariant_cast<ModelNode *>(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