1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmllistmodel_p_p.h"
41#include "qqmllistmodelworkeragent_p.h"
42#include <private/qqmlopenmetaobject_p.h>
43#include <private/qqmljsast_p.h>
44#include <private/qqmljsengine_p.h>
45#include <private/qjsvalue_p.h>
46
47#include <private/qqmlcustomparser_p.h>
48#include <private/qqmlengine_p.h>
49#include <private/qqmlnotifier_p.h>
50
51#include <private/qv4object_p.h>
52#include <private/qv4dateobject_p.h>
53#include <private/qv4objectiterator_p.h>
54#include <private/qv4alloca_p.h>
55
56#include <qqmlcontext.h>
57#include <qqmlinfo.h>
58
59#include <QtCore/qdebug.h>
60#include <QtCore/qstack.h>
61#include <QXmlStreamReader>
62#include <QtCore/qdatetime.h>
63#include <QScopedValueRollback>
64
65QT_BEGIN_NAMESPACE
66
67// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
68enum { MIN_LISTMODEL_UID = 1024 };
69
70static QAtomicInt uidCounter(MIN_LISTMODEL_UID);
71
72template <typename T>
73static bool isMemoryUsed(const char *mem)
74{
75 for (size_t i=0 ; i < sizeof(T) ; ++i) {
76 if (mem[i] != 0)
77 return true;
78 }
79
80 return false;
81}
82
83static QString roleTypeName(ListLayout::Role::DataType t)
84{
85 static const QString roleTypeNames[] = {
86 QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"),
87 QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"),
88 QStringLiteral("DateTime"), QStringLiteral("Function")
89 };
90
91 if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
92 return roleTypeNames[t];
93
94 return QString();
95}
96
97const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
98{
99 QStringHash<Role *>::Node *node = roleHash.findNode(key);
100 if (node) {
101 const Role &r = *node->value;
102 if (type != r.type)
103 qmlWarning(0) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
104 return r;
105 }
106
107 return createRole(key, type);
108}
109
110const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::DataType type)
111{
112 QStringHash<Role *>::Node *node = roleHash.findNode(key);
113 if (node) {
114 const Role &r = *node->value;
115 if (type != r.type)
116 qmlWarning(0) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
117 return r;
118 }
119
120 QString qkey = key->toQString();
121
122 return createRole(qkey, type);
123}
124
125const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
126{
127 const int dataSizes[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QPointer<QObject>), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
128 const int dataAlignments[] = { sizeof(QString), sizeof(double), sizeof(bool), sizeof(ListModel *), sizeof(QObject *), sizeof(QVariantMap), sizeof(QDateTime), sizeof(QJSValue) };
129
130 Role *r = new Role;
131 r->name = key;
132 r->type = type;
133
134 if (type == Role::List) {
135 r->subLayout = new ListLayout;
136 } else {
137 r->subLayout = 0;
138 }
139
140 int dataSize = dataSizes[type];
141 int dataAlignment = dataAlignments[type];
142
143 int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
144 if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
145 r->blockIndex = ++currentBlock;
146 r->blockOffset = 0;
147 currentBlockOffset = dataSize;
148 } else {
149 r->blockIndex = currentBlock;
150 r->blockOffset = dataOffset;
151 currentBlockOffset = dataOffset + dataSize;
152 }
153
154 int roleIndex = roles.count();
155 r->index = roleIndex;
156
157 roles.append(r);
158 roleHash.insert(key, r);
159
160 return *r;
161}
162
163ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
164{
165 const int otherRolesCount = other->roles.count();
166 roles.reserve(otherRolesCount);
167 for (int i=0 ; i < otherRolesCount; ++i) {
168 Role *role = new Role(other->roles[i]);
169 roles.append(role);
170 roleHash.insert(role->name, role);
171 }
172 currentBlockOffset = other->currentBlockOffset;
173 currentBlock = other->currentBlock;
174}
175
176ListLayout::~ListLayout()
177{
178 qDeleteAll(roles);
179}
180
181void ListLayout::sync(ListLayout *src, ListLayout *target)
182{
183 int roleOffset = target->roles.count();
184 int newRoleCount = src->roles.count() - roleOffset;
185
186 for (int i=0 ; i < newRoleCount ; ++i) {
187 Role *role = new Role(src->roles[roleOffset + i]);
188 target->roles.append(role);
189 target->roleHash.insert(role->name, role);
190 }
191
192 target->currentBlockOffset = src->currentBlockOffset;
193 target->currentBlock = src->currentBlock;
194}
195
196ListLayout::Role::Role(const Role *other)
197{
198 name = other->name;
199 type = other->type;
200 blockIndex = other->blockIndex;
201 blockOffset = other->blockOffset;
202 index = other->index;
203 if (other->subLayout)
204 subLayout = new ListLayout(other->subLayout);
205 else
206 subLayout = 0;
207}
208
209ListLayout::Role::~Role()
210{
211 delete subLayout;
212}
213
214const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
215{
216 Role::DataType type;
217
218 switch (data.type()) {
219 case QVariant::Double: type = Role::Number; break;
220 case QVariant::Int: type = Role::Number; break;
221 case QVariant::Bool: type = Role::Bool; break;
222 case QVariant::String: type = Role::String; break;
223 case QVariant::Map: type = Role::VariantMap; break;
224 case QVariant::DateTime: type = Role::DateTime; break;
225 case QVariant::UserType: {
226 if (data.userType() == qMetaTypeId<QJSValue>() &&
227 data.value<QJSValue>().isCallable()) {
228 type = Role::Function;
229 break;
230 } else {
231 type = Role::List;
232 break;
233 }
234 }
235 default: type = Role::Invalid; break;
236 }
237
238 if (type == Role::Invalid) {
239 qmlWarning(0) << "Can't create role for unsupported data type";
240 return 0;
241 }
242
243 return &getRoleOrCreate(key, type);
244}
245
246const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const
247{
248 Role *r = 0;
249 QStringHash<Role *>::Node *node = roleHash.findNode(key);
250 if (node)
251 r = node->value;
252 return r;
253}
254
255const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const
256{
257 Role *r = 0;
258 QStringHash<Role *>::Node *node = roleHash.findNode(key);
259 if (node)
260 r = node->value;
261 return r;
262}
263
264QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex)
265{
266 ListElement *e = elements[elementIndex];
267 if (e->m_objectCache == 0) {
268 e->m_objectCache = new QObject;
269 (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
270 }
271 return e->m_objectCache;
272}
273
274void ListModel::sync(ListModel *src, ListModel *target, QHash<int, ListModel *> *targetModelHash)
275{
276 // Sanity check
277 target->m_uid = src->m_uid;
278 if (targetModelHash)
279 targetModelHash->insert(target->m_uid, target);
280
281 // Build hash of elements <-> uid for each of the lists
282 QHash<int, ElementSync> elementHash;
283 for (int i=0 ; i < target->elements.count() ; ++i) {
284 ListElement *e = target->elements.at(i);
285 int uid = e->getUid();
286 ElementSync sync;
287 sync.target = e;
288 elementHash.insert(uid, sync);
289 }
290 for (int i=0 ; i < src->elements.count() ; ++i) {
291 ListElement *e = src->elements.at(i);
292 int uid = e->getUid();
293
294 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
295 if (it == elementHash.end()) {
296 ElementSync sync;
297 sync.src = e;
298 elementHash.insert(uid, sync);
299 } else {
300 ElementSync &sync = it.value();
301 sync.src = e;
302 }
303 }
304
305 // Get list of elements that are in the target but no longer in the source. These get deleted first.
306 QHash<int, ElementSync>::iterator it = elementHash.begin();
307 QHash<int, ElementSync>::iterator end = elementHash.end();
308 while (it != end) {
309 const ElementSync &s = it.value();
310 if (s.src == 0) {
311 s.target->destroy(target->m_layout);
312 target->elements.removeOne(s.target);
313 delete s.target;
314 }
315 ++it;
316 }
317
318 // Sync the layouts
319 ListLayout::sync(src->m_layout, target->m_layout);
320
321 // Clear the target list, and append in correct order from the source
322 target->elements.clear();
323 for (int i=0 ; i < src->elements.count() ; ++i) {
324 ListElement *srcElement = src->elements.at(i);
325 it = elementHash.find(srcElement->getUid());
326 const ElementSync &s = it.value();
327 ListElement *targetElement = s.target;
328 if (targetElement == 0) {
329 targetElement = new ListElement(srcElement->getUid());
330 }
331 ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout, targetModelHash);
332 target->elements.append(targetElement);
333 }
334
335 target->updateCacheIndices();
336
337 // Update values stored in target meta objects
338 for (int i=0 ; i < target->elements.count() ; ++i) {
339 ListElement *e = target->elements[i];
340 if (ModelNodeMetaObject *mo = e->objectCache())
341 mo->updateValues();
342 }
343}
344
345ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache, int uid) : m_layout(layout), m_modelCache(modelCache)
346{
347 if (uid == -1)
348 uid = uidCounter.fetchAndAddOrdered(1);
349 m_uid = uid;
350}
351
352void ListModel::destroy()
353{
354 for (const auto &destroyer : remove(0, elements.count()))
355 destroyer();
356
357 m_uid = -1;
358 m_layout = 0;
359 if (m_modelCache && m_modelCache->m_primary == false)
360 delete m_modelCache;
361 m_modelCache = 0;
362}
363
364int ListModel::appendElement()
365{
366 int elementIndex = elements.count();
367 newElement(elementIndex);
368 return elementIndex;
369}
370
371void ListModel::insertElement(int index)
372{
373 newElement(index);
374 updateCacheIndices(index);
375}
376
377void ListModel::move(int from, int to, int n)
378{
379 if (from > to) {
380 // Only move forwards - flip if backwards moving
381 int tfrom = from;
382 int tto = to;
383 from = tto;
384 to = tto+n;
385 n = tfrom-tto;
386 }
387
388 QPODVector<ListElement *, 4> store;
389 for (int i=0 ; i < (to-from) ; ++i)
390 store.append(elements[from+n+i]);
391 for (int i=0 ; i < n ; ++i)
392 store.append(elements[from+i]);
393 for (int i=0 ; i < store.count() ; ++i)
394 elements[from+i] = store[i];
395
396 updateCacheIndices(from, to + n);
397}
398
399void ListModel::newElement(int index)
400{
401 ListElement *e = new ListElement;
402 elements.insert(index, e);
403}
404
405void ListModel::updateCacheIndices(int start, int end)
406{
407 int count = elements.count();
408
409 if (end < 0 || end > count)
410 end = count;
411
412 for (int i = start; i < end; ++i) {
413 ListElement *e = elements.at(i);
414 if (ModelNodeMetaObject *mo = e->objectCache())
415 mo->m_elementIndex = i;
416 }
417}
418
419QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
420{
421 if (roleIndex >= m_layout->roleCount())
422 return QVariant();
423 ListElement *e = elements[elementIndex];
424 const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
425 return e->getProperty(r, owner, eng);
426}
427
428ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
429{
430 ListElement *e = elements[elementIndex];
431 return e->getListProperty(role);
432}
433
434void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
435{
436 ListElement *e = elements[elementIndex];
437
438 QV4::ExecutionEngine *v4 = object->engine();
439 QV4::Scope scope(v4);
440 QV4::ScopedObject o(scope);
441
442 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::WithProtoChain|QV4::ObjectIterator::EnumerableOnly);
443 QV4::ScopedString propertyName(scope);
444 QV4::ScopedValue propertyValue(scope);
445 while (1) {
446 propertyName = it.nextPropertyNameAsString(propertyValue);
447 if (!propertyName)
448 break;
449
450 // Check if this key exists yet
451 int roleIndex = -1;
452
453 // Add the value now
454 if (const QV4::String *s = propertyValue->as<QV4::String>()) {
455 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
456 roleIndex = e->setStringProperty(r, s->toQString());
457 } else if (propertyValue->isNumber()) {
458 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
459 roleIndex = e->setDoubleProperty(r, propertyValue->asDouble());
460 } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
461 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
462 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
463
464 int arrayLength = a->getLength();
465 for (int j=0 ; j < arrayLength ; ++j) {
466 o = a->getIndexed(j);
467 subModel->append(o);
468 }
469
470 roleIndex = e->setListProperty(r, subModel);
471 } else if (propertyValue->isBoolean()) {
472 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
473 roleIndex = e->setBoolProperty(r, propertyValue->booleanValue());
474 } else if (QV4::DateObject *dd = propertyValue->as<QV4::DateObject>()) {
475 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
476 QDateTime dt = dd->toQDateTime();
477 roleIndex = e->setDateTimeProperty(r, dt);
478 } else if (QV4::FunctionObject *f = propertyValue->as<QV4::FunctionObject>()) {
479 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function);
480 QV4::ScopedFunctionObject func(scope, f);
481 QJSValue jsv;
482 QJSValuePrivate::setValue(&jsv, v4, func);
483 roleIndex = e->setFunctionProperty(r, jsv);
484 } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
485 if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
486 QObject *o = wrapper->object();
487 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
488 if (role.type == ListLayout::Role::QObject)
489 roleIndex = e->setQObjectProperty(role, o);
490 } else {
491 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
492 if (role.type == ListLayout::Role::VariantMap) {
493 QV4::ScopedObject obj(scope, o);
494 roleIndex = e->setVariantMapProperty(role, obj);
495 }
496 }
497 } else if (propertyValue->isNullOrUndefined()) {
498 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
499 if (r)
500 e->clearProperty(*r);
501 }
502
503 if (roleIndex != -1)
504 roles->append(roleIndex);
505 }
506
507 if (ModelNodeMetaObject *mo = e->objectCache())
508 mo->updateValues(*roles);
509}
510
511void ListModel::set(int elementIndex, QV4::Object *object)
512{
513 if (!object)
514 return;
515
516 ListElement *e = elements[elementIndex];
517
518 QV4::ExecutionEngine *v4 = object->engine();
519 QV4::Scope scope(v4);
520
521 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::WithProtoChain|QV4::ObjectIterator::EnumerableOnly);
522 QV4::ScopedString propertyName(scope);
523 QV4::ScopedValue propertyValue(scope);
524 QV4::ScopedObject o(scope);
525 while (1) {
526 propertyName = it.nextPropertyNameAsString(propertyValue);
527 if (!propertyName)
528 break;
529
530 // Add the value now
531 if (QV4::String *s = propertyValue->stringValue()) {
532 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
533 if (r.type == ListLayout::Role::String)
534 e->setStringPropertyFast(r, s->toQString());
535 } else if (propertyValue->isNumber()) {
536 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
537 if (r.type == ListLayout::Role::Number) {
538 e->setDoublePropertyFast(r, propertyValue->asDouble());
539 }
540 } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
541 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::List);
542 if (r.type == ListLayout::Role::List) {
543 ListModel *subModel = new ListModel(r.subLayout, 0, -1);
544
545 int arrayLength = a->getLength();
546 for (int j=0 ; j < arrayLength ; ++j) {
547 o = a->getIndexed(j);
548 subModel->append(o);
549 }
550
551 e->setListPropertyFast(r, subModel);
552 }
553 } else if (propertyValue->isBoolean()) {
554 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
555 if (r.type == ListLayout::Role::Bool) {
556 e->setBoolPropertyFast(r, propertyValue->booleanValue());
557 }
558 } else if (QV4::DateObject *date = propertyValue->as<QV4::DateObject>()) {
559 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
560 if (r.type == ListLayout::Role::DateTime) {
561 QDateTime dt = date->toQDateTime();;
562 e->setDateTimePropertyFast(r, dt);
563 }
564 } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
565 if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
566 QObject *o = wrapper->object();
567 const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
568 if (r.type == ListLayout::Role::QObject)
569 e->setQObjectPropertyFast(r, o);
570 } else {
571 const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
572 if (role.type == ListLayout::Role::VariantMap)
573 e->setVariantMapFast(role, o);
574 }
575 } else if (propertyValue->isNullOrUndefined()) {
576 const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
577 if (r)
578 e->clearProperty(*r);
579 }
580 }
581}
582
583QVector<std::function<void()>> ListModel::remove(int index, int count)
584{
585 QVector<std::function<void()>> toDestroy;
586 auto layout = m_layout;
587 for (int i=0 ; i < count ; ++i) {
588 auto element = elements[index+i];
589 toDestroy.append([element, layout](){
590 element->destroy(layout);
591 delete element;
592 });
593 }
594 elements.remove(index, count);
595 updateCacheIndices(index);
596 return toDestroy;
597}
598
599void ListModel::insert(int elementIndex, QV4::Object *object)
600{
601 insertElement(elementIndex);
602 set(elementIndex, object);
603}
604
605int ListModel::append(QV4::Object *object)
606{
607 int elementIndex = appendElement();
608 set(elementIndex, object);
609 return elementIndex;
610}
611
612int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
613{
614 int roleIndex = -1;
615
616 if (elementIndex >= 0 && elementIndex < elements.count()) {
617 ListElement *e = elements[elementIndex];
618
619 const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
620 if (r) {
621 roleIndex = e->setVariantProperty(*r, data);
622
623 ModelNodeMetaObject *cache = e->objectCache();
624
625 if (roleIndex != -1 && cache)
626 cache->updateValues(QVector<int>(1, roleIndex));
627 }
628 }
629
630 return roleIndex;
631}
632
633int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng)
634{
635 int roleIndex = -1;
636
637 if (elementIndex >= 0 && elementIndex < elements.count()) {
638 ListElement *e = elements[elementIndex];
639 const ListLayout::Role *r = m_layout->getExistingRole(key);
640 if (r)
641 roleIndex = e->setJsProperty(*r, data, eng);
642 }
643
644 return roleIndex;
645}
646
647inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
648{
649 ListElement *e = this;
650 int blockIndex = 0;
651 while (blockIndex < role.blockIndex) {
652 if (e->next == 0) {
653 e->next = new ListElement;
654 e->next->uid = uid;
655 }
656 e = e->next;
657 ++blockIndex;
658 }
659
660 char *mem = &e->data[role.blockOffset];
661 return mem;
662}
663
664ModelNodeMetaObject *ListElement::objectCache()
665{
666 if (!m_objectCache)
667 return 0;
668 return ModelNodeMetaObject::get(m_objectCache);
669}
670
671QString *ListElement::getStringProperty(const ListLayout::Role &role)
672{
673 char *mem = getPropertyMemory(role);
674 QString *s = reinterpret_cast<QString *>(mem);
675 return s->data_ptr() ? s : 0;
676}
677
678QObject *ListElement::getQObjectProperty(const ListLayout::Role &role)
679{
680 char *mem = getPropertyMemory(role);
681 QPointer<QObject> *o = reinterpret_cast<QPointer<QObject> *>(mem);
682 return o->data();
683}
684
685QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
686{
687 QVariantMap *map = 0;
688
689 char *mem = getPropertyMemory(role);
690 if (isMemoryUsed<QVariantMap>(mem))
691 map = reinterpret_cast<QVariantMap *>(mem);
692
693 return map;
694}
695
696QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
697{
698 QDateTime *dt = 0;
699
700 char *mem = getPropertyMemory(role);
701 if (isMemoryUsed<QDateTime>(mem))
702 dt = reinterpret_cast<QDateTime *>(mem);
703
704 return dt;
705}
706
707QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role)
708{
709 QJSValue *f = 0;
710
711 char *mem = getPropertyMemory(role);
712 if (isMemoryUsed<QJSValue>(mem))
713 f = reinterpret_cast<QJSValue *>(mem);
714
715 return f;
716}
717
718QPointer<QObject> *ListElement::getGuardProperty(const ListLayout::Role &role)
719{
720 char *mem = getPropertyMemory(role);
721
722 bool existingGuard = false;
723 for (size_t i=0 ; i < sizeof(QPointer<QObject>) ; ++i) {
724 if (mem[i] != 0) {
725 existingGuard = true;
726 break;
727 }
728 }
729
730 QPointer<QObject> *o = 0;
731
732 if (existingGuard)
733 o = reinterpret_cast<QPointer<QObject> *>(mem);
734
735 return o;
736}
737
738ListModel *ListElement::getListProperty(const ListLayout::Role &role)
739{
740 char *mem = getPropertyMemory(role);
741 ListModel **value = reinterpret_cast<ListModel **>(mem);
742 return *value;
743}
744
745QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
746{
747 char *mem = getPropertyMemory(role);
748
749 QVariant data;
750
751 switch (role.type) {
752 case ListLayout::Role::Number:
753 {
754 double *value = reinterpret_cast<double *>(mem);
755 data = *value;
756 }
757 break;
758 case ListLayout::Role::String:
759 {
760 QString *value = reinterpret_cast<QString *>(mem);
761 if (value->data_ptr() != 0)
762 data = *value;
763 }
764 break;
765 case ListLayout::Role::Bool:
766 {
767 bool *value = reinterpret_cast<bool *>(mem);
768 data = *value;
769 }
770 break;
771 case ListLayout::Role::List:
772 {
773 ListModel **value = reinterpret_cast<ListModel **>(mem);
774 ListModel *model = *value;
775
776 if (model) {
777 if (model->m_modelCache == 0) {
778 model->m_modelCache = new QQmlListModel(owner, model, eng);
779 QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
780 }
781
782 QObject *object = model->m_modelCache;
783 data = QVariant::fromValue(object);
784 }
785 }
786 break;
787 case ListLayout::Role::QObject:
788 {
789 QPointer<QObject> *guard = reinterpret_cast<QPointer<QObject> *>(mem);
790 QObject *object = guard->data();
791 if (object)
792 data = QVariant::fromValue(object);
793 }
794 break;
795 case ListLayout::Role::VariantMap:
796 {
797 if (isMemoryUsed<QVariantMap>(mem)) {
798 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
799 data = *map;
800 }
801 }
802 break;
803 case ListLayout::Role::DateTime:
804 {
805 if (isMemoryUsed<QDateTime>(mem)) {
806 QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
807 data = *dt;
808 }
809 }
810 break;
811 case ListLayout::Role::Function:
812 {
813 if (isMemoryUsed<QJSValue>(mem)) {
814 QJSValue *func = reinterpret_cast<QJSValue *>(mem);
815 data = QVariant::fromValue(*func);
816 }
817 }
818 break;
819 default:
820 break;
821 }
822
823 return data;
824}
825
826int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
827{
828 int roleIndex = -1;
829
830 if (role.type == ListLayout::Role::String) {
831 char *mem = getPropertyMemory(role);
832 QString *c = reinterpret_cast<QString *>(mem);
833 bool changed;
834 if (c->data_ptr() == 0) {
835 new (mem) QString(s);
836 changed = true;
837 } else {
838 changed = c->compare(s) != 0;
839 *c = s;
840 }
841 if (changed)
842 roleIndex = role.index;
843 }
844
845 return roleIndex;
846}
847
848int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
849{
850 int roleIndex = -1;
851
852 if (role.type == ListLayout::Role::Number) {
853 char *mem = getPropertyMemory(role);
854 double *value = reinterpret_cast<double *>(mem);
855 bool changed = *value != d;
856 *value = d;
857 if (changed)
858 roleIndex = role.index;
859 }
860
861 return roleIndex;
862}
863
864int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
865{
866 int roleIndex = -1;
867
868 if (role.type == ListLayout::Role::Bool) {
869 char *mem = getPropertyMemory(role);
870 bool *value = reinterpret_cast<bool *>(mem);
871 bool changed = *value != b;
872 *value = b;
873 if (changed)
874 roleIndex = role.index;
875 }
876
877 return roleIndex;
878}
879
880int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
881{
882 int roleIndex = -1;
883
884 if (role.type == ListLayout::Role::List) {
885 char *mem = getPropertyMemory(role);
886 ListModel **value = reinterpret_cast<ListModel **>(mem);
887 if (*value && *value != m) {
888 (*value)->destroy();
889 delete *value;
890 }
891 *value = m;
892 roleIndex = role.index;
893 }
894
895 return roleIndex;
896}
897
898int ListElement::setQObjectProperty(const ListLayout::Role &role, QObject *o)
899{
900 int roleIndex = -1;
901
902 if (role.type == ListLayout::Role::QObject) {
903 char *mem = getPropertyMemory(role);
904 QPointer<QObject> *g = reinterpret_cast<QPointer<QObject> *>(mem);
905 bool existingGuard = false;
906 for (size_t i=0 ; i < sizeof(QPointer<QObject>) ; ++i) {
907 if (mem[i] != 0) {
908 existingGuard = true;
909 break;
910 }
911 }
912 bool changed;
913 if (existingGuard) {
914 changed = g->data() != o;
915 g->~QPointer();
916 } else {
917 changed = true;
918 }
919 new (mem) QPointer<QObject>(o);
920 if (changed)
921 roleIndex = role.index;
922 }
923
924 return roleIndex;
925}
926
927int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o)
928{
929 int roleIndex = -1;
930
931 if (role.type == ListLayout::Role::VariantMap) {
932 char *mem = getPropertyMemory(role);
933 if (isMemoryUsed<QVariantMap>(mem)) {
934 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
935 map->~QMap();
936 }
937 new (mem) QVariantMap(o->engine()->variantMapFromJS(o));
938 roleIndex = role.index;
939 }
940
941 return roleIndex;
942}
943
944int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
945{
946 int roleIndex = -1;
947
948 if (role.type == ListLayout::Role::VariantMap) {
949 char *mem = getPropertyMemory(role);
950 if (isMemoryUsed<QVariantMap>(mem)) {
951 QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
952 map->~QMap();
953 }
954 if (m)
955 new (mem) QVariantMap(*m);
956 else
957 new (mem) QVariantMap;
958 roleIndex = role.index;
959 }
960
961 return roleIndex;
962}
963
964int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt)
965{
966 int roleIndex = -1;
967
968 if (role.type == ListLayout::Role::DateTime) {
969 char *mem = getPropertyMemory(role);
970 if (isMemoryUsed<QDateTime>(mem)) {
971 QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
972 dt->~QDateTime();
973 }
974 new (mem) QDateTime(dt);
975 roleIndex = role.index;
976 }
977
978 return roleIndex;
979}
980
981int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f)
982{
983 int roleIndex = -1;
984
985 if (role.type == ListLayout::Role::Function) {
986 char *mem = getPropertyMemory(role);
987 if (isMemoryUsed<QJSValue>(mem)) {
988 QJSValue *f = reinterpret_cast<QJSValue *>(mem);
989 f->~QJSValue();
990 }
991 new (mem) QJSValue(f);
992 roleIndex = role.index;
993 }
994
995 return roleIndex;
996}
997
998
999void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
1000{
1001 char *mem = getPropertyMemory(role);
1002 new (mem) QString(s);
1003}
1004
1005void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
1006{
1007 char *mem = getPropertyMemory(role);
1008 double *value = new (mem) double;
1009 *value = d;
1010}
1011
1012void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
1013{
1014 char *mem = getPropertyMemory(role);
1015 bool *value = new (mem) bool;
1016 *value = b;
1017}
1018
1019void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QObject *o)
1020{
1021 char *mem = getPropertyMemory(role);
1022 new (mem) QPointer<QObject>(o);
1023}
1024
1025void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
1026{
1027 char *mem = getPropertyMemory(role);
1028 ListModel **value = new (mem) ListModel *;
1029 *value = m;
1030}
1031
1032void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o)
1033{
1034 char *mem = getPropertyMemory(role);
1035 QVariantMap *map = new (mem) QVariantMap;
1036 *map = o->engine()->variantMapFromJS(o);
1037}
1038
1039void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt)
1040{
1041 char *mem = getPropertyMemory(role);
1042 new (mem) QDateTime(dt);
1043}
1044
1045void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f)
1046{
1047 char *mem = getPropertyMemory(role);
1048 new (mem) QJSValue(f);
1049}
1050
1051void ListElement::clearProperty(const ListLayout::Role &role)
1052{
1053 switch (role.type) {
1054 case ListLayout::Role::String:
1055 setStringProperty(role, QString());
1056 break;
1057 case ListLayout::Role::Number:
1058 setDoubleProperty(role, 0.0);
1059 break;
1060 case ListLayout::Role::Bool:
1061 setBoolProperty(role, false);
1062 break;
1063 case ListLayout::Role::List:
1064 setListProperty(role, 0);
1065 break;
1066 case ListLayout::Role::QObject:
1067 setQObjectProperty(role, 0);
1068 break;
1069 case ListLayout::Role::DateTime:
1070 setDateTimeProperty(role, QDateTime());
1071 break;
1072 case ListLayout::Role::VariantMap:
1073 setVariantMapProperty(role, (QVariantMap *)0);
1074 break;
1075 case ListLayout::Role::Function:
1076 setFunctionProperty(role, QJSValue());
1077 break;
1078 default:
1079 break;
1080 }
1081}
1082
1083ListElement::ListElement()
1084{
1085 m_objectCache = 0;
1086 uid = uidCounter.fetchAndAddOrdered(1);
1087 next = 0;
1088 memset(data, 0, sizeof(data));
1089}
1090
1091ListElement::ListElement(int existingUid)
1092{
1093 m_objectCache = 0;
1094 uid = existingUid;
1095 next = 0;
1096 memset(data, 0, sizeof(data));
1097}
1098
1099ListElement::~ListElement()
1100{
1101 delete next;
1102}
1103
1104void ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout, QHash<int, ListModel *> *targetModelHash)
1105{
1106 for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
1107 const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
1108 const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);
1109
1110 switch (srcRole.type) {
1111 case ListLayout::Role::List:
1112 {
1113 ListModel *srcSubModel = src->getListProperty(srcRole);
1114 ListModel *targetSubModel = target->getListProperty(targetRole);
1115
1116 if (srcSubModel) {
1117 if (targetSubModel == 0) {
1118 targetSubModel = new ListModel(targetRole.subLayout, 0, srcSubModel->getUid());
1119 target->setListPropertyFast(targetRole, targetSubModel);
1120 }
1121 ListModel::sync(srcSubModel, targetSubModel, targetModelHash);
1122 }
1123 }
1124 break;
1125 case ListLayout::Role::QObject:
1126 {
1127 QObject *object = src->getQObjectProperty(srcRole);
1128 target->setQObjectProperty(targetRole, object);
1129 }
1130 break;
1131 case ListLayout::Role::String:
1132 case ListLayout::Role::Number:
1133 case ListLayout::Role::Bool:
1134 case ListLayout::Role::DateTime:
1135 case ListLayout::Role::Function:
1136 {
1137 QVariant v = src->getProperty(srcRole, 0, 0);
1138 target->setVariantProperty(targetRole, v);
1139 }
1140 break;
1141 case ListLayout::Role::VariantMap:
1142 {
1143 QVariantMap *map = src->getVariantMapProperty(srcRole);
1144 target->setVariantMapProperty(targetRole, map);
1145 }
1146 break;
1147 default:
1148 break;
1149 }
1150 }
1151
1152}
1153
1154void ListElement::destroy(ListLayout *layout)
1155{
1156 if (layout) {
1157 for (int i=0 ; i < layout->roleCount() ; ++i) {
1158 const ListLayout::Role &r = layout->getExistingRole(i);
1159
1160 switch (r.type) {
1161 case ListLayout::Role::String:
1162 {
1163 QString *string = getStringProperty(r);
1164 if (string)
1165 string->~QString();
1166 }
1167 break;
1168 case ListLayout::Role::List:
1169 {
1170 ListModel *model = getListProperty(r);
1171 if (model) {
1172 model->destroy();
1173 delete model;
1174 }
1175 }
1176 break;
1177 case ListLayout::Role::QObject:
1178 {
1179 QPointer<QObject> *guard = getGuardProperty(r);
1180 if (guard)
1181 guard->~QPointer();
1182 }
1183 break;
1184 case ListLayout::Role::VariantMap:
1185 {
1186 QVariantMap *map = getVariantMapProperty(r);
1187 if (map)
1188 map->~QMap();
1189 }
1190 break;
1191 case ListLayout::Role::DateTime:
1192 {
1193 QDateTime *dt = getDateTimeProperty(r);
1194 if (dt)
1195 dt->~QDateTime();
1196 }
1197 break;
1198 case ListLayout::Role::Function:
1199 {
1200 QJSValue *f = getFunctionProperty(r);
1201 if (f)
1202 f->~QJSValue();
1203 }
1204 break;
1205 default:
1206 // other types don't need explicit cleanup.
1207 break;
1208 }
1209 }
1210
1211 delete m_objectCache;
1212 }
1213
1214 if (next)
1215 next->destroy(0);
1216 uid = -1;
1217}
1218
1219int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
1220{
1221 int roleIndex = -1;
1222
1223 switch (role.type) {
1224 case ListLayout::Role::Number:
1225 roleIndex = setDoubleProperty(role, d.toDouble());
1226 break;
1227 case ListLayout::Role::String:
1228 roleIndex = setStringProperty(role, d.toString());
1229 break;
1230 case ListLayout::Role::Bool:
1231 roleIndex = setBoolProperty(role, d.toBool());
1232 break;
1233 case ListLayout::Role::List:
1234 roleIndex = setListProperty(role, d.value<ListModel *>());
1235 break;
1236 case ListLayout::Role::VariantMap: {
1237 QVariantMap map = d.toMap();
1238 roleIndex = setVariantMapProperty(role, &map);
1239 }
1240 break;
1241 case ListLayout::Role::DateTime:
1242 roleIndex = setDateTimeProperty(role, d.toDateTime());
1243 break;
1244 case ListLayout::Role::Function:
1245 roleIndex = setFunctionProperty(role, d.value<QJSValue>());
1246 break;
1247 default:
1248 break;
1249 }
1250
1251 return roleIndex;
1252}
1253
1254int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng)
1255{
1256 // Check if this key exists yet
1257 int roleIndex = -1;
1258
1259 QV4::Scope scope(eng);
1260
1261 // Add the value now
1262 if (d.isString()) {
1263 QString qstr = d.toQString();
1264 roleIndex = setStringProperty(role, qstr);
1265 } else if (d.isNumber()) {
1266 roleIndex = setDoubleProperty(role, d.asDouble());
1267 } else if (d.as<QV4::ArrayObject>()) {
1268 QV4::ScopedArrayObject a(scope, d);
1269 if (role.type == ListLayout::Role::List) {
1270 QV4::Scope scope(a->engine());
1271 QV4::ScopedObject o(scope);
1272
1273 ListModel *subModel = new ListModel(role.subLayout, 0, -1);
1274 int arrayLength = a->getLength();
1275 for (int j=0 ; j < arrayLength ; ++j) {
1276 o = a->getIndexed(j);
1277 subModel->append(o);
1278 }
1279 roleIndex = setListProperty(role, subModel);
1280 } else {
1281 qmlWarning(0) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
1282 }
1283 } else if (d.isBoolean()) {
1284 roleIndex = setBoolProperty(role, d.booleanValue());
1285 } else if (d.as<QV4::DateObject>()) {
1286 QV4::Scoped<QV4::DateObject> dd(scope, d);
1287 QDateTime dt = dd->toQDateTime();
1288 roleIndex = setDateTimeProperty(role, dt);
1289 } else if (d.as<QV4::FunctionObject>()) {
1290 QV4::ScopedFunctionObject f(scope, d);
1291 QJSValue jsv;
1292 QJSValuePrivate::setValue(&jsv, eng, f);
1293 roleIndex = setFunctionProperty(role, jsv);
1294 } else if (d.isObject()) {
1295 QV4::ScopedObject o(scope, d);
1296 QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>();
1297 if (role.type == ListLayout::Role::QObject && wrapper) {
1298 QObject *o = wrapper->object();
1299 roleIndex = setQObjectProperty(role, o);
1300 } else if (role.type == ListLayout::Role::VariantMap) {
1301 roleIndex = setVariantMapProperty(role, o);
1302 }
1303 } else if (d.isNullOrUndefined()) {
1304 clearProperty(role);
1305 }
1306
1307 return roleIndex;
1308}
1309
1310ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex)
1311: QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false)
1312{}
1313
1314void ModelNodeMetaObject::initialize()
1315{
1316 const int roleCount = m_model->m_listModel->roleCount();
1317 QVector<QByteArray> properties;
1318 properties.reserve(roleCount);
1319 for (int i = 0 ; i < roleCount ; ++i) {
1320 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1321 QByteArray name = role.name.toUtf8();
1322 properties << name;
1323 }
1324 type()->createProperties(properties);
1325 updateValues();
1326 m_enabled = true;
1327}
1328
1329ModelNodeMetaObject::~ModelNodeMetaObject()
1330{
1331}
1332
1333QAbstractDynamicMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object)
1334{
1335 if (!m_initialized) {
1336 m_initialized = true;
1337 initialize();
1338 }
1339 return QQmlOpenMetaObject::toDynamicMetaObject(object);
1340}
1341
1342ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj)
1343{
1344 QObjectPrivate *op = QObjectPrivate::get(obj);
1345 return static_cast<ModelNodeMetaObject*>(op->metaObject);
1346}
1347
1348void ModelNodeMetaObject::updateValues()
1349{
1350 const int roleCount = m_model->m_listModel->roleCount();
1351 if (!m_initialized) {
1352 if (roleCount) {
1353 Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int));
1354 for (int i = 0; i < roleCount; ++i)
1355 changedRoles[i] = i;
1356 emitDirectNotifies(changedRoles, roleCount);
1357 }
1358 return;
1359 }
1360 for (int i=0 ; i < roleCount ; ++i) {
1361 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
1362 QByteArray name = role.name.toUtf8();
1363 const QVariant &data = m_model->data(m_elementIndex, i);
1364 setValue(name, data, role.type == ListLayout::Role::List);
1365 }
1366}
1367
1368void ModelNodeMetaObject::updateValues(const QVector<int> &roles)
1369{
1370 if (!m_initialized) {
1371 emitDirectNotifies(roles.constData(), roles.count());
1372 return;
1373 }
1374 int roleCount = roles.count();
1375 for (int i=0 ; i < roleCount ; ++i) {
1376 int roleIndex = roles.at(i);
1377 const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
1378 QByteArray name = role.name.toUtf8();
1379 const QVariant &data = m_model->data(m_elementIndex, roleIndex);
1380 setValue(name, data, role.type == ListLayout::Role::List);
1381 }
1382}
1383
1384void ModelNodeMetaObject::propertyWritten(int index)
1385{
1386 if (!m_enabled)
1387 return;
1388
1389 QString propName = QString::fromUtf8(name(index));
1390 QVariant value = operator[](index);
1391
1392 QV4::Scope scope(m_model->engine());
1393 QV4::ScopedValue v(scope, scope.engine->fromVariant(value));
1394
1395 int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine);
1396 if (roleIndex != -1)
1397 m_model->emitItemsChanged(m_elementIndex, 1, QVector<int>(1, roleIndex));
1398}
1399
1400// Does the emission of the notifiers when we haven't created the meta-object yet
1401void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount)
1402{
1403 Q_ASSERT(!m_initialized);
1404 QQmlData *ddata = QQmlData::get(object(), /*create*/false);
1405 if (!ddata)
1406 return;
1407 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine(m_model));
1408 if (!ep)
1409 return;
1410 for (int i = 0; i < roleCount; ++i) {
1411 const int changedRole = changedRoles[i];
1412 QQmlNotifier::notify(ddata, changedRole);
1413 }
1414}
1415
1416namespace QV4 {
1417
1418bool ModelObject::put(Managed *m, String *name, const Value &value)
1419{
1420 ModelObject *that = static_cast<ModelObject*>(m);
1421
1422 ExecutionEngine *eng = that->engine();
1423 const int elementIndex = that->d()->m_elementIndex;
1424 const QString propName = name->toQString();
1425 int roleIndex = that->d()->m_model->m_listModel->setExistingProperty(elementIndex, propName, value, eng);
1426 if (roleIndex != -1)
1427 that->d()->m_model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
1428
1429 ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object());
1430 if (mo->initialized())
1431 mo->emitPropertyNotification(name->toQString().toUtf8());
1432 return true;
1433}
1434
1435ReturnedValue ModelObject::get(const Managed *m, String *name, bool *hasProperty)
1436{
1437 const ModelObject *that = static_cast<const ModelObject*>(m);
1438 const ListLayout::Role *role = that->d()->m_model->m_listModel->getExistingRole(name);
1439 if (!role)
1440 return QObjectWrapper::get(m, name, hasProperty);
1441 if (hasProperty)
1442 *hasProperty = true;
1443
1444 if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) {
1445 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine);
1446 if (ep && ep->propertyCapture)
1447 ep->propertyCapture->captureProperty(that->object(), -1, role->index,
1448 QQmlPropertyCapture::OnlyOnce, false);
1449 }
1450
1451 const int elementIndex = that->d()->m_elementIndex;
1452 QVariant value = that->d()->m_model->data(elementIndex, role->index);
1453 return that->engine()->fromVariant(value);
1454}
1455
1456void ModelObject::advanceIterator(Managed *m, ObjectIterator *it, Value *name, uint *index, Property *p, PropertyAttributes *attributes)
1457{
1458 ModelObject *that = static_cast<ModelObject*>(m);
1459 ExecutionEngine *v4 = that->engine();
1460 name->setM(0);
1461 *index = UINT_MAX;
1462 if (it->arrayIndex < uint(that->d()->m_model->m_listModel->roleCount())) {
1463 Scope scope(that->engine());
1464 const ListLayout::Role &role = that->d()->m_model->m_listModel->getExistingRole(it->arrayIndex);
1465 ++it->arrayIndex;
1466 ScopedString roleName(scope, v4->newString(role.name));
1467 name->setM(roleName->d());
1468 *attributes = QV4::Attr_Data;
1469 QVariant value = that->d()->m_model->data(that->d()->m_elementIndex, role.index);
1470 p->value = v4->fromVariant(value);
1471 return;
1472 }
1473 // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add
1474 // unnecessary entries that relate to the roles used. These just create extra work
1475 // later on as they will just be ignored.
1476 QV4::Object::advanceIterator(m, it, name, index, p, attributes);
1477}
1478
1479DEFINE_OBJECT_VTABLE(ModelObject);
1480
1481} // namespace QV4
1482
1483DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
1484{
1485 setNodeUpdatesEnabled(true);
1486}
1487
1488DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner)
1489{
1490 DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
1491 QVector<int> roles;
1492 object->updateValues(obj, roles);
1493 return object;
1494}
1495
1496void DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target, QHash<int, QQmlListModel *> *targetModelHash)
1497{
1498 for (int i=0 ; i < src->m_meta->count() ; ++i) {
1499 const QByteArray &name = src->m_meta->name(i);
1500 QVariant value = src->m_meta->value(i);
1501
1502 QQmlListModel *srcModel = qobject_cast<QQmlListModel *>(value.value<QObject *>());
1503 QQmlListModel *targetModel = qobject_cast<QQmlListModel *>(target->m_meta->value(i).value<QObject *>());
1504
1505 if (srcModel) {
1506 if (targetModel == 0)
1507 targetModel = QQmlListModel::createWithOwner(target->m_owner);
1508
1509 QQmlListModel::sync(srcModel, targetModel, targetModelHash);
1510
1511 QObject *targetModelObject = targetModel;
1512 value = QVariant::fromValue(targetModelObject);
1513 } else if (targetModel) {
1514 delete targetModel;
1515 }
1516
1517 target->setValue(name, value);
1518 }
1519}
1520
1521void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int> &roles)
1522{
1523 for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) {
1524 const QString &key = it.key();
1525
1526 int roleIndex = m_owner->m_roles.indexOf(key);
1527 if (roleIndex == -1) {
1528 roleIndex = m_owner->m_roles.count();
1529 m_owner->m_roles.append(key);
1530 }
1531
1532 QVariant value = it.value();
1533
1534 // A JS array/object is translated into a (hierarchical) QQmlListModel,
1535 // so translate to a variant map/list first with toVariant().
1536 if (value.userType() == qMetaTypeId<QJSValue>())
1537 value = value.value<QJSValue>().toVariant();
1538
1539 if (value.type() == QVariant::List) {
1540 QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);
1541
1542 QVariantList subArray = value.toList();
1543 QVariantList::const_iterator subIt = subArray.cbegin();
1544 QVariantList::const_iterator subEnd = subArray.cend();
1545 while (subIt != subEnd) {
1546 const QVariantMap &subObject = subIt->toMap();
1547 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1548 ++subIt;
1549 }
1550
1551 QObject *subModelObject = subModel;
1552 value = QVariant::fromValue(subModelObject);
1553 }
1554
1555 const QByteArray &keyUtf8 = key.toUtf8();
1556
1557 QQmlListModel *existingModel = qobject_cast<QQmlListModel *>(m_meta->value(keyUtf8).value<QObject *>());
1558 delete existingModel;
1559
1560 if (m_meta->setValue(keyUtf8, value))
1561 roles << roleIndex;
1562 }
1563}
1564
1565DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
1566 : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
1567{
1568}
1569
1570DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
1571{
1572 for (int i=0 ; i < count() ; ++i) {
1573 QQmlListModel *subModel = qobject_cast<QQmlListModel *>(value(i).value<QObject *>());
1574 delete subModel;
1575 }
1576}
1577
1578void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
1579{
1580 if (!m_enabled)
1581 return;
1582
1583 QVariant v = value(index);
1584 QQmlListModel *model = qobject_cast<QQmlListModel *>(v.value<QObject *>());
1585 delete model;
1586}
1587
1588void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
1589{
1590 if (!m_enabled)
1591 return;
1592
1593 QQmlListModel *parentModel = m_owner->m_owner;
1594
1595 QVariant v = value(index);
1596
1597 // A JS array/object is translated into a (hierarchical) QQmlListModel,
1598 // so translate to a variant map/list first with toVariant().
1599 if (v.userType() == qMetaTypeId<QJSValue>())
1600 v= v.value<QJSValue>().toVariant();
1601
1602 if (v.type() == QVariant::List) {
1603 QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);
1604
1605 QVariantList subArray = v.toList();
1606 QVariantList::const_iterator subIt = subArray.cbegin();
1607 QVariantList::const_iterator subEnd = subArray.cend();
1608 while (subIt != subEnd) {
1609 const QVariantMap &subObject = subIt->toMap();
1610 subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
1611 ++subIt;
1612 }
1613
1614 QObject *subModelObject = subModel;
1615 v = QVariant::fromValue(subModelObject);
1616
1617 setValue(index, v);
1618 }
1619
1620 int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
1621 if (elementIndex != -1) {
1622 int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
1623 if (roleIndex != -1)
1624 parentModel->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
1625 }
1626}
1627
1628/*!
1629 \qmltype ListModel
1630 \instantiates QQmlListModel
1631 \inqmlmodule QtQml.Models
1632 \ingroup qtquick-models
1633 \brief Defines a free-form list data source
1634
1635 The ListModel is a simple container of ListElement definitions, each
1636 containing data roles. The contents can be defined dynamically, or
1637 explicitly in QML.
1638
1639 The number of elements in the model can be obtained from its \l count property.
1640 A number of familiar methods are also provided to manipulate the contents of the
1641 model, including append(), insert(), move(), remove() and set(). These methods
1642 accept dictionaries as their arguments; these are translated to ListElement objects
1643 by the model.
1644
1645 Elements can be manipulated via the model using the setProperty() method, which
1646 allows the roles of the specified element to be set and changed.
1647
1648 \section1 Example Usage
1649
1650 The following example shows a ListModel containing three elements, with the roles
1651 "name" and "cost".
1652
1653 \div {class="float-right"}
1654 \inlineimage listmodel.png
1655 \enddiv
1656
1657 \snippet qml/listmodel/listmodel.qml 0
1658
1659 Roles (properties) in each element must begin with a lower-case letter and
1660 should be common to all elements in a model. The ListElement documentation
1661 provides more guidelines for how elements should be defined.
1662
1663 Since the example model contains an \c id property, it can be referenced
1664 by views, such as the ListView in this example:
1665
1666 \snippet qml/listmodel/listmodel-simple.qml 0
1667 \dots 8
1668 \snippet qml/listmodel/listmodel-simple.qml 1
1669
1670 It is possible for roles to contain list data. In the following example we
1671 create a list of fruit attributes:
1672
1673 \snippet qml/listmodel/listmodel-nested.qml model
1674
1675 The delegate displays all the fruit attributes:
1676
1677 \div {class="float-right"}
1678 \inlineimage listmodel-nested.png
1679 \enddiv
1680
1681 \snippet qml/listmodel/listmodel-nested.qml delegate
1682
1683 \section1 Modifying List Models
1684
1685 The content of a ListModel may be created and modified using the clear(),
1686 append(), set(), insert() and setProperty() methods. For example:
1687
1688 \snippet qml/listmodel/listmodel-modify.qml delegate
1689
1690 Note that when creating content dynamically the set of available properties
1691 cannot be changed once set. Whatever properties are first added to the model
1692 are the only permitted properties in the model.
1693
1694 \section1 Using Threaded List Models with WorkerScript
1695
1696 ListModel can be used together with WorkerScript access a list model
1697 from multiple threads. This is useful if list modifications are
1698 synchronous and take some time: the list operations can be moved to a
1699 different thread to avoid blocking of the main GUI thread.
1700
1701 Here is an example that uses WorkerScript to periodically append the
1702 current time to a list model:
1703
1704 \snippet ../quick/threading/threadedlistmodel/timedisplay.qml 0
1705
1706 The included file, \tt dataloader.js, looks like this:
1707
1708 \snippet ../quick/threading/threadedlistmodel/dataloader.js 0
1709
1710 The timer in the main example sends messages to the worker script by calling
1711 \l WorkerScript::sendMessage(). When this message is received,
1712 \c WorkerScript.onMessage() is invoked in \c dataloader.js,
1713 which appends the current time to the list model.
1714
1715 Note the call to sync() from the external thread.
1716 You must call sync() or else the changes made to the list from that
1717 thread will not be reflected in the list model in the main thread.
1718
1719 \sa {qml-data-models}{Data Models}, {Qt Quick Examples - Threading}, {Qt QML}
1720*/
1721
1722QQmlListModel::QQmlListModel(QObject *parent)
1723: QAbstractListModel(parent)
1724{
1725 m_mainThread = true;
1726 m_primary = true;
1727 m_agent = 0;
1728 m_uid = uidCounter.fetchAndAddOrdered(1);
1729 m_dynamicRoles = false;
1730
1731 m_layout = new ListLayout;
1732 m_listModel = new ListModel(m_layout, this, -1);
1733
1734 m_engine = 0;
1735}
1736
1737QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent)
1738: QAbstractListModel(parent)
1739{
1740 m_mainThread = owner->m_mainThread;
1741 m_primary = false;
1742 m_agent = owner->m_agent;
1743
1744 Q_ASSERT(owner->m_dynamicRoles == false);
1745 m_dynamicRoles = false;
1746 m_layout = 0;
1747 m_listModel = data;
1748
1749 m_engine = engine;
1750}
1751
1752QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent)
1753: QAbstractListModel(agent)
1754{
1755 m_mainThread = false;
1756 m_primary = true;
1757 m_agent = agent;
1758 m_dynamicRoles = orig->m_dynamicRoles;
1759
1760 m_layout = new ListLayout(orig->m_layout);
1761 m_listModel = new ListModel(m_layout, this, orig->m_listModel->getUid());
1762
1763 if (m_dynamicRoles)
1764 sync(orig, this, 0);
1765 else
1766 ListModel::sync(orig->m_listModel, m_listModel, 0);
1767
1768 m_engine = 0;
1769}
1770
1771QQmlListModel::~QQmlListModel()
1772{
1773 qDeleteAll(m_modelObjects);
1774
1775 if (m_primary) {
1776 m_listModel->destroy();
1777 delete m_listModel;
1778
1779 if (m_mainThread && m_agent) {
1780 m_agent->modelDestroyed();
1781 m_agent->release();
1782 }
1783 }
1784
1785 m_listModel = 0;
1786
1787 delete m_layout;
1788 m_layout = 0;
1789}
1790
1791QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
1792{
1793 QQmlListModel *model = new QQmlListModel;
1794
1795 model->m_mainThread = newOwner->m_mainThread;
1796 model->m_engine = newOwner->m_engine;
1797 model->m_agent = newOwner->m_agent;
1798 model->m_dynamicRoles = newOwner->m_dynamicRoles;
1799
1800 if (model->m_mainThread && model->m_agent)
1801 model->m_agent->addref();
1802
1803 QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));
1804
1805 return model;
1806}
1807
1808QV4::ExecutionEngine *QQmlListModel::engine() const
1809{
1810 if (m_engine == 0) {
1811 m_engine = QQmlEnginePrivate::get(qmlEngine(this))->v4engine();
1812 }
1813
1814 return m_engine;
1815}
1816
1817void QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target, QHash<int, QQmlListModel *> *targetModelHash)
1818{
1819 Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);
1820
1821 target->m_uid = src->m_uid;
1822 if (targetModelHash)
1823 targetModelHash->insert(target->m_uid, target);
1824 target->m_roles = src->m_roles;
1825
1826 // Build hash of elements <-> uid for each of the lists
1827 QHash<int, ElementSync> elementHash;
1828 for (int i=0 ; i < target->m_modelObjects.count() ; ++i) {
1829 DynamicRoleModelNode *e = target->m_modelObjects.at(i);
1830 int uid = e->getUid();
1831 ElementSync sync;
1832 sync.target = e;
1833 elementHash.insert(uid, sync);
1834 }
1835 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1836 DynamicRoleModelNode *e = src->m_modelObjects.at(i);
1837 int uid = e->getUid();
1838
1839 QHash<int, ElementSync>::iterator it = elementHash.find(uid);
1840 if (it == elementHash.end()) {
1841 ElementSync sync;
1842 sync.src = e;
1843 elementHash.insert(uid, sync);
1844 } else {
1845 ElementSync &sync = it.value();
1846 sync.src = e;
1847 }
1848 }
1849
1850 // Get list of elements that are in the target but no longer in the source. These get deleted first.
1851 QHash<int, ElementSync>::iterator it = elementHash.begin();
1852 QHash<int, ElementSync>::iterator end = elementHash.end();
1853 while (it != end) {
1854 const ElementSync &s = it.value();
1855 if (s.src == 0) {
1856 int targetIndex = target->m_modelObjects.indexOf(s.target);
1857 target->m_modelObjects.remove(targetIndex, 1);
1858 delete s.target;
1859 }
1860 ++it;
1861 }
1862
1863 // Clear the target list, and append in correct order from the source
1864 target->m_modelObjects.clear();
1865 for (int i=0 ; i < src->m_modelObjects.count() ; ++i) {
1866 DynamicRoleModelNode *srcElement = src->m_modelObjects.at(i);
1867 it = elementHash.find(srcElement->getUid());
1868 const ElementSync &s = it.value();
1869 DynamicRoleModelNode *targetElement = s.target;
1870 if (targetElement == 0) {
1871 targetElement = new DynamicRoleModelNode(target, srcElement->getUid());
1872 }
1873 DynamicRoleModelNode::sync(srcElement, targetElement, targetModelHash);
1874 target->m_modelObjects.append(targetElement);
1875 }
1876}
1877
1878void QQmlListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
1879{
1880 if (count <= 0)
1881 return;
1882
1883 if (m_mainThread) {
1884 emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
1885 } else {
1886 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1887 m_agent->data.changedChange(uid, index, count, roles);
1888 }
1889}
1890
1891void QQmlListModel::emitItemsAboutToBeRemoved(int index, int count)
1892{
1893 if (count <= 0 || !m_mainThread)
1894 return;
1895
1896 beginRemoveRows(QModelIndex(), index, index + count - 1);
1897}
1898
1899void QQmlListModel::emitItemsRemoved(int index, int count)
1900{
1901 if (count <= 0)
1902 return;
1903
1904 if (m_mainThread) {
1905 endRemoveRows();
1906 emit countChanged();
1907 } else {
1908 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1909 if (index == 0 && count == this->count())
1910 m_agent->data.clearChange(uid);
1911 m_agent->data.removeChange(uid, index, count);
1912 }
1913}
1914
1915void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
1916{
1917 if (count <= 0 || !m_mainThread)
1918 return;
1919
1920 beginInsertRows(QModelIndex(), index, index + count - 1);
1921}
1922
1923void QQmlListModel::emitItemsInserted(int index, int count)
1924{
1925 if (count <= 0)
1926 return;
1927
1928 if (m_mainThread) {
1929 endInsertRows();
1930 emit countChanged();
1931 } else {
1932 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1933 m_agent->data.insertChange(uid, index, count);
1934 }
1935}
1936
1937void QQmlListModel::emitItemsAboutToBeMoved(int from, int to, int n)
1938{
1939 if (n <= 0 || !m_mainThread)
1940 return;
1941
1942 beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);
1943}
1944
1945void QQmlListModel::emitItemsMoved(int from, int to, int n)
1946{
1947 if (n <= 0)
1948 return;
1949
1950 if (m_mainThread) {
1951 endMoveRows();
1952 } else {
1953 int uid = m_dynamicRoles ? getUid() : m_listModel->getUid();
1954 m_agent->data.moveChange(uid, from, n, to);
1955 }
1956}
1957
1958QQmlListModelWorkerAgent *QQmlListModel::agent()
1959{
1960 if (m_agent)
1961 return m_agent;
1962
1963 m_agent = new QQmlListModelWorkerAgent(this);
1964 return m_agent;
1965}
1966
1967QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const
1968{
1969 return row >= 0 && row < count() && column == 0 && !parent.isValid()
1970 ? createIndex(row, column)
1971 : QModelIndex();
1972}
1973
1974int QQmlListModel::rowCount(const QModelIndex &parent) const
1975{
1976 return !parent.isValid() ? count() : 0;
1977}
1978
1979QVariant QQmlListModel::data(const QModelIndex &index, int role) const
1980{
1981 return data(index.row(), role);
1982}
1983
1984bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
1985{
1986 const int row = index.row();
1987 if (row >= count() || row < 0)
1988 return false;
1989
1990 if (m_dynamicRoles) {
1991 const QByteArray property = m_roles.at(role).toUtf8();
1992 if (m_modelObjects[row]->setValue(property, value)) {
1993 emitItemsChanged(row, 1, QVector<int>(1, role));
1994 return true;
1995 }
1996 } else {
1997 const ListLayout::Role &r = m_listModel->getExistingRole(role);
1998 const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value);
1999 if (roleIndex != -1) {
2000 emitItemsChanged(row, 1, QVector<int>(1, role));
2001 return true;
2002 }
2003 }
2004
2005 return false;
2006}
2007
2008QVariant QQmlListModel::data(int index, int role) const
2009{
2010 QVariant v;
2011
2012 if (index >= count() || index < 0)
2013 return v;
2014
2015 if (m_dynamicRoles)
2016 v = m_modelObjects[index]->getValue(m_roles[role]);
2017 else
2018 v = m_listModel->getProperty(index, role, this, engine());
2019
2020 return v;
2021}
2022
2023QHash<int, QByteArray> QQmlListModel::roleNames() const
2024{
2025 QHash<int, QByteArray> roleNames;
2026
2027 if (m_dynamicRoles) {
2028 for (int i = 0 ; i < m_roles.count() ; ++i)
2029 roleNames.insert(i, m_roles.at(i).toUtf8());
2030 } else {
2031 for (int i = 0 ; i < m_listModel->roleCount() ; ++i) {
2032 const ListLayout::Role &r = m_listModel->getExistingRole(i);
2033 roleNames.insert(i, r.name.toUtf8());
2034 }
2035 }
2036
2037 return roleNames;
2038}
2039
2040/*!
2041 \qmlproperty bool ListModel::dynamicRoles
2042
2043 By default, the type of a role is fixed the first time
2044 the role is used. For example, if you create a role called
2045 "data" and assign a number to it, you can no longer assign
2046 a string to the "data" role. However, when the dynamicRoles
2047 property is enabled, the type of a given role is not fixed
2048 and can be different between elements.
2049
2050 The dynamicRoles property must be set before any data is
2051 added to the ListModel, and must be set from the main
2052 thread.
2053
2054 A ListModel that has data statically defined (via the
2055 ListElement QML syntax) cannot have the dynamicRoles
2056 property enabled.
2057
2058 There is a significant performance cost to using a
2059 ListModel with dynamic roles enabled. The cost varies
2060 from platform to platform but is typically somewhere
2061 between 4-6x slower than using static role types.
2062
2063 Due to the performance cost of using dynamic roles,
2064 they are disabled by default.
2065*/
2066void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
2067{
2068 if (m_mainThread && m_agent == 0) {
2069 if (enableDynamicRoles) {
2070 if (m_layout->roleCount())
2071 qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
2072 else
2073 m_dynamicRoles = true;
2074 } else {
2075 if (m_roles.count()) {
2076 qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
2077 } else {
2078 m_dynamicRoles = false;
2079 }
2080 }
2081 } else {
2082 qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
2083 }
2084}
2085
2086/*!
2087 \qmlproperty int ListModel::count
2088 The number of data entries in the model.
2089*/
2090int QQmlListModel::count() const
2091{
2092 return m_dynamicRoles ? m_modelObjects.count() : m_listModel->elementCount();
2093}
2094
2095/*!
2096 \qmlmethod ListModel::clear()
2097
2098 Deletes all content from the model.
2099
2100 \sa append(), remove()
2101*/
2102void QQmlListModel::clear()
2103{
2104 removeElements(0, count());
2105}
2106
2107/*!
2108 \qmlmethod ListModel::remove(int index, int count = 1)
2109
2110 Deletes the content at \a index from the model.
2111
2112 \sa clear()
2113*/
2114void QQmlListModel::remove(QQmlV4Function *args)
2115{
2116 int argLength = args->length();
2117
2118 if (argLength == 1 || argLength == 2) {
2119 QV4::Scope scope(args->v4engine());
2120 int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
2121 int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);
2122
2123 if (index < 0 || index+removeCount > count() || removeCount <= 0) {
2124 qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
2125 return;
2126 }
2127
2128 removeElements(index, removeCount);
2129 } else {
2130 qmlWarning(this) << tr("remove: incorrect number of arguments");
2131 }
2132}
2133
2134void QQmlListModel::removeElements(int index, int removeCount)
2135{
2136 emitItemsAboutToBeRemoved(index, removeCount);
2137
2138 QVector<std::function<void()>> toDestroy;
2139 if (m_dynamicRoles) {
2140 for (int i=0 ; i < removeCount ; ++i) {
2141 auto modelObject = m_modelObjects[index+i];
2142 toDestroy.append([modelObject](){
2143 delete modelObject;
2144 });
2145 }
2146 m_modelObjects.remove(index, removeCount);
2147 } else {
2148 toDestroy = m_listModel->remove(index, removeCount);
2149 }
2150
2151 emitItemsRemoved(index, removeCount);
2152 for (const auto &destroyer : toDestroy)
2153 destroyer();
2154}
2155
2156/*!
2157 \qmlmethod ListModel::insert(int index, jsobject dict)
2158
2159 Adds a new item to the list model at position \a index, with the
2160 values in \a dict.
2161
2162 \code
2163 fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
2164 \endcode
2165
2166 The \a index must be to an existing item in the list, or one past
2167 the end of the list (equivalent to append).
2168
2169 \sa set(), append()
2170*/
2171
2172void QQmlListModel::insert(QQmlV4Function *args)
2173{
2174 if (args->length() == 2) {
2175 QV4::Scope scope(args->v4engine());
2176 QV4::ScopedValue arg0(scope, (*args)[0]);
2177 int index = arg0->toInt32();
2178
2179 if (index < 0 || index > count()) {
2180 qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
2181 return;
2182 }
2183
2184 QV4::ScopedObject argObject(scope, (*args)[1]);
2185 QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
2186 if (objectArray) {
2187 QV4::ScopedObject argObject(scope);
2188
2189 int objectArrayLength = objectArray->getLength();
2190 emitItemsAboutToBeInserted(index, objectArrayLength);
2191 for (int i=0 ; i < objectArrayLength ; ++i) {
2192 argObject = objectArray->getIndexed(i);
2193
2194 if (m_dynamicRoles) {
2195 m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2196 } else {
2197 m_listModel->insert(index+i, argObject);
2198 }
2199 }
2200 emitItemsInserted(index, objectArrayLength);
2201 } else if (argObject) {
2202 emitItemsAboutToBeInserted(index, 1);
2203
2204 if (m_dynamicRoles) {
2205 m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2206 } else {
2207 m_listModel->insert(index, argObject);
2208 }
2209
2210 emitItemsInserted(index, 1);
2211 } else {
2212 qmlWarning(this) << tr("insert: value is not an object");
2213 }
2214 } else {
2215 qmlWarning(this) << tr("insert: value is not an object");
2216 }
2217}
2218
2219/*!
2220 \qmlmethod ListModel::move(int from, int to, int n)
2221
2222 Moves \a n items \a from one position \a to another.
2223
2224 The from and to ranges must exist; for example, to move the first 3 items
2225 to the end of the list:
2226
2227 \code
2228 fruitModel.move(0, fruitModel.count - 3, 3)
2229 \endcode
2230
2231 \sa append()
2232*/
2233void QQmlListModel::move(int from, int to, int n)
2234{
2235 if (n==0 || from==to)
2236 return;
2237 if (!canMove(from, to, n)) {
2238 qmlWarning(this) << tr("move: out of range");
2239 return;
2240 }
2241
2242 emitItemsAboutToBeMoved(from, to, n);
2243
2244 if (m_dynamicRoles) {
2245
2246 int realFrom = from;
2247 int realTo = to;
2248 int realN = n;
2249
2250 if (from > to) {
2251 // Only move forwards - flip if backwards moving
2252 int tfrom = from;
2253 int tto = to;
2254 realFrom = tto;
2255 realTo = tto+n;
2256 realN = tfrom-tto;
2257 }
2258
2259 QPODVector<DynamicRoleModelNode *, 4> store;
2260 for (int i=0 ; i < (realTo-realFrom) ; ++i)
2261 store.append(m_modelObjects[realFrom+realN+i]);
2262 for (int i=0 ; i < realN ; ++i)
2263 store.append(m_modelObjects[realFrom+i]);
2264 for (int i=0 ; i < store.count() ; ++i)
2265 m_modelObjects[realFrom+i] = store[i];
2266
2267 } else {
2268 m_listModel->move(from, to, n);
2269 }
2270
2271 emitItemsMoved(from, to, n);
2272}
2273
2274/*!
2275 \qmlmethod ListModel::append(jsobject dict)
2276
2277 Adds a new item to the end of the list model, with the
2278 values in \a dict.
2279
2280 \code
2281 fruitModel.append({"cost": 5.95, "name":"Pizza"})
2282 \endcode
2283
2284 \sa set(), remove()
2285*/
2286void QQmlListModel::append(QQmlV4Function *args)
2287{
2288 if (args->length() == 1) {
2289 QV4::Scope scope(args->v4engine());
2290 QV4::ScopedObject argObject(scope, (*args)[0]);
2291 QV4::ScopedArrayObject objectArray(scope, (*args)[0]);
2292
2293 if (objectArray) {
2294 QV4::ScopedObject argObject(scope);
2295
2296 int objectArrayLength = objectArray->getLength();
2297
2298 int index = count();
2299 emitItemsAboutToBeInserted(index, objectArrayLength);
2300
2301 for (int i=0 ; i < objectArrayLength ; ++i) {
2302 argObject = objectArray->getIndexed(i);
2303
2304 if (m_dynamicRoles) {
2305 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2306 } else {
2307 m_listModel->append(argObject);
2308 }
2309 }
2310
2311 emitItemsInserted(index, objectArrayLength);
2312 } else if (argObject) {
2313 int index;
2314
2315 if (m_dynamicRoles) {
2316 index = m_modelObjects.count();
2317 emitItemsAboutToBeInserted(index, 1);
2318 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
2319 } else {
2320 index = m_listModel->elementCount();
2321 emitItemsAboutToBeInserted(index, 1);
2322 m_listModel->append(argObject);
2323 }
2324
2325 emitItemsInserted(index, 1);
2326 } else {
2327 qmlWarning(this) << tr("append: value is not an object");
2328 }
2329 } else {
2330 qmlWarning(this) << tr("append: value is not an object");
2331 }
2332}
2333
2334/*!
2335 \qmlmethod object ListModel::get(int index)
2336
2337 Returns the item at \a index in the list model. This allows the item
2338 data to be accessed or modified from JavaScript:
2339
2340 \code
2341 Component.onCompleted: {
2342 fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
2343 console.log(fruitModel.get(0).cost);
2344 fruitModel.get(0).cost = 10.95;
2345 }
2346 \endcode
2347
2348 The \a index must be an element in the list.
2349
2350 Note that properties of the returned object that are themselves objects
2351 will also be models, and this get() method is used to access elements:
2352
2353 \code
2354 fruitModel.append(..., "attributes":
2355 [{"name":"spikes","value":"7mm"},
2356 {"name":"color","value":"green"}]);
2357 fruitModel.get(0).attributes.get(1).value; // == "green"
2358 \endcode
2359
2360 \warning The returned object is not guaranteed to remain valid. It
2361 should not be used in \l{Property Binding}{property bindings}.
2362
2363 \sa append()
2364*/
2365QQmlV4Handle QQmlListModel::get(int index) const
2366{
2367 QV4::Scope scope(engine());
2368 QV4::ScopedValue result(scope, QV4::Primitive::undefinedValue());
2369
2370 if (index >= 0 && index < count()) {
2371
2372 if (m_dynamicRoles) {
2373 DynamicRoleModelNode *object = m_modelObjects[index];
2374 result = QV4::QObjectWrapper::wrap(scope.engine, object);
2375 } else {
2376 QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
2377 result = scope.engine->memoryManager->allocObject<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this), index);
2378 // Keep track of the QObjectWrapper in persistent value storage
2379 QV4::Value *val = scope.engine->memoryManager->m_weakValues->allocate();
2380 *val = result;
2381 }
2382 }
2383
2384 return QQmlV4Handle(result);
2385}
2386
2387/*!
2388 \qmlmethod ListModel::set(int index, jsobject dict)
2389
2390 Changes the item at \a index in the list model with the
2391 values in \a dict. Properties not appearing in \a dict
2392 are left unchanged.
2393
2394 \code
2395 fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
2396 \endcode
2397
2398 If \a index is equal to count() then a new item is appended to the
2399 list. Otherwise, \a index must be an element in the list.
2400
2401 \sa append()
2402*/
2403void QQmlListModel::set(int index, const QQmlV4Handle &handle)
2404{
2405 QV4::Scope scope(engine());
2406 QV4::ScopedObject object(scope, handle);
2407
2408 if (!object) {
2409 qmlWarning(this) << tr("set: value is not an object");
2410 return;
2411 }
2412 if (index > count() || index < 0) {
2413 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2414 return;
2415 }
2416
2417
2418 if (index == count()) {
2419 emitItemsAboutToBeInserted(index, 1);
2420
2421 if (m_dynamicRoles) {
2422 m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
2423 } else {
2424 m_listModel->insert(index, object);
2425 }
2426
2427 emitItemsInserted(index, 1);
2428 } else {
2429
2430 QVector<int> roles;
2431
2432 if (m_dynamicRoles) {
2433 m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
2434 } else {
2435 m_listModel->set(index, object, &roles);
2436 }
2437
2438 if (roles.count())
2439 emitItemsChanged(index, 1, roles);
2440 }
2441}
2442
2443/*!
2444 \qmlmethod ListModel::setProperty(int index, string property, variant value)
2445
2446 Changes the \a property of the item at \a index in the list model to \a value.
2447
2448 \code
2449 fruitModel.setProperty(3, "cost", 5.95)
2450 \endcode
2451
2452 The \a index must be an element in the list.
2453
2454 \sa append()
2455*/
2456void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
2457{
2458 if (count() == 0 || index >= count() || index < 0) {
2459 qmlWarning(this) << tr("set: index %1 out of range").arg(index);
2460 return;
2461 }
2462
2463 if (m_dynamicRoles) {
2464 int roleIndex = m_roles.indexOf(property);
2465 if (roleIndex == -1) {
2466 roleIndex = m_roles.count();
2467 m_roles.append(property);
2468 }
2469 if (m_modelObjects[index]->setValue(property.toUtf8(), value))
2470 emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
2471 } else {
2472 int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
2473 if (roleIndex != -1)
2474 emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
2475 }
2476}
2477
2478/*!
2479 \qmlmethod ListModel::sync()
2480
2481 Writes any unsaved changes to the list model after it has been modified
2482 from a worker script.
2483*/
2484void QQmlListModel::sync()
2485{
2486 // This is just a dummy method to make it look like sync() exists in
2487 // ListModel (and not just QQmlListModelWorkerAgent) and to let
2488 // us document sync().
2489 qmlWarning(this) << "List sync() can only be called from a WorkerScript";
2490}
2491
2492bool QQmlListModelParser::verifyProperty(const QV4::CompiledData::Unit *qmlUnit, const QV4::CompiledData::Binding *binding)
2493{
2494 if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
2495 const quint32 targetObjectIndex = binding->value.objectIndex;
2496 const QV4::CompiledData::Object *target = qmlUnit->objectAt(targetObjectIndex);
2497 QString objName = qmlUnit->stringAt(target->inheritedTypeNameIndex);
2498 if (objName != listElementTypeName) {
2499 const QMetaObject *mo = resolveType(objName);
2500 if (mo != &QQmlListElement::staticMetaObject) {
2501 error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2502 return false;
2503 }
2504 listElementTypeName = objName; // cache right name for next time
2505 }
2506
2507 if (!qmlUnit->stringAt(target->idNameIndex).isEmpty()) {
2508 error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
2509 return false;
2510 }
2511
2512 const QV4::CompiledData::Binding *binding = target->bindingTable();
2513 for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
2514 QString propName = qmlUnit->stringAt(binding->propertyNameIndex);
2515 if (propName.isEmpty()) {
2516 error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
2517 return false;
2518 }
2519 if (!verifyProperty(qmlUnit, binding))
2520 return false;
2521 }
2522 } else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
2523 QString scriptStr = binding->valueAsScriptString(qmlUnit);
2524 if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
2525 QByteArray script = scriptStr.toUtf8();
2526 bool ok;
2527 evaluateEnum(script, &ok);
2528 if (!ok) {
2529 error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
2530 return false;
2531 }
2532 }
2533 }
2534
2535 return true;
2536}
2537
2538bool QQmlListModelParser::applyProperty(QV4::CompiledData::CompilationUnit *compilationUnit, const QV4::CompiledData::Unit *qmlUnit, const QV4::CompiledData::Binding *binding, ListModel *model, int outterElementIndex)
2539{
2540 const QString elementName = qmlUnit->stringAt(binding->propertyNameIndex);
2541
2542 bool roleSet = false;
2543 if (binding->type >= QV4::CompiledData::Binding::Type_Object) {
2544 const quint32 targetObjectIndex = binding->value.objectIndex;
2545 const QV4::CompiledData::Object *target = qmlUnit->objectAt(targetObjectIndex);
2546
2547 ListModel *subModel = 0;
2548 if (outterElementIndex == -1) {
2549 subModel = model;
2550 } else {
2551 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2552 if (role.type == ListLayout::Role::List) {
2553 subModel = model->getListProperty(outterElementIndex, role);
2554 if (subModel == 0) {
2555 subModel = new ListModel(role.subLayout, 0, -1);
2556 QVariant vModel = QVariant::fromValue(subModel);
2557 model->setOrCreateProperty(outterElementIndex, elementName, vModel);
2558 }
2559 }
2560 }
2561
2562 int elementIndex = subModel ? subModel->appendElement() : -1;
2563
2564 const QV4::CompiledData::Binding *subBinding = target->bindingTable();
2565 for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
2566 roleSet |= applyProperty(compilationUnit, qmlUnit, subBinding, subModel, elementIndex);
2567 }
2568
2569 } else {
2570 QVariant value;
2571
2572 if (binding->evaluatesToString()) {
2573 value = binding->valueAsString(qmlUnit);
2574 } else if (binding->type == QV4::CompiledData::Binding::Type_Number) {
2575 value = binding->valueAsNumber();
2576 } else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) {
2577 value = binding->valueAsBoolean();
2578 } else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
2579 QString scriptStr = binding->valueAsScriptString(qmlUnit);
2580 if (definesEmptyList(scriptStr)) {
2581 const ListLayout::Role &role = model->getOrCreateListRole(elementName);
2582 ListModel *emptyModel = new ListModel(role.subLayout, 0, -1);
2583 value = QVariant::fromValue(emptyModel);
2584 } else if (binding->isFunctionExpression()) {
2585 QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
2586 Q_ASSERT(id != QQmlBinding::Invalid);
2587
2588 auto v4 = compilationUnit->engine;
2589 QV4::Scope scope(v4);
2590 // for now we do not provide a context object; data from the ListElement must be passed to the function
2591 QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
2592 QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));
2593
2594 // ### we need the inner function declaration (at this point the function has been wrapped)
2595 const unsigned int parameterCount = function->formalParameterCount();
2596 QV4::ScopedCallData callData(scope, parameterCount);
2597 callData->thisObject = v4->globalObject;
2598 function->call(scope, callData);
2599
2600 QJSValue v;
2601 QJSValuePrivate::setValue(&v, v4, scope.result);
2602 value.setValue<QJSValue>(v);
2603 } else {
2604 QByteArray script = scriptStr.toUtf8();
2605 bool ok;
2606 value = evaluateEnum(script, &ok);
2607 }
2608 } else {
2609 Q_UNREACHABLE();
2610 }
2611
2612 model->setOrCreateProperty(outterElementIndex, elementName, value);
2613 roleSet = true;
2614 }
2615 return roleSet;
2616}
2617
2618void QQmlListModelParser::verifyBindings(const QV4::CompiledData::Unit *qmlUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
2619{
2620 listElementTypeName = QString(); // unknown
2621
2622 for (const QV4::CompiledData::Binding *binding : bindings) {
2623 QString propName = qmlUnit->stringAt(binding->propertyNameIndex);
2624 if (!propName.isEmpty()) { // isn't default property
2625 error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
2626 return;
2627 }
2628 if (!verifyProperty(qmlUnit, binding))
2629 return;
2630 }
2631}
2632
2633void QQmlListModelParser::applyBindings(QObject *obj, QV4::CompiledData::CompilationUnit *compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
2634{
2635 QQmlListModel *rv = static_cast<QQmlListModel *>(obj);
2636
2637 rv->m_engine = QV8Engine::getV4(qmlEngine(rv));
2638
2639 const QV4::CompiledData::Unit *qmlUnit = compilationUnit->data;
2640
2641 bool setRoles = false;
2642
2643 for (const QV4::CompiledData::Binding *binding : bindings) {
2644 if (binding->type != QV4::CompiledData::Binding::Type_Object)
2645 continue;
2646 setRoles |= applyProperty(compilationUnit, qmlUnit, binding, rv->m_listModel, /*outter element index*/-1);
2647 }
2648
2649 if (setRoles == false)
2650 qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
2651}
2652
2653bool QQmlListModelParser::definesEmptyList(const QString &s)
2654{
2655 if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
2656 for (int i=1; i<s.length()-1; i++) {
2657 if (!s[i].isSpace())
2658 return false;
2659 }
2660 return true;
2661 }
2662 return false;
2663}
2664
2665
2666/*!
2667 \qmltype ListElement
2668 \instantiates QQmlListElement
2669 \inqmlmodule QtQml.Models
2670 \brief Defines a data item in a ListModel
2671 \ingroup qtquick-models
2672
2673 List elements are defined inside ListModel definitions, and represent items in a
2674 list that will be displayed using ListView or \l Repeater items.
2675
2676 List elements are defined like other QML elements except that they contain
2677 a collection of \e role definitions instead of properties. Using the same
2678 syntax as property definitions, roles both define how the data is accessed
2679 and include the data itself.
2680
2681 The names used for roles must begin with a lower-case letter and should be
2682 common to all elements in a given model. Values must be simple constants; either
2683 strings (quoted and optionally within a call to QT_TR_NOOP), boolean values
2684 (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
2685
2686 Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
2687 a role. This allows the definition of ListElements with callable actions.
2688
2689 \section1 Referencing Roles
2690
2691 The role names are used by delegates to obtain data from list elements.
2692 Each role name is accessible in the delegate's scope, and refers to the
2693 corresponding role in the current element. Where a role name would be
2694 ambiguous to use, it can be accessed via the \l{ListView::}{model}
2695 property (e.g., \c{model.cost} instead of \c{cost}).
2696
2697 \section1 Example Usage
2698
2699 The following model defines a series of list elements, each of which
2700 contain "name" and "cost" roles and their associated values.
2701
2702 \snippet qml/listmodel/listelements.qml model
2703
2704 The delegate obtains the name and cost for each element by simply referring
2705 to \c name and \c cost:
2706
2707 \snippet qml/listmodel/listelements.qml view
2708
2709 \sa ListModel
2710*/
2711
2712QT_END_NAMESPACE
2713
2714#include "moc_qqmllistmodel_p.cpp"
2715