1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "private/qdeclarativelistmodel_p_p.h"
43 #include "private/qdeclarativelistmodelworkeragent_p.h"
44 #include "private/qdeclarativeopenmetaobject_p.h"
46 #include <qdeclarativecustomparser_p.h>
47 #include <qdeclarativeparser_p.h>
48 #include <qdeclarativeengine_p.h>
49 #include <qdeclarativecontext.h>
50 #include <qdeclarativeinfo.h>
52 #include <QtCore/qdebug.h>
53 #include <QtCore/qstack.h>
54 #include <QXmlStreamReader>
55 #include <QtScript/qscriptvalueiterator.h>
57 Q_DECLARE_METATYPE(QListModelInterface *)
62 void qdeclarativelistmodel_move(int from, int to, int n, T *items)
65 items->move(from, to);
69 typename T::ConstIterator it=items->begin(); it += from+n;
70 for (; i<to-from; ++i,++it)
73 it=items->begin(); it += from;
76 typename T::ConstIterator f=replaced.begin();
77 typename T::Iterator t=items->begin(); t += from;
78 for (; f != replaced.end(); ++f, ++t)
83 QDeclarativeListModelParser::ListInstruction *QDeclarativeListModelParser::ListModelData::instructions() const
85 return (QDeclarativeListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
89 \qmlclass ListModel QDeclarativeListModel
90 \ingroup qml-working-with-data
92 \brief The ListModel element defines a free-form list data source.
94 The ListModel is a simple container of ListElement definitions, each containing data roles.
95 The contents can be defined dynamically, or explicitly in QML.
97 The number of elements in the model can be obtained from its \l count property.
98 A number of familiar methods are also provided to manipulate the contents of the
99 model, including append(), insert(), move(), remove() and set(). These methods
100 accept dictionaries as their arguments; these are translated to ListElement objects
103 Elements can be manipulated via the model using the setProperty() method, which
104 allows the roles of the specified element to be set and changed.
106 \section1 Example Usage
108 The following example shows a ListModel containing three elements, with the roles
111 \div {class="float-right"}
112 \inlineimage listmodel.png
115 \snippet doc/src/snippets/declarative/listmodel.qml 0
118 Roles (properties) in each element must begin with a lower-case letter and
119 should be common to all elements in a model. The ListElement documentation
120 provides more guidelines for how elements should be defined.
122 Since the example model contains an \c id property, it can be referenced
123 by views, such as the ListView in this example:
125 \snippet doc/src/snippets/declarative/listmodel-simple.qml 0
127 \snippet doc/src/snippets/declarative/listmodel-simple.qml 1
129 It is possible for roles to contain list data. In the following example we
130 create a list of fruit attributes:
132 \snippet doc/src/snippets/declarative/listmodel-nested.qml model
134 The delegate displays all the fruit attributes:
136 \div {class="float-right"}
137 \inlineimage listmodel-nested.png
140 \snippet doc/src/snippets/declarative/listmodel-nested.qml delegate
143 \section1 Modifying List Models
145 The content of a ListModel may be created and modified using the clear(),
146 append(), set(), insert() and setProperty() methods. For example:
148 \snippet doc/src/snippets/declarative/listmodel-modify.qml delegate
150 Note that when creating content dynamically the set of available properties
151 cannot be changed once set. Whatever properties are first added to the model
152 are the only permitted properties in the model.
154 \section1 Using Threaded List Models with WorkerScript
156 ListModel can be used together with WorkerScript access a list model
157 from multiple threads. This is useful if list modifications are
158 synchronous and take some time: the list operations can be moved to a
159 different thread to avoid blocking of the main GUI thread.
161 Here is an example that uses WorkerScript to periodically append the
162 current time to a list model:
164 \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
166 The included file, \tt dataloader.js, looks like this:
168 \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
170 The timer in the main example sends messages to the worker script by calling
171 \l WorkerScript::sendMessage(). When this message is received,
172 \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
173 which appends the current time to the list model.
175 Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
176 handler. You must call sync() or else the changes made to the list from the external
177 thread will not be reflected in the list model in the main thread.
179 \section1 Restrictions
181 If a list model is to be accessed from a WorkerScript, it cannot
182 contain list-type data. So, the following model cannot be used from a WorkerScript
183 because of the list contained in the "attributes" property:
192 ListElement { description: "Core" },
193 ListElement { description: "Deciduous" }
199 In addition, the WorkerScript cannot add list-type data to the model.
201 \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
206 A ListModel internally uses either a NestedListModel or FlatListModel.
208 A NestedListModel can contain lists of ListElements (which
209 when retrieved from get() is accessible as a list model within the list
210 model) whereas a FlatListModel cannot.
212 ListModel uses a NestedListModel to begin with, and if the model is later
213 used from a WorkerScript, it changes to use a FlatListModel instead. This
214 is because ModelNode (which abstracts the nested list model data) needs
215 access to the declarative engine and script engine, which cannot be
216 safely used from outside of the main thread.
219 QDeclarativeListModel::QDeclarativeListModel(QObject *parent)
220 : QListModelInterface(parent), m_agent(0), m_nested(new NestedListModel(this)), m_flat(0)
224 QDeclarativeListModel::QDeclarativeListModel(const QDeclarativeListModel *orig, QDeclarativeListModelWorkerAgent *parent)
225 : QListModelInterface(parent), m_agent(0), m_nested(0), m_flat(0)
227 m_flat = new FlatListModel(this);
228 m_flat->m_parentAgent = parent;
231 m_flat->m_roles = orig->m_flat->m_roles;
232 m_flat->m_strings = orig->m_flat->m_strings;
233 m_flat->m_values = orig->m_flat->m_values;
235 m_flat->m_nodeData.reserve(m_flat->m_values.count());
236 for (int i=0; i<m_flat->m_values.count(); i++)
237 m_flat->m_nodeData << 0;
241 QDeclarativeListModel::~QDeclarativeListModel()
250 bool QDeclarativeListModel::flatten()
255 QList<int> roles = m_nested->roles();
257 QList<QHash<int, QVariant> > values;
258 bool hasNested = false;
259 for (int i=0; i<m_nested->count(); i++) {
260 values.append(m_nested->data(i, roles, &hasNested));
265 FlatListModel *flat = new FlatListModel(this);
266 flat->m_values = values;
268 for (int i=0; i<roles.count(); i++) {
269 QString s = m_nested->toString(roles[i]);
270 flat->m_roles.insert(roles[i], s);
271 flat->m_strings.insert(s, roles[i]);
274 flat->m_nodeData.reserve(flat->m_values.count());
275 for (int i=0; i<flat->m_values.count(); i++)
276 flat->m_nodeData << 0;
284 bool QDeclarativeListModel::inWorkerThread() const
286 return m_flat && m_flat->m_parentAgent;
289 QDeclarativeListModelWorkerAgent *QDeclarativeListModel::agent()
295 qmlInfo(this) << "List contains list-type data and cannot be used from a worker script";
299 m_agent = new QDeclarativeListModelWorkerAgent(this);
303 QList<int> QDeclarativeListModel::roles() const
305 return m_flat ? m_flat->roles() : m_nested->roles();
308 QString QDeclarativeListModel::toString(int role) const
310 return m_flat ? m_flat->toString(role) : m_nested->toString(role);
313 QVariant QDeclarativeListModel::data(int index, int role) const
315 if (index >= count() || index < 0)
318 return m_flat ? m_flat->data(index, role) : m_nested->data(index, role);
322 \qmlproperty int ListModel::count
323 The number of data entries in the model.
325 int QDeclarativeListModel::count() const
327 return m_flat ? m_flat->count() : m_nested->count();
331 \qmlmethod ListModel::clear()
333 Deletes all content from the model.
335 \sa append() remove()
337 void QDeclarativeListModel::clear()
339 int cleared = count();
345 if (!inWorkerThread()) {
346 emit itemsRemoved(0, cleared);
351 QDeclarativeListModel *ModelNode::model(const NestedListModel *model)
354 modelCache = new QDeclarativeListModel;
355 QDeclarativeEngine::setContextForObject(modelCache,QDeclarativeEngine::contextForObject(model->m_listModel));
356 modelCache->m_nested->_root = this; // ListModel defaults to nestable model
358 for (int i=0; i<values.count(); ++i) {
359 ModelNode *subNode = qvariant_cast<ModelNode *>(values.at(i));
361 subNode->m_model = modelCache->m_nested;
367 ModelObject *ModelNode::object(const NestedListModel *model)
370 objectCache = new ModelObject(this,
371 const_cast<NestedListModel*>(model),
372 QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(model->m_listModel)));
373 QHash<QString, ModelNode *>::iterator it;
374 for (it = properties.begin(); it != properties.end(); ++it) {
375 objectCache->setValue(it.key().toUtf8(), model->valueForNode(*it));
377 objectCache->setNodeUpdatesEnabled(true);
383 \qmlmethod ListModel::remove(int index)
385 Deletes the content at \a index from the model.
389 void QDeclarativeListModel::remove(int index)
391 if (index < 0 || index >= count()) {
392 qmlInfo(this) << tr("remove: index %1 out of range").arg(index);
397 m_flat->remove(index);
399 m_nested->remove(index);
401 if (!inWorkerThread()) {
402 emit itemsRemoved(index, 1);
408 \qmlmethod ListModel::insert(int index, jsobject dict)
410 Adds a new item to the list model at position \a index, with the
414 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
417 The \a index must be to an existing item in the list, or one past
418 the end of the list (equivalent to append).
422 void QDeclarativeListModel::insert(int index, const QScriptValue& valuemap)
424 if (!valuemap.isObject() || valuemap.isArray()) {
425 qmlInfo(this) << tr("insert: value is not an object");
429 if (index < 0 || index > count()) {
430 qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
434 bool ok = m_flat ? m_flat->insert(index, valuemap) : m_nested->insert(index, valuemap);
435 if (ok && !inWorkerThread()) {
436 emit itemsInserted(index, 1);
442 \qmlmethod ListModel::move(int from, int to, int n)
444 Moves \a n items \a from one position \a to another.
446 The from and to ranges must exist; for example, to move the first 3 items
447 to the end of the list:
450 fruitModel.move(0, fruitModel.count - 3, 3)
455 void QDeclarativeListModel::move(int from, int to, int n)
457 if (n==0 || from==to)
459 if (!canMove(from, to, n)) {
460 qmlInfo(this) << tr("move: out of range");
468 // Only move forwards - flip if backwards moving
477 m_flat->move(from, to, n);
479 m_nested->move(from, to, n);
481 if (!inWorkerThread())
482 emit itemsMoved(origfrom, origto, orign);
486 \qmlmethod ListModel::append(jsobject dict)
488 Adds a new item to the end of the list model, with the
492 fruitModel.append({"cost": 5.95, "name":"Pizza"})
497 void QDeclarativeListModel::append(const QScriptValue& valuemap)
499 if (!valuemap.isObject() || valuemap.isArray()) {
500 qmlInfo(this) << tr("append: value is not an object");
504 insert(count(), valuemap);
508 \qmlmethod object ListModel::get(int index)
510 Returns the item at \a index in the list model. This allows the item
511 data to be accessed or modified from JavaScript:
514 Component.onCompleted: {
515 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
516 console.log(fruitModel.get(0).cost);
517 fruitModel.get(0).cost = 10.95;
521 The \a index must be an element in the list.
523 Note that properties of the returned object that are themselves objects
524 will also be models, and this get() method is used to access elements:
527 fruitModel.append(..., "attributes":
528 [{"name":"spikes","value":"7mm"},
529 {"name":"color","value":"green"}]);
530 fruitModel.get(0).attributes.get(1).value; // == "green"
533 \warning The returned object is not guaranteed to remain valid. It
534 should not be used in \l{Property Binding}{property bindings}.
538 QScriptValue QDeclarativeListModel::get(int index) const
540 // the internal flat/nested class checks for bad index
541 return m_flat ? m_flat->get(index) : m_nested->get(index);
545 \qmlmethod ListModel::set(int index, jsobject dict)
547 Changes the item at \a index in the list model with the
548 values in \a dict. Properties not appearing in \a dict
552 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
555 If \a index is equal to count() then a new item is appended to the
556 list. Otherwise, \a index must be an element in the list.
560 void QDeclarativeListModel::set(int index, const QScriptValue& valuemap)
563 set(index, valuemap, &roles);
564 if (!roles.isEmpty() && !inWorkerThread())
565 emit itemsChanged(index, 1, roles);
568 void QDeclarativeListModel::set(int index, const QScriptValue& valuemap, QList<int> *roles)
570 if (!valuemap.isObject() || valuemap.isArray()) {
571 qmlInfo(this) << tr("set: value is not an object");
574 if (index > count() || index < 0) {
575 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
579 if (index == count()) {
583 m_flat->set(index, valuemap, roles);
585 m_nested->set(index, valuemap, roles);
590 \qmlmethod ListModel::setProperty(int index, string property, variant value)
592 Changes the \a property of the item at \a index in the list model to \a value.
595 fruitModel.setProperty(3, "cost", 5.95)
598 The \a index must be an element in the list.
602 void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value)
605 setProperty(index, property, value, &roles);
606 if (!roles.isEmpty() && !inWorkerThread())
607 emit itemsChanged(index, 1, roles);
610 void QDeclarativeListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
612 if (count() == 0 || index >= count() || index < 0) {
613 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
618 m_flat->setProperty(index, property, value, roles);
620 m_nested->setProperty(index, property, value, roles);
624 \qmlmethod ListModel::sync()
626 Writes any unsaved changes to the list model after it has been modified
627 from a worker script.
629 void QDeclarativeListModel::sync()
631 // This is just a dummy method to make it look like sync() exists in
632 // ListModel (and not just QDeclarativeListModelWorkerAgent) and to let
633 // us document sync().
634 qmlInfo(this) << "List sync() can only be called from a WorkerScript";
637 bool QDeclarativeListModelParser::compileProperty(const QDeclarativeCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
639 QList<QVariant> values = prop.assignedValues();
640 for(int ii = 0; ii < values.count(); ++ii) {
641 const QVariant &value = values.at(ii);
643 if(value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
644 QDeclarativeCustomParserNode node =
645 qvariant_cast<QDeclarativeCustomParserNode>(value);
647 if (node.name() != listElementTypeName) {
648 const QMetaObject *mo = resolveType(node.name());
649 if (mo != &QDeclarativeListElement::staticMetaObject) {
650 error(node, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
653 listElementTypeName = node.name(); // cache right name for next time
658 li.type = ListInstruction::Push;
663 QList<QDeclarativeCustomParserProperty> props = node.properties();
664 for(int jj = 0; jj < props.count(); ++jj) {
665 const QDeclarativeCustomParserProperty &nodeProp = props.at(jj);
666 if (nodeProp.name().isEmpty()) {
667 error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot contain nested elements"));
670 if (nodeProp.name() == "id") {
671 error(nodeProp, QDeclarativeListModel::tr("ListElement: cannot use reserved \"id\" property"));
676 int ref = data.count();
677 data.append(nodeProp.name());
679 li.type = ListInstruction::Set;
683 if(!compileProperty(nodeProp, instr, data))
686 li.type = ListInstruction::Pop;
693 li.type = ListInstruction::Pop;
700 QDeclarativeParser::Variant variant =
701 qvariant_cast<QDeclarativeParser::Variant>(value);
703 int ref = data.count();
706 d += char(variant.type()); // type tag
707 if (variant.isString()) {
708 d += variant.asString().toUtf8();
709 } else if (variant.isNumber()) {
710 d += QByteArray::number(variant.asNumber(),'g',20);
711 } else if (variant.isBoolean()) {
712 d += char(variant.asBoolean());
713 } else if (variant.isScript()) {
714 if (definesEmptyList(variant.asScript())) {
715 d[0] = char(QDeclarativeParser::Variant::Invalid); // marks empty list
717 QByteArray script = variant.asScript().toUtf8();
718 int v = evaluateEnum(script);
720 if (script.startsWith("QT_TR_NOOP(\"") && script.endsWith("\")")) {
721 d[0] = char(QDeclarativeParser::Variant::String);
722 d += script.mid(12,script.length()-14);
724 error(prop, QDeclarativeListModel::tr("ListElement: cannot use script for property value"));
728 d[0] = char(QDeclarativeParser::Variant::Number);
729 d += QByteArray::number(v);
737 li.type = ListInstruction::Value;
746 QByteArray QDeclarativeListModelParser::compile(const QList<QDeclarativeCustomParserProperty> &customProps)
748 QList<ListInstruction> instr;
750 listElementTypeName = QByteArray(); // unknown
752 for(int ii = 0; ii < customProps.count(); ++ii) {
753 const QDeclarativeCustomParserProperty &prop = customProps.at(ii);
754 if(!prop.name().isEmpty()) { // isn't default property
755 error(prop, QDeclarativeListModel::tr("ListModel: undefined property '%1'").arg(QString::fromUtf8(prop.name())));
759 if(!compileProperty(prop, instr, data)) {
764 int size = sizeof(ListModelData) +
765 instr.count() * sizeof(ListInstruction) +
771 ListModelData *lmd = (ListModelData *)rv.data();
772 lmd->dataOffset = sizeof(ListModelData) +
773 instr.count() * sizeof(ListInstruction);
774 lmd->instrCount = instr.count();
775 for (int ii = 0; ii < instr.count(); ++ii)
776 lmd->instructions()[ii] = instr.at(ii);
777 ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
782 void QDeclarativeListModelParser::setCustomData(QObject *obj, const QByteArray &d)
784 QDeclarativeListModel *rv = static_cast<QDeclarativeListModel *>(obj);
786 ModelNode *root = new ModelNode(rv->m_nested);
787 rv->m_nested->_root = root;
788 QStack<ModelNode *> nodes;
791 bool processingSet = false;
793 const ListModelData *lmd = (const ListModelData *)d.constData();
794 const char *data = ((const char *)lmd) + lmd->dataOffset;
796 for (int ii = 0; ii < lmd->instrCount; ++ii) {
797 const ListInstruction &instr = lmd->instructions()[ii];
800 case ListInstruction::Push:
802 ModelNode *n = nodes.top();
803 ModelNode *n2 = new ModelNode(rv->m_nested);
804 n->values << QVariant::fromValue(n2);
811 case ListInstruction::Pop:
815 case ListInstruction::Value:
817 ModelNode *n = nodes.top();
818 switch (QDeclarativeParser::Variant::Type(data[instr.dataIdx])) {
819 case QDeclarativeParser::Variant::Invalid:
822 case QDeclarativeParser::Variant::Boolean:
823 n->values.append(bool(data[1 + instr.dataIdx]));
825 case QDeclarativeParser::Variant::Number:
826 n->values.append(QByteArray(data + 1 + instr.dataIdx).toDouble());
828 case QDeclarativeParser::Variant::String:
829 n->values.append(QString::fromUtf8(data + 1 + instr.dataIdx));
832 Q_ASSERT("Format error in ListInstruction");
835 processingSet = false;
839 case ListInstruction::Set:
841 ModelNode *n = nodes.top();
842 ModelNode *n2 = new ModelNode(rv->m_nested);
843 n->properties.insert(QString::fromUtf8(data + instr.dataIdx), n2);
845 processingSet = true;
851 ModelNode *rootNode = rv->m_nested->_root;
852 for (int i=0; i<rootNode->values.count(); ++i) {
853 ModelNode *node = qvariant_cast<ModelNode *>(rootNode->values[i]);
855 node->updateListIndexes();
859 bool QDeclarativeListModelParser::definesEmptyList(const QString &s)
861 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
862 for (int i=1; i<s.length()-1; i++) {
873 \qmlclass ListElement QDeclarativeListElement
874 \ingroup qml-working-with-data
876 \brief The ListElement element defines a data item in a ListModel.
878 List elements are defined inside ListModel definitions, and represent items in a
879 list that will be displayed using ListView or \l Repeater items.
881 List elements are defined like other QML elements except that they contain
882 a collection of \e role definitions instead of properties. Using the same
883 syntax as property definitions, roles both define how the data is accessed
884 and include the data itself.
886 The names used for roles must begin with a lower-case letter and should be
887 common to all elements in a given model. Values must be simple constants; either
888 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
889 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
891 \section1 Referencing Roles
893 The role names are used by delegates to obtain data from list elements.
894 Each role name is accessible in the delegate's scope, and refers to the
895 corresponding role in the current element. Where a role name would be
896 ambiguous to use, it can be accessed via the \l{ListView::}{model}
897 property (e.g., \c{model.cost} instead of \c{cost}).
899 \section1 Example Usage
901 The following model defines a series of list elements, each of which
902 contain "name" and "cost" roles and their associated values.
904 \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml model
906 The delegate obtains the name and cost for each element by simply referring
907 to \c name and \c cost:
909 \snippet doc/src/snippets/declarative/qml-data-models/listelements.qml view
914 FlatListModel::FlatListModel(QDeclarativeListModel *base)
915 : m_scriptEngine(0), m_listModel(base), m_scriptClass(0), m_parentAgent(0)
919 FlatListModel::~FlatListModel()
921 qDeleteAll(m_nodeData);
924 QVariant FlatListModel::data(int index, int role) const
926 Q_ASSERT(index >= 0 && index < m_values.count());
927 if (m_values[index].contains(role))
928 return m_values[index][role];
932 QList<int> FlatListModel::roles() const
934 return m_roles.keys();
937 QString FlatListModel::toString(int role) const
939 if (m_roles.contains(role))
940 return m_roles[role];
944 int FlatListModel::count() const
946 return m_values.count();
949 void FlatListModel::clear()
953 qDeleteAll(m_nodeData);
957 void FlatListModel::remove(int index)
959 m_values.removeAt(index);
963 bool FlatListModel::insert(int index, const QScriptValue &value)
965 Q_ASSERT(index >= 0 && index <= m_values.count());
967 QHash<int, QVariant> row;
968 if (!addValue(value, &row, 0))
971 m_values.insert(index, row);
977 QScriptValue FlatListModel::get(int index) const
979 QScriptEngine *scriptEngine = m_scriptEngine ? m_scriptEngine : QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(m_listModel));
984 if (index < 0 || index >= m_values.count())
985 return scriptEngine->undefinedValue();
987 FlatListModel *that = const_cast<FlatListModel*>(this);
989 that->m_scriptClass = new FlatListScriptClass(that, scriptEngine);
991 FlatNodeData *data = m_nodeData.value(index);
993 data = new FlatNodeData(index);
994 that->m_nodeData.replace(index, data);
997 return QScriptDeclarativeClass::newObject(scriptEngine, m_scriptClass, new FlatNodeObjectData(data));
1000 void FlatListModel::set(int index, const QScriptValue &value, QList<int> *roles)
1002 Q_ASSERT(index >= 0 && index < m_values.count());
1004 QHash<int, QVariant> row = m_values[index];
1005 if (addValue(value, &row, roles))
1006 m_values[index] = row;
1009 void FlatListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
1011 Q_ASSERT(index >= 0 && index < m_values.count());
1013 QHash<QString, int>::Iterator iter = m_strings.find(property);
1015 if (iter == m_strings.end()) {
1016 role = m_roles.count();
1017 m_roles.insert(role, property);
1018 m_strings.insert(property, role);
1020 role = iter.value();
1023 if (m_values[index][role] != value) {
1024 roles->append(role);
1025 m_values[index][role] = value;
1029 void FlatListModel::move(int from, int to, int n)
1031 qdeclarativelistmodel_move<QList<QHash<int, QVariant> > >(from, to, n, &m_values);
1032 moveNodes(from, to, n);
1035 bool FlatListModel::addValue(const QScriptValue &value, QHash<int, QVariant> *row, QList<int> *roles)
1037 QScriptValueIterator it(value);
1038 while (it.hasNext()) {
1040 QScriptValue value = it.value();
1041 if (!value.isVariant() && !value.isRegExp() && !value.isDate() && value.isObject()) {
1042 qmlInfo(m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
1046 QString name = it.name();
1047 QVariant v = it.value().toVariant();
1049 QHash<QString, int>::Iterator iter = m_strings.find(name);
1050 if (iter == m_strings.end()) {
1051 int role = m_roles.count();
1052 m_roles.insert(role, name);
1053 iter = m_strings.insert(name, role);
1055 roles->append(role);
1057 int role = iter.value();
1058 if (roles && row->contains(role) && row->value(role) != v)
1059 roles->append(role);
1061 row->insert(*iter, v);
1066 void FlatListModel::insertedNode(int index)
1068 if (index >= 0 && index <= m_values.count()) {
1069 m_nodeData.insert(index, 0);
1071 for (int i=index + 1; i<m_nodeData.count(); i++) {
1073 m_nodeData[i]->index = i;
1078 void FlatListModel::removedNode(int index)
1080 if (index >= 0 && index < m_nodeData.count()) {
1081 delete m_nodeData.takeAt(index);
1083 for (int i=index; i<m_nodeData.count(); i++) {
1085 m_nodeData[i]->index = i;
1090 void FlatListModel::moveNodes(int from, int to, int n)
1092 if (!m_listModel->canMove(from, to, n))
1095 qdeclarativelistmodel_move<QList<FlatNodeData *> >(from, to, n, &m_nodeData);
1097 for (int i=from; i<from + (to-from); i++) {
1099 m_nodeData[i]->index = i;
1105 FlatNodeData::~FlatNodeData()
1107 for (QSet<FlatNodeObjectData *>::Iterator iter = objects.begin(); iter != objects.end(); ++iter) {
1108 FlatNodeObjectData *data = *iter;
1113 void FlatNodeData::addData(FlatNodeObjectData *data)
1115 objects.insert(data);
1118 void FlatNodeData::removeData(FlatNodeObjectData *data)
1120 objects.remove(data);
1124 FlatListScriptClass::FlatListScriptClass(FlatListModel *model, QScriptEngine *seng)
1125 : QScriptDeclarativeClass(seng),
1130 QScriptDeclarativeClass::Value FlatListScriptClass::property(Object *obj, const Identifier &name)
1132 FlatNodeObjectData *objData = static_cast<FlatNodeObjectData*>(obj);
1133 if (!objData->nodeData) // item at this index has been deleted
1134 return QScriptDeclarativeClass::Value(engine(), engine()->undefinedValue());
1136 int index = objData->nodeData->index;
1137 QString propName = toString(name);
1138 int role = m_model->m_strings.value(propName, -1);
1140 if (role >= 0 && index >=0 ) {
1141 const QHash<int, QVariant> &row = m_model->m_values[index];
1142 QScriptValue sv = engine()->toScriptValue<QVariant>(row[role]);
1143 return QScriptDeclarativeClass::Value(engine(), sv);
1146 return QScriptDeclarativeClass::Value(engine(), engine()->undefinedValue());
1149 void FlatListScriptClass::setProperty(Object *obj, const Identifier &name, const QScriptValue &value)
1151 if (!value.isVariant() && !value.isRegExp() && !value.isDate() && value.isObject()) {
1152 qmlInfo(m_model->m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
1156 FlatNodeObjectData *objData = static_cast<FlatNodeObjectData*>(obj);
1157 if (!objData->nodeData) // item at this index has been deleted
1160 int index = objData->nodeData->index;
1161 QString propName = toString(name);
1163 int role = m_model->m_strings.value(propName, -1);
1164 if (role >= 0 && index >= 0) {
1165 QHash<int, QVariant> &row = m_model->m_values[index];
1166 row[role] = value.toVariant();
1170 if (m_model->m_parentAgent) {
1171 // This is the list in the worker thread, so tell the agent to
1172 // emit itemsChanged() later
1173 m_model->m_parentAgent->changedData(index, 1, roles);
1175 // This is the list in the main thread, so emit itemsChanged()
1176 emit m_model->m_listModel->itemsChanged(index, 1, roles);
1181 QScriptClass::QueryFlags FlatListScriptClass::queryProperty(Object *, const Identifier &, QScriptClass::QueryFlags)
1183 return (QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess);
1186 bool FlatListScriptClass::compare(Object *obj1, Object *obj2)
1188 FlatNodeObjectData *data1 = static_cast<FlatNodeObjectData*>(obj1);
1189 FlatNodeObjectData *data2 = static_cast<FlatNodeObjectData*>(obj2);
1191 if (!data1->nodeData || !data2->nodeData)
1194 return data1->nodeData->index == data2->nodeData->index;
1199 NestedListModel::NestedListModel(QDeclarativeListModel *base)
1200 : _root(0), m_ownsRoot(false), m_listModel(base), _rolesOk(false)
1204 NestedListModel::~NestedListModel()
1210 QVariant NestedListModel::valueForNode(ModelNode *node, bool *hasNested) const
1216 if (node->isArray) {
1218 rv = node->model(this);
1222 if (!node->properties.isEmpty()) {
1224 rv = node->object(this);
1225 } else if (node->values.count() == 0) {
1228 } else if (node->values.count() == 1) {
1230 QVariant &var = node->values[0];
1231 ModelNode *valueNode = qvariant_cast<ModelNode *>(var);
1233 if (!valueNode->properties.isEmpty())
1234 rv = valueNode->object(this);
1236 rv = valueNode->model(this);
1244 return QVariant::fromValue(rv);
1250 QHash<int,QVariant> NestedListModel::data(int index, const QList<int> &roles, bool *hasNested) const
1252 Q_ASSERT(_root && index >= 0 && index < _root->values.count());
1254 QHash<int, QVariant> rv;
1256 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1260 for (int ii = 0; ii < roles.count(); ++ii) {
1261 const QString &roleString = roleStrings.at(roles.at(ii));
1263 QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
1264 if (iter != node->properties.end()) {
1265 ModelNode *row = *iter;
1266 rv.insert(roles.at(ii), valueForNode(row, hasNested));
1273 QVariant NestedListModel::data(int index, int role) const
1275 Q_ASSERT(_root && index >= 0 && index < _root->values.count());
1278 if (roleStrings.count() < role)
1281 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1285 const QString &roleString = roleStrings.at(role);
1287 QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
1288 if (iter != node->properties.end()) {
1289 ModelNode *row = *iter;
1290 rv = valueForNode(row);
1296 int NestedListModel::count() const
1298 if (!_root) return 0;
1299 return _root->values.count();
1302 void NestedListModel::clear()
1308 void NestedListModel::remove(int index)
1312 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1313 _root->values.removeAt(index);
1318 bool NestedListModel::insert(int index, const QScriptValue& valuemap)
1321 _root = new ModelNode(this);
1325 ModelNode *mn = new ModelNode(this);
1326 mn->listIndex = index;
1327 mn->setObjectValue(valuemap);
1328 _root->values.insert(index,QVariant::fromValue(mn));
1332 void NestedListModel::move(int from, int to, int n)
1336 qdeclarativelistmodel_move<QVariantList>(from, to, n, &_root->values);
1339 QScriptValue NestedListModel::get(int index) const
1341 QDeclarativeEngine *eng = qmlEngine(m_listModel);
1345 if (index < 0 || index >= count()) {
1346 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(eng);
1348 return seng->undefinedValue();
1352 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1356 return QDeclarativeEnginePrivate::qmlScriptObject(node->object(this), eng);
1359 void NestedListModel::set(int index, const QScriptValue& valuemap, QList<int> *roles)
1361 Q_ASSERT(index >=0 && index < count());
1363 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1364 bool emitItemsChanged = node->setObjectValue(valuemap);
1365 if (!emitItemsChanged)
1368 QScriptValueIterator it(valuemap);
1369 while (it.hasNext()) {
1371 int r = roleStrings.indexOf(it.name());
1373 r = roleStrings.count();
1374 roleStrings << it.name();
1380 void NestedListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
1382 Q_ASSERT(index >=0 && index < count());
1384 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1385 bool emitItemsChanged = node->setProperty(property, value);
1386 if (!emitItemsChanged)
1389 int r = roleStrings.indexOf(property);
1391 r = roleStrings.count();
1392 roleStrings << property;
1397 void NestedListModel::checkRoles() const
1399 if (_rolesOk || !_root)
1402 for (int i = 0; i<_root->values.count(); ++i) {
1403 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
1405 foreach (const QString &role, node->properties.keys()) {
1406 if (!roleStrings.contains(role))
1407 roleStrings.append(role);
1415 QList<int> NestedListModel::roles() const
1419 for (int ii = 0; ii < roleStrings.count(); ++ii)
1424 QString NestedListModel::toString(int role) const
1427 if (role < roleStrings.count())
1428 return roleStrings.at(role);
1434 ModelNode::ModelNode(NestedListModel *model)
1435 : modelCache(0), objectCache(0), isArray(false), m_model(model), listIndex(-1)
1439 ModelNode::~ModelNode()
1442 if (modelCache) { modelCache->m_nested->_root = 0/* ==this */; delete modelCache; modelCache = 0; }
1443 if (objectCache) { delete objectCache; objectCache = 0; }
1446 void ModelNode::clear()
1449 for (int ii = 0; ii < values.count(); ++ii) {
1450 node = qvariant_cast<ModelNode *>(values.at(ii));
1451 if (node) { delete node; node = 0; }
1455 qDeleteAll(properties.values());
1459 bool ModelNode::setObjectValue(const QScriptValue& valuemap, bool writeToCache)
1461 bool emitItemsChanged = false;
1463 QScriptValueIterator it(valuemap);
1464 while (it.hasNext()) {
1466 ModelNode *prev = properties.value(it.name());
1467 ModelNode *value = new ModelNode(m_model);
1468 QScriptValue v = it.value();
1471 value->isArray = true;
1472 value->setListValue(v);
1473 if (writeToCache && objectCache)
1474 objectCache->setValue(it.name().toUtf8(), QVariant::fromValue(value->model(m_model)));
1475 emitItemsChanged = true; // for now, too inefficient to check whether list and sublists have changed
1477 value->values << v.toVariant();
1478 if (writeToCache && objectCache)
1479 objectCache->setValue(it.name().toUtf8(), value->values.last());
1480 if (!emitItemsChanged && prev && prev->values.count() == 1
1481 && prev->values[0] != value->values.last()) {
1482 emitItemsChanged = true;
1485 if (properties.contains(it.name()))
1486 delete properties[it.name()];
1487 properties.insert(it.name(), value);
1489 return emitItemsChanged;
1492 void ModelNode::setListValue(const QScriptValue& valuelist) {
1494 int size = valuelist.property(QLatin1String("length")).toInt32();
1495 for (int i=0; i<size; i++) {
1496 ModelNode *value = new ModelNode(m_model);
1497 QScriptValue v = valuelist.property(i);
1499 value->isArray = true;
1500 value->setListValue(v);
1501 } else if (v.isObject()) {
1502 value->listIndex = i;
1503 value->setObjectValue(v);
1505 value->listIndex = i;
1506 value->values << v.toVariant();
1508 values.append(QVariant::fromValue(value));
1512 bool ModelNode::setProperty(const QString& prop, const QVariant& val) {
1513 QHash<QString, ModelNode *>::const_iterator it = properties.find(prop);
1514 bool emitItemsChanged = false;
1515 if (it != properties.end()) {
1516 if (val != (*it)->values[0])
1517 emitItemsChanged = true;
1518 (*it)->values[0] = val;
1520 ModelNode *n = new ModelNode(m_model);
1522 properties.insert(prop,n);
1525 objectCache->setValue(prop.toUtf8(), val);
1526 return emitItemsChanged;
1529 void ModelNode::updateListIndexes()
1531 for (QHash<QString, ModelNode *>::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) {
1532 ModelNode *node = iter.value();
1533 if (node->isArray) {
1534 for (int i=0; i<node->values.count(); ++i) {
1535 ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(i));
1537 subNode->listIndex = i;
1540 node->updateListIndexes();
1545 Need to call this to emit itemsChanged() for modifications outside of set()
1546 and setProperty(), i.e. if an item returned from get() is modified
1548 void ModelNode::changedProperty(const QString &name) const
1553 m_model->checkRoles();
1555 int role = m_model->roleStrings.indexOf(name);
1557 roles = m_model->roles();
1560 emit m_model->m_listModel->itemsChanged(listIndex, 1, roles);
1563 void ModelNode::dump(ModelNode *node, int ind)
1565 QByteArray indentBa(ind * 4, ' ');
1566 const char *indent = indentBa.constData();
1568 for (int ii = 0; ii < node->values.count(); ++ii) {
1569 ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii));
1571 qWarning().nospace() << indent << "Sub-node " << ii;
1572 dump(subNode, ind + 1);
1574 qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString();
1578 for (QHash<QString, ModelNode *>::ConstIterator iter = node->properties.begin(); iter != node->properties.end(); ++iter) {
1579 qWarning().nospace() << indent << "Property " << iter.key() << ':';
1580 dump(iter.value(), ind + 1);
1584 ModelObject::ModelObject(ModelNode *node, NestedListModel *model, QScriptEngine *seng)
1587 m_meta(new ModelNodeMetaObject(seng, this))
1591 void ModelObject::setValue(const QByteArray &name, const QVariant &val)
1593 m_meta->setValue(name, val);
1594 //setProperty(name.constData(), val);
1597 void ModelObject::setNodeUpdatesEnabled(bool enable)
1599 m_meta->m_enabled = enable;
1603 ModelNodeMetaObject::ModelNodeMetaObject(QScriptEngine *seng, ModelObject *object)
1604 : QDeclarativeOpenMetaObject(object),
1611 void ModelNodeMetaObject::propertyWritten(int index)
1616 QString propName = QString::fromUtf8(name(index));
1617 QVariant value = operator[](index);
1619 QScriptValue sv = m_seng->newObject();
1620 sv.setProperty(propName, m_seng->newVariant(value));
1621 bool changed = m_obj->m_node->setObjectValue(sv, false);
1623 m_obj->m_node->changedProperty(propName);