1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "QtQuick1/private/qdeclarativelistmodel_p_p.h"
43 #include "QtQuick1/private/qdeclarativelistmodelworkeragent_p.h"
44 #include "QtQuick1/private/qdeclarativeopenmetaobject_p.h"
46 #include <QtDeclarative/private/qdeclarativecustomparser_p.h>
47 #include <QtDeclarative/private/qdeclarativescript_p.h>
48 #include <QtDeclarative/private/qdeclarativeengine_p.h>
49 #include <QtDeclarative/qdeclarativecontext.h>
50 #include <QtDeclarative/qdeclarativeinfo.h>
52 #include <QtCore/qdebug.h>
53 #include <QtCore/qstack.h>
54 #include <QXmlStreamReader>
55 #include <QtDeclarative/qscriptvalueiterator.h>
57 Q_DECLARE_METATYPE(QListModelInterface *)
64 void qdeclarativelistmodel_move(int from, int to, int n, T *items)
67 items->move(from, to);
71 typename T::ConstIterator it=items->begin(); it += from+n;
72 for (; i<to-from; ++i,++it)
75 it=items->begin(); it += from;
78 typename T::ConstIterator f=replaced.begin();
79 typename T::Iterator t=items->begin(); t += from;
80 for (; f != replaced.end(); ++f, ++t)
85 QDeclarative1ListModelParser::ListInstruction *QDeclarative1ListModelParser::ListModelData::instructions() const
87 return (QDeclarative1ListModelParser::ListInstruction *)((char *)this + sizeof(ListModelData));
91 \qmlclass ListModel QDeclarative1ListModel
92 \inqmlmodule QtQuick 1
93 \ingroup qml-working-with-data
95 \brief The ListModel element defines a free-form list data source.
97 The ListModel is a simple container of ListElement definitions, each containing data roles.
98 The contents can be defined dynamically, or explicitly in QML.
100 The number of elements in the model can be obtained from its \l count property.
101 A number of familiar methods are also provided to manipulate the contents of the
102 model, including append(), insert(), move(), remove() and set(). These methods
103 accept dictionaries as their arguments; these are translated to ListElement objects
106 Elements can be manipulated via the model using the setProperty() method, which
107 allows the roles of the specified element to be set and changed.
109 \section1 Example Usage
111 The following example shows a ListModel containing three elements, with the roles
114 \div {class="float-right"}
115 \inlineimage listmodel.png
118 \snippet doc/src/snippets/qtquick1/listmodel.qml 0
121 Roles (properties) in each element must begin with a lower-case letter and
122 should be common to all elements in a model. The ListElement documentation
123 provides more guidelines for how elements should be defined.
125 Since the example model contains an \c id property, it can be referenced
126 by views, such as the ListView in this example:
128 \snippet doc/src/snippets/qtquick1/listmodel-simple.qml 0
130 \snippet doc/src/snippets/qtquick1/listmodel-simple.qml 1
132 It is possible for roles to contain list data. In the following example we
133 create a list of fruit attributes:
135 \snippet doc/src/snippets/qtquick1/listmodel-nested.qml model
137 The delegate displays all the fruit attributes:
139 \div {class="float-right"}
140 \inlineimage listmodel-nested.png
143 \snippet doc/src/snippets/qtquick1/listmodel-nested.qml delegate
146 \section1 Modifying List Models
148 The content of a ListModel may be created and modified using the clear(),
149 append(), set(), insert() and setProperty() methods. For example:
151 \snippet doc/src/snippets/qtquick1/listmodel-modify.qml delegate
153 Note that when creating content dynamically the set of available properties
154 cannot be changed once set. Whatever properties are first added to the model
155 are the only permitted properties in the model.
157 \section1 Using Threaded List Models with WorkerScript
159 ListModel can be used together with WorkerScript access a list model
160 from multiple threads. This is useful if list modifications are
161 synchronous and take some time: the list operations can be moved to a
162 different thread to avoid blocking of the main GUI thread.
164 Here is an example that uses WorkerScript to periodically append the
165 current time to a list model:
167 \snippet examples/declarative/threading/threadedlistmodel/timedisplay.qml 0
169 The included file, \tt dataloader.js, looks like this:
171 \snippet examples/declarative/threading/threadedlistmodel/dataloader.js 0
173 The timer in the main example sends messages to the worker script by calling
174 \l WorkerScript::sendMessage(). When this message is received,
175 \l{WorkerScript::onMessage}{WorkerScript.onMessage()} is invoked in \c dataloader.js,
176 which appends the current time to the list model.
178 Note the call to sync() from the \l{WorkerScript::onMessage}{WorkerScript.onMessage()}
179 handler. You must call sync() or else the changes made to the list from the external
180 thread will not be reflected in the list model in the main thread.
182 \section1 Restrictions
184 If a list model is to be accessed from a WorkerScript, it cannot
185 contain list-type data. So, the following model cannot be used from a WorkerScript
186 because of the list contained in the "attributes" property:
195 ListElement { description: "Core" },
196 ListElement { description: "Deciduous" }
202 In addition, the WorkerScript cannot add list-type data to the model.
204 \sa {qmlmodels}{Data Models}, {declarative/threading/threadedlistmodel}{Threaded ListModel example}, QtDeclarative
209 A ListModel internally uses either a NestedListModel_1 or FlatListModel_1.
211 A NestedListModel_1 can contain lists of ListElements (which
212 when retrieved from get() is accessible as a list model within the list
213 model) whereas a FlatListModel_1 cannot.
215 ListModel uses a NestedListModel_1 to begin with, and if the model is later
216 used from a WorkerScript, it changes to use a FlatListModel_1 instead. This
217 is because ModelNode (which abstracts the nested list model data) needs
218 access to the declarative engine and script engine, which cannot be
219 safely used from outside of the main thread.
222 QDeclarative1ListModel::QDeclarative1ListModel(QObject *parent)
223 : QListModelInterface(parent), m_agent(0), m_nested(new NestedListModel_1(this)), m_flat(0)
227 QDeclarative1ListModel::QDeclarative1ListModel(const QDeclarative1ListModel *orig, QDeclarative1ListModelWorkerAgent *parent)
228 : QListModelInterface(parent), m_agent(0), m_nested(0), m_flat(0)
230 m_flat = new FlatListModel_1(this);
231 m_flat->m_parentAgent = parent;
234 m_flat->m_roles = orig->m_flat->m_roles;
235 m_flat->m_strings = orig->m_flat->m_strings;
236 m_flat->m_values = orig->m_flat->m_values;
238 m_flat->m_nodeData.reserve(m_flat->m_values.count());
239 for (int i=0; i<m_flat->m_values.count(); i++)
240 m_flat->m_nodeData << 0;
244 QDeclarative1ListModel::~QDeclarative1ListModel()
253 bool QDeclarative1ListModel::flatten()
258 QList<int> roles = m_nested->roles();
260 QList<QHash<int, QVariant> > values;
261 bool hasNested = false;
262 for (int i=0; i<m_nested->count(); i++) {
263 values.append(m_nested->data(i, roles, &hasNested));
268 FlatListModel_1 *flat = new FlatListModel_1(this);
269 flat->m_values = values;
271 for (int i=0; i<roles.count(); i++) {
272 QString s = m_nested->toString(roles[i]);
273 flat->m_roles.insert(roles[i], s);
274 flat->m_strings.insert(s, roles[i]);
277 flat->m_nodeData.reserve(flat->m_values.count());
278 for (int i=0; i<flat->m_values.count(); i++)
279 flat->m_nodeData << 0;
287 bool QDeclarative1ListModel::inWorkerThread() const
289 return m_flat && m_flat->m_parentAgent;
292 QDeclarative1ListModelWorkerAgent *QDeclarative1ListModel::agent()
298 qmlInfo(this) << "List contains list-type data and cannot be used from a worker script";
302 m_agent = new QDeclarative1ListModelWorkerAgent(this);
306 QList<int> QDeclarative1ListModel::roles() const
308 return m_flat ? m_flat->roles() : m_nested->roles();
311 QString QDeclarative1ListModel::toString(int role) const
313 return m_flat ? m_flat->toString(role) : m_nested->toString(role);
316 QVariant QDeclarative1ListModel::data(int index, int role) const
318 if (index >= count() || index < 0)
321 return m_flat ? m_flat->data(index, role) : m_nested->data(index, role);
325 \qmlproperty int QtQuick1::ListModel::count
326 The number of data entries in the model.
328 int QDeclarative1ListModel::count() const
330 return m_flat ? m_flat->count() : m_nested->count();
334 \qmlmethod QtQuick1::ListModel::clear()
336 Deletes all content from the model.
338 \sa append() remove()
340 void QDeclarative1ListModel::clear()
342 int cleared = count();
348 if (!inWorkerThread()) {
349 emit itemsRemoved(0, cleared);
354 QDeclarative1ListModel *ModelNode::model(const NestedListModel_1 *model)
357 modelCache = new QDeclarative1ListModel;
358 QDeclarativeEngine::setContextForObject(modelCache,QDeclarativeEngine::contextForObject(model->m_listModel));
359 modelCache->m_nested->_root = this; // ListModel defaults to nestable model
361 for (int i=0; i<values.count(); ++i) {
362 ModelNode *subNode = qvariant_cast<ModelNode *>(values.at(i));
364 subNode->m_model = modelCache->m_nested;
370 ModelObject_1 *ModelNode::object(const NestedListModel_1 *model)
373 objectCache = new ModelObject_1(this,
374 const_cast<NestedListModel_1*>(model),
375 QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(model->m_listModel)));
376 QHash<QString, ModelNode *>::iterator it;
377 for (it = properties.begin(); it != properties.end(); ++it) {
378 objectCache->setValue(it.key().toUtf8(), model->valueForNode(*it));
380 objectCache->setNodeUpdatesEnabled(true);
386 \qmlmethod QtQuick1::ListModel::remove(int index)
388 Deletes the content at \a index from the model.
392 void QDeclarative1ListModel::remove(int index)
394 if (index < 0 || index >= count()) {
395 qmlInfo(this) << tr("remove: index %1 out of range").arg(index);
400 m_flat->remove(index);
402 m_nested->remove(index);
404 if (!inWorkerThread()) {
405 emit itemsRemoved(index, 1);
411 \qmlmethod QtQuick1::ListModel::insert(int index, jsobject dict)
413 Adds a new item to the list model at position \a index, with the
417 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
420 The \a index must be to an existing item in the list, or one past
421 the end of the list (equivalent to append).
425 void QDeclarative1ListModel::insert(int index, const QScriptValue& valuemap)
427 if (!valuemap.isObject() || valuemap.isArray()) {
428 qmlInfo(this) << tr("insert: value is not an object");
432 if (index < 0 || index > count()) {
433 qmlInfo(this) << tr("insert: index %1 out of range").arg(index);
437 bool ok = m_flat ? m_flat->insert(index, valuemap) : m_nested->insert(index, valuemap);
438 if (ok && !inWorkerThread()) {
439 emit itemsInserted(index, 1);
445 \qmlmethod QtQuick1::ListModel::move(int from, int to, int n)
447 Moves \a n items \a from one position \a to another.
449 The from and to ranges must exist; for example, to move the first 3 items
450 to the end of the list:
453 fruitModel.move(0, fruitModel.count - 3, 3)
458 void QDeclarative1ListModel::move(int from, int to, int n)
460 if (n==0 || from==to)
462 if (!canMove(from, to, n)) {
463 qmlInfo(this) << tr("move: out of range");
471 // Only move forwards - flip if backwards moving
480 m_flat->move(from, to, n);
482 m_nested->move(from, to, n);
484 if (!inWorkerThread())
485 emit itemsMoved(origfrom, origto, orign);
489 \qmlmethod QtQuick1::ListModel::append(jsobject dict)
491 Adds a new item to the end of the list model, with the
495 fruitModel.append({"cost": 5.95, "name":"Pizza"})
500 void QDeclarative1ListModel::append(const QScriptValue& valuemap)
502 if (!valuemap.isObject() || valuemap.isArray()) {
503 qmlInfo(this) << tr("append: value is not an object");
507 insert(count(), valuemap);
511 \qmlmethod object QtQuick1::ListModel::get(int index)
513 Returns the item at \a index in the list model. This allows the item
514 data to be accessed or modified from JavaScript:
517 Component.onCompleted: {
518 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
519 console.log(fruitModel.get(0).cost);
520 fruitModel.get(0).cost = 10.95;
524 The \a index must be an element in the list.
526 Note that properties of the returned object that are themselves objects
527 will also be models, and this get() method is used to access elements:
530 fruitModel.append(..., "attributes":
531 [{"name":"spikes","value":"7mm"},
532 {"name":"color","value":"green"}]);
533 fruitModel.get(0).attributes.get(1).value; // == "green"
536 \warning The returned object is not guaranteed to remain valid. It
537 should not be used in \l{Property Binding}{property bindings}.
541 QScriptValue QDeclarative1ListModel::get(int index) const
543 // the internal flat/nested class checks for bad index
544 return m_flat ? m_flat->get(index) : m_nested->get(index);
548 \qmlmethod QtQuick1::ListModel::set(int index, jsobject dict)
550 Changes the item at \a index in the list model with the
551 values in \a dict. Properties not appearing in \a dict
555 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
558 If \a index is equal to count() then a new item is appended to the
559 list. Otherwise, \a index must be an element in the list.
563 void QDeclarative1ListModel::set(int index, const QScriptValue& valuemap)
566 set(index, valuemap, &roles);
567 if (!roles.isEmpty() && !inWorkerThread())
568 emit itemsChanged(index, 1, roles);
571 void QDeclarative1ListModel::set(int index, const QScriptValue& valuemap, QList<int> *roles)
573 if (!valuemap.isObject() || valuemap.isArray()) {
574 qmlInfo(this) << tr("set: value is not an object");
577 if (index > count() || index < 0) {
578 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
582 if (index == count()) {
586 m_flat->set(index, valuemap, roles);
588 m_nested->set(index, valuemap, roles);
593 \qmlmethod QtQuick1::ListModel::setProperty(int index, string property, variant value)
595 Changes the \a property of the item at \a index in the list model to \a value.
598 fruitModel.setProperty(3, "cost", 5.95)
601 The \a index must be an element in the list.
605 void QDeclarative1ListModel::setProperty(int index, const QString& property, const QVariant& value)
608 setProperty(index, property, value, &roles);
609 if (!roles.isEmpty() && !inWorkerThread())
610 emit itemsChanged(index, 1, roles);
613 void QDeclarative1ListModel::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
615 if (count() == 0 || index >= count() || index < 0) {
616 qmlInfo(this) << tr("set: index %1 out of range").arg(index);
621 m_flat->setProperty(index, property, value, roles);
623 m_nested->setProperty(index, property, value, roles);
627 \qmlmethod QtQuick1::ListModel::sync()
629 Writes any unsaved changes to the list model after it has been modified
630 from a worker script.
632 void QDeclarative1ListModel::sync()
634 // This is just a dummy method to make it look like sync() exists in
635 // ListModel (and not just QDeclarative1ListModelWorkerAgent) and to let
636 // us document sync().
637 qmlInfo(this) << "List sync() can only be called from a WorkerScript";
640 bool QDeclarative1ListModelParser::compileProperty(const QDeclarativeCustomParserProperty &prop, QList<ListInstruction> &instr, QByteArray &data)
642 QList<QVariant> values = prop.assignedValues();
643 for(int ii = 0; ii < values.count(); ++ii) {
644 const QVariant &value = values.at(ii);
646 if(value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
647 QDeclarativeCustomParserNode node =
648 qvariant_cast<QDeclarativeCustomParserNode>(value);
650 if (node.name() != listElementTypeName) {
651 const QMetaObject *mo = resolveType(node.name());
652 if (mo != &QDeclarative1ListElement::staticMetaObject) {
653 error(node, QDeclarative1ListModel::tr("ListElement: cannot contain nested elements"));
656 listElementTypeName = node.name(); // cache right name for next time
661 li.type = ListInstruction::Push;
666 QList<QDeclarativeCustomParserProperty> props = node.properties();
667 for(int jj = 0; jj < props.count(); ++jj) {
668 const QDeclarativeCustomParserProperty &nodeProp = props.at(jj);
669 if (nodeProp.name().isEmpty()) {
670 error(nodeProp, QDeclarative1ListModel::tr("ListElement: cannot contain nested elements"));
673 if (nodeProp.name() == "id") {
674 error(nodeProp, QDeclarative1ListModel::tr("ListElement: cannot use reserved \"id\" property"));
679 int ref = data.count();
680 data.append(nodeProp.name());
682 li.type = ListInstruction::Set;
686 if(!compileProperty(nodeProp, instr, data))
689 li.type = ListInstruction::Pop;
696 li.type = ListInstruction::Pop;
703 QDeclarativeParser::Variant variant =
704 qvariant_cast<QDeclarativeParser::Variant>(value);
706 int ref = data.count();
709 d += char(variant.type()); // type tag
710 if (variant.isString()) {
711 d += variant.asString().toUtf8();
712 } else if (variant.isNumber()) {
713 d += QByteArray::number(variant.asNumber(),'g',20);
714 } else if (variant.isBoolean()) {
715 d += char(variant.asBoolean());
716 } else if (variant.isScript()) {
717 if (definesEmptyList(variant.asScript())) {
718 d[0] = char(QDeclarativeParser::Variant::Invalid); // marks empty list
720 QByteArray script = variant.asScript().toUtf8();
721 int v = evaluateEnum(script);
723 if (script.startsWith("QT_TR_NOOP(\"") && script.endsWith("\")")) {
724 d[0] = char(QDeclarativeParser::Variant::String);
725 d += script.mid(12,script.length()-14);
727 error(prop, QDeclarative1ListModel::tr("ListElement: cannot use script for property value"));
731 d[0] = char(QDeclarativeParser::Variant::Number);
732 d += QByteArray::number(v);
740 li.type = ListInstruction::Value;
749 QByteArray QDeclarative1ListModelParser::compile(const QList<QDeclarativeCustomParserProperty> &customProps)
751 QList<ListInstruction> instr;
753 listElementTypeName = QByteArray(); // unknown
755 for(int ii = 0; ii < customProps.count(); ++ii) {
756 const QDeclarativeCustomParserProperty &prop = customProps.at(ii);
757 if(!prop.name().isEmpty()) { // isn't default property
758 error(prop, QDeclarative1ListModel::tr("ListModel: undefined property '%1'").arg(QString::fromUtf8(prop.name())));
762 if(!compileProperty(prop, instr, data)) {
767 int size = sizeof(ListModelData) +
768 instr.count() * sizeof(ListInstruction) +
774 ListModelData *lmd = (ListModelData *)rv.data();
775 lmd->dataOffset = sizeof(ListModelData) +
776 instr.count() * sizeof(ListInstruction);
777 lmd->instrCount = instr.count();
778 for (int ii = 0; ii < instr.count(); ++ii)
779 lmd->instructions()[ii] = instr.at(ii);
780 ::memcpy(rv.data() + lmd->dataOffset, data.constData(), data.count());
785 void QDeclarative1ListModelParser::setCustomData(QObject *obj, const QByteArray &d)
787 QDeclarative1ListModel *rv = static_cast<QDeclarative1ListModel *>(obj);
789 ModelNode *root = new ModelNode(rv->m_nested);
790 rv->m_nested->m_ownsRoot = true;
791 rv->m_nested->_root = root;
792 QStack<ModelNode *> nodes;
795 bool processingSet = false;
797 const ListModelData *lmd = (const ListModelData *)d.constData();
798 const char *data = ((const char *)lmd) + lmd->dataOffset;
800 for (int ii = 0; ii < lmd->instrCount; ++ii) {
801 const ListInstruction &instr = lmd->instructions()[ii];
804 case ListInstruction::Push:
806 ModelNode *n = nodes.top();
807 ModelNode *n2 = new ModelNode(rv->m_nested);
808 n->values << QVariant::fromValue(n2);
815 case ListInstruction::Pop:
819 case ListInstruction::Value:
821 ModelNode *n = nodes.top();
822 switch (QDeclarativeParser::Variant::Type(data[instr.dataIdx])) {
823 case QDeclarativeParser::Variant::Invalid:
826 case QDeclarativeParser::Variant::Boolean:
827 n->values.append(bool(data[1 + instr.dataIdx]));
829 case QDeclarativeParser::Variant::Number:
830 n->values.append(QByteArray(data + 1 + instr.dataIdx).toDouble());
832 case QDeclarativeParser::Variant::String:
833 n->values.append(QString::fromUtf8(data + 1 + instr.dataIdx));
836 Q_ASSERT("Format error in ListInstruction");
839 processingSet = false;
843 case ListInstruction::Set:
845 ModelNode *n = nodes.top();
846 ModelNode *n2 = new ModelNode(rv->m_nested);
847 n->properties.insert(QString::fromUtf8(data + instr.dataIdx), n2);
849 processingSet = true;
855 ModelNode *rootNode = rv->m_nested->_root;
856 for (int i=0; i<rootNode->values.count(); ++i) {
857 ModelNode *node = qvariant_cast<ModelNode *>(rootNode->values[i]);
859 node->updateListIndexes();
863 bool QDeclarative1ListModelParser::definesEmptyList(const QString &s)
865 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
866 for (int i=1; i<s.length()-1; i++) {
877 \qmlclass ListElement QDeclarative1ListElement
878 \inqmlmodule QtQuick 1
879 \ingroup qml-working-with-data
881 \brief The ListElement element defines a data item in a ListModel.
883 List elements are defined inside ListModel definitions, and represent items in a
884 list that will be displayed using ListView or \l Repeater items.
886 List elements are defined like other QML elements except that they contain
887 a collection of \e role definitions instead of properties. Using the same
888 syntax as property definitions, roles both define how the data is accessed
889 and include the data itself.
891 The names used for roles must begin with a lower-case letter and should be
892 common to all elements in a given model. Values must be simple constants; either
893 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
894 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
896 \section1 Referencing Roles
898 The role names are used by delegates to obtain data from list elements.
899 Each role name is accessible in the delegate's scope, and refers to the
900 corresponding role in the current element. Where a role name would be
901 ambiguous to use, it can be accessed via the \l{ListView::}{model}
902 property (e.g., \c{model.cost} instead of \c{cost}).
904 \section1 Example Usage
906 The following model defines a series of list elements, each of which
907 contain "name" and "cost" roles and their associated values.
909 \snippet doc/src/snippets/qtquick1/qml-data-models/listelements.qml model
911 The delegate obtains the name and cost for each element by simply referring
912 to \c name and \c cost:
914 \snippet doc/src/snippets/qtquick1/qml-data-models/listelements.qml view
919 FlatListModel_1::FlatListModel_1(QDeclarative1ListModel *base)
920 : m_scriptEngine(0), m_listModel(base), m_scriptClass(0), m_parentAgent(0)
924 FlatListModel_1::~FlatListModel_1()
926 qDeleteAll(m_nodeData);
929 QVariant FlatListModel_1::data(int index, int role) const
931 Q_ASSERT(index >= 0 && index < m_values.count());
932 if (m_values[index].contains(role))
933 return m_values[index][role];
937 QList<int> FlatListModel_1::roles() const
939 return m_roles.keys();
942 QString FlatListModel_1::toString(int role) const
944 if (m_roles.contains(role))
945 return m_roles[role];
949 int FlatListModel_1::count() const
951 return m_values.count();
954 void FlatListModel_1::clear()
958 qDeleteAll(m_nodeData);
962 void FlatListModel_1::remove(int index)
964 m_values.removeAt(index);
968 bool FlatListModel_1::insert(int index, const QScriptValue &value)
970 Q_ASSERT(index >= 0 && index <= m_values.count());
972 QHash<int, QVariant> row;
973 if (!addValue(value, &row, 0))
976 m_values.insert(index, row);
982 QScriptValue FlatListModel_1::get(int index) const
984 QScriptEngine *scriptEngine = m_scriptEngine ? m_scriptEngine : QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(m_listModel));
989 if (index < 0 || index >= m_values.count())
990 return scriptEngine->undefinedValue();
992 FlatListModel_1 *that = const_cast<FlatListModel_1*>(this);
994 that->m_scriptClass = new FlatListScriptClass_1(that, scriptEngine);
996 FlatNodeData_1 *data = m_nodeData.value(index);
998 data = new FlatNodeData_1(index);
999 that->m_nodeData.replace(index, data);
1002 return QScriptDeclarativeClass::newObject(scriptEngine, m_scriptClass, new FlatNodeObjectData(data));
1005 void FlatListModel_1::set(int index, const QScriptValue &value, QList<int> *roles)
1007 Q_ASSERT(index >= 0 && index < m_values.count());
1009 QHash<int, QVariant> row = m_values[index];
1010 if (addValue(value, &row, roles))
1011 m_values[index] = row;
1014 void FlatListModel_1::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
1016 Q_ASSERT(index >= 0 && index < m_values.count());
1018 QHash<QString, int>::Iterator iter = m_strings.find(property);
1020 if (iter == m_strings.end()) {
1021 role = m_roles.count();
1022 m_roles.insert(role, property);
1023 m_strings.insert(property, role);
1025 role = iter.value();
1028 if (m_values[index][role] != value) {
1029 roles->append(role);
1030 m_values[index][role] = value;
1034 void FlatListModel_1::move(int from, int to, int n)
1036 qdeclarativelistmodel_move<QList<QHash<int, QVariant> > >(from, to, n, &m_values);
1037 moveNodes(from, to, n);
1040 bool FlatListModel_1::addValue(const QScriptValue &value, QHash<int, QVariant> *row, QList<int> *roles)
1042 QScriptValueIterator it(value);
1043 while (it.hasNext()) {
1045 QScriptValue value = it.value();
1046 if (!value.isVariant() && !value.isRegExp() && !value.isDate() && value.isObject()) {
1047 qmlInfo(m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
1051 QString name = it.name();
1052 QVariant v = it.value().toVariant();
1054 QHash<QString, int>::Iterator iter = m_strings.find(name);
1055 if (iter == m_strings.end()) {
1056 int role = m_roles.count();
1057 m_roles.insert(role, name);
1058 iter = m_strings.insert(name, role);
1060 roles->append(role);
1062 int role = iter.value();
1063 if (roles && row->contains(role) && row->value(role) != v)
1064 roles->append(role);
1066 row->insert(*iter, v);
1071 void FlatListModel_1::insertedNode(int index)
1073 if (index >= 0 && index <= m_values.count()) {
1074 m_nodeData.insert(index, 0);
1076 for (int i=index + 1; i<m_nodeData.count(); i++) {
1078 m_nodeData[i]->index = i;
1083 void FlatListModel_1::removedNode(int index)
1085 if (index >= 0 && index < m_nodeData.count()) {
1086 delete m_nodeData.takeAt(index);
1088 for (int i=index; i<m_nodeData.count(); i++) {
1090 m_nodeData[i]->index = i;
1095 void FlatListModel_1::moveNodes(int from, int to, int n)
1097 if (!m_listModel->canMove(from, to, n))
1100 qdeclarativelistmodel_move<QList<FlatNodeData_1 *> >(from, to, n, &m_nodeData);
1102 for (int i=from; i<from + (to-from); i++) {
1104 m_nodeData[i]->index = i;
1110 FlatNodeData_1::~FlatNodeData_1()
1112 for (QSet<FlatNodeObjectData *>::Iterator iter = objects.begin(); iter != objects.end(); ++iter) {
1113 FlatNodeObjectData *data = *iter;
1118 void FlatNodeData_1::addData(FlatNodeObjectData *data)
1120 objects.insert(data);
1123 void FlatNodeData_1::removeData(FlatNodeObjectData *data)
1125 objects.remove(data);
1129 FlatListScriptClass_1::FlatListScriptClass_1(FlatListModel_1 *model, QScriptEngine *seng)
1130 : QScriptDeclarativeClass(seng),
1135 QScriptDeclarativeClass::Value FlatListScriptClass_1::property(Object *obj, const Identifier &name)
1137 FlatNodeObjectData *objData = static_cast<FlatNodeObjectData*>(obj);
1138 if (!objData->nodeData) // item at this index has been deleted
1139 return QScriptDeclarativeClass::Value(engine(), engine()->undefinedValue());
1141 int index = objData->nodeData->index;
1142 QString propName = toString(name);
1143 int role = m_model->m_strings.value(propName, -1);
1145 if (role >= 0 && index >=0 ) {
1146 const QHash<int, QVariant> &row = m_model->m_values[index];
1147 QScriptValue sv = engine()->toScriptValue<QVariant>(row[role]);
1148 return QScriptDeclarativeClass::Value(engine(), sv);
1151 return QScriptDeclarativeClass::Value(engine(), engine()->undefinedValue());
1154 void FlatListScriptClass_1::setProperty(Object *obj, const Identifier &name, const QScriptValue &value)
1156 if (!value.isVariant() && !value.isRegExp() && !value.isDate() && value.isObject()) {
1157 qmlInfo(m_model->m_listModel) << "Cannot add list-type data when modifying or after modification from a worker script";
1161 FlatNodeObjectData *objData = static_cast<FlatNodeObjectData*>(obj);
1162 if (!objData->nodeData) // item at this index has been deleted
1165 int index = objData->nodeData->index;
1166 QString propName = toString(name);
1168 int role = m_model->m_strings.value(propName, -1);
1169 if (role >= 0 && index >= 0) {
1170 QHash<int, QVariant> &row = m_model->m_values[index];
1171 row[role] = value.toVariant();
1175 if (m_model->m_parentAgent) {
1176 // This is the list in the worker thread, so tell the agent to
1177 // emit itemsChanged() later
1178 m_model->m_parentAgent->changedData(index, 1, roles);
1180 // This is the list in the main thread, so emit itemsChanged()
1181 emit m_model->m_listModel->itemsChanged(index, 1, roles);
1186 QScriptClass::QueryFlags FlatListScriptClass_1::queryProperty(Object *, const Identifier &, QScriptClass::QueryFlags)
1188 return (QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess);
1191 bool FlatListScriptClass_1::compare(Object *obj1, Object *obj2)
1193 FlatNodeObjectData *data1 = static_cast<FlatNodeObjectData*>(obj1);
1194 FlatNodeObjectData *data2 = static_cast<FlatNodeObjectData*>(obj2);
1196 if (!data1->nodeData || !data2->nodeData)
1199 return data1->nodeData->index == data2->nodeData->index;
1204 NestedListModel_1::NestedListModel_1(QDeclarative1ListModel *base)
1205 : _root(0), m_ownsRoot(false), m_listModel(base), _rolesOk(false)
1209 NestedListModel_1::~NestedListModel_1()
1215 QVariant NestedListModel_1::valueForNode(ModelNode *node, bool *hasNested) const
1221 if (node->isArray) {
1223 rv = node->model(this);
1227 if (!node->properties.isEmpty()) {
1229 rv = node->object(this);
1230 } else if (node->values.count() == 0) {
1233 } else if (node->values.count() == 1) {
1235 QVariant &var = node->values[0];
1236 ModelNode *valueNode = qvariant_cast<ModelNode *>(var);
1238 if (!valueNode->properties.isEmpty())
1239 rv = valueNode->object(this);
1241 rv = valueNode->model(this);
1249 return QVariant::fromValue(rv);
1255 QHash<int,QVariant> NestedListModel_1::data(int index, const QList<int> &roles, bool *hasNested) const
1257 Q_ASSERT(_root && index >= 0 && index < _root->values.count());
1259 QHash<int, QVariant> rv;
1261 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1265 for (int ii = 0; ii < roles.count(); ++ii) {
1266 const QString &roleString = roleStrings.at(roles.at(ii));
1268 QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
1269 if (iter != node->properties.end()) {
1270 ModelNode *row = *iter;
1271 rv.insert(roles.at(ii), valueForNode(row, hasNested));
1278 QVariant NestedListModel_1::data(int index, int role) const
1280 Q_ASSERT(_root && index >= 0 && index < _root->values.count());
1283 if (roleStrings.count() < role)
1286 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1290 const QString &roleString = roleStrings.at(role);
1292 QHash<QString, ModelNode *>::ConstIterator iter = node->properties.find(roleString);
1293 if (iter != node->properties.end()) {
1294 ModelNode *row = *iter;
1295 rv = valueForNode(row);
1301 int NestedListModel_1::count() const
1303 if (!_root) return 0;
1304 return _root->values.count();
1307 void NestedListModel_1::clear()
1313 void NestedListModel_1::remove(int index)
1317 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1318 _root->values.removeAt(index);
1323 bool NestedListModel_1::insert(int index, const QScriptValue& valuemap)
1326 _root = new ModelNode(this);
1330 ModelNode *mn = new ModelNode(this);
1331 mn->listIndex = index;
1332 mn->setObjectValue(valuemap);
1333 _root->values.insert(index,QVariant::fromValue(mn));
1337 void NestedListModel_1::move(int from, int to, int n)
1341 qdeclarativelistmodel_move<QVariantList>(from, to, n, &_root->values);
1344 QScriptValue NestedListModel_1::get(int index) const
1346 QDeclarativeEngine *eng = qmlEngine(m_listModel);
1350 if (index < 0 || index >= count()) {
1351 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(eng);
1353 return seng->undefinedValue();
1357 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1361 return QDeclarativeEnginePrivate::qmlScriptObject(node->object(this), eng);
1364 void NestedListModel_1::set(int index, const QScriptValue& valuemap, QList<int> *roles)
1366 Q_ASSERT(index >=0 && index < count());
1368 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1369 bool emitItemsChanged = node->setObjectValue(valuemap);
1370 if (!emitItemsChanged)
1373 QScriptValueIterator it(valuemap);
1374 while (it.hasNext()) {
1376 int r = roleStrings.indexOf(it.name());
1378 r = roleStrings.count();
1379 roleStrings << it.name();
1385 void NestedListModel_1::setProperty(int index, const QString& property, const QVariant& value, QList<int> *roles)
1387 Q_ASSERT(index >=0 && index < count());
1389 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(index));
1390 bool emitItemsChanged = node->setProperty(property, value);
1391 if (!emitItemsChanged)
1394 int r = roleStrings.indexOf(property);
1396 r = roleStrings.count();
1397 roleStrings << property;
1402 void NestedListModel_1::checkRoles() const
1404 if (_rolesOk || !_root)
1407 for (int i = 0; i<_root->values.count(); ++i) {
1408 ModelNode *node = qvariant_cast<ModelNode *>(_root->values.at(i));
1410 foreach (const QString &role, node->properties.keys()) {
1411 if (!roleStrings.contains(role))
1412 roleStrings.append(role);
1420 QList<int> NestedListModel_1::roles() const
1424 for (int ii = 0; ii < roleStrings.count(); ++ii)
1429 QString NestedListModel_1::toString(int role) const
1432 if (role < roleStrings.count())
1433 return roleStrings.at(role);
1439 ModelNode::ModelNode(NestedListModel_1 *model)
1440 : modelCache(0), objectCache(0), isArray(false), m_model(model), listIndex(-1)
1444 ModelNode::~ModelNode()
1447 if (modelCache) { modelCache->m_nested->_root = 0/* ==this */; delete modelCache; modelCache = 0; }
1448 if (objectCache) { delete objectCache; objectCache = 0; }
1451 void ModelNode::clear()
1454 for (int ii = 0; ii < values.count(); ++ii) {
1455 node = qvariant_cast<ModelNode *>(values.at(ii));
1456 if (node) { delete node; node = 0; }
1460 qDeleteAll(properties.values());
1464 bool ModelNode::setObjectValue(const QScriptValue& valuemap, bool writeToCache)
1466 bool emitItemsChanged = false;
1468 QScriptValueIterator it(valuemap);
1469 while (it.hasNext()) {
1471 ModelNode *prev = properties.value(it.name());
1472 ModelNode *value = new ModelNode(m_model);
1473 QScriptValue v = it.value();
1476 value->isArray = true;
1477 value->setListValue(v);
1478 if (writeToCache && objectCache)
1479 objectCache->setValue(it.name().toUtf8(), QVariant::fromValue(value->model(m_model)));
1480 emitItemsChanged = true; // for now, too inefficient to check whether list and sublists have changed
1482 value->values << v.toVariant();
1483 if (writeToCache && objectCache)
1484 objectCache->setValue(it.name().toUtf8(), value->values.last());
1485 if (!emitItemsChanged && prev && prev->values.count() == 1
1486 && prev->values[0] != value->values.last()) {
1487 emitItemsChanged = true;
1490 if (properties.contains(it.name()))
1491 delete properties[it.name()];
1492 properties.insert(it.name(), value);
1494 return emitItemsChanged;
1497 void ModelNode::setListValue(const QScriptValue& valuelist) {
1499 int size = valuelist.property(QLatin1String("length")).toInt32();
1500 for (int i=0; i<size; i++) {
1501 ModelNode *value = new ModelNode(m_model);
1502 QScriptValue v = valuelist.property(i);
1504 value->isArray = true;
1505 value->setListValue(v);
1506 } else if (v.isObject()) {
1507 value->listIndex = i;
1508 value->setObjectValue(v);
1510 value->listIndex = i;
1511 value->values << v.toVariant();
1513 values.append(QVariant::fromValue(value));
1517 bool ModelNode::setProperty(const QString& prop, const QVariant& val) {
1518 QHash<QString, ModelNode *>::const_iterator it = properties.find(prop);
1519 bool emitItemsChanged = false;
1520 if (it != properties.end()) {
1521 if (val != (*it)->values[0])
1522 emitItemsChanged = true;
1523 (*it)->values[0] = val;
1525 ModelNode *n = new ModelNode(m_model);
1527 properties.insert(prop,n);
1530 objectCache->setValue(prop.toUtf8(), val);
1531 return emitItemsChanged;
1534 void ModelNode::updateListIndexes()
1536 for (QHash<QString, ModelNode *>::ConstIterator iter = properties.begin(); iter != properties.end(); ++iter) {
1537 ModelNode *node = iter.value();
1538 if (node->isArray) {
1539 for (int i=0; i<node->values.count(); ++i) {
1540 ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(i));
1542 subNode->listIndex = i;
1545 node->updateListIndexes();
1550 Need to call this to emit itemsChanged() for modifications outside of set()
1551 and setProperty(), i.e. if an item returned from get() is modified
1553 void ModelNode::changedProperty(const QString &name) const
1558 m_model->checkRoles();
1560 int role = m_model->roleStrings.indexOf(name);
1562 roles = m_model->roles();
1565 emit m_model->m_listModel->itemsChanged(listIndex, 1, roles);
1568 void ModelNode::dump(ModelNode *node, int ind)
1570 QByteArray indentBa(ind * 4, ' ');
1571 const char *indent = indentBa.constData();
1573 for (int ii = 0; ii < node->values.count(); ++ii) {
1574 ModelNode *subNode = qvariant_cast<ModelNode *>(node->values.at(ii));
1576 qWarning().nospace() << indent << "Sub-node " << ii;
1577 dump(subNode, ind + 1);
1579 qWarning().nospace() << indent << "Sub-node " << ii << ": " << node->values.at(ii).toString();
1583 for (QHash<QString, ModelNode *>::ConstIterator iter = node->properties.begin(); iter != node->properties.end(); ++iter) {
1584 qWarning().nospace() << indent << "Property " << iter.key() << ':';
1585 dump(iter.value(), ind + 1);
1589 ModelObject_1::ModelObject_1(ModelNode *node, NestedListModel_1 *model, QScriptEngine *seng)
1592 m_meta(new ModelNodeMetaObject_1(seng, this))
1596 void ModelObject_1::setValue(const QByteArray &name, const QVariant &val)
1598 m_meta->setValue(name, val);
1599 //setProperty(name.constData(), val);
1602 void ModelObject_1::setNodeUpdatesEnabled(bool enable)
1604 m_meta->m_enabled = enable;
1608 ModelNodeMetaObject_1::ModelNodeMetaObject_1(QScriptEngine *seng, ModelObject_1 *object)
1609 : QDeclarative1OpenMetaObject(object),
1616 void ModelNodeMetaObject_1::propertyWritten(int index)
1621 QString propName = QString::fromUtf8(name(index));
1622 QVariant value = operator[](index);
1624 QScriptValue sv = m_seng->newObject();
1625 sv.setProperty(propName, m_seng->newVariant(value));
1626 bool changed = m_obj->m_node->setObjectValue(sv, false);
1628 m_obj->m_node->changedProperty(propName);