1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlobjectmodel_p.h"
5
6#include <QtQml/qqmlcontext.h>
7#include <QtQml/qqmlengine.h>
8#include <QtQml/qqmlinfo.h>
9
10#include <private/qqmlchangeset_p.h>
11#include <private/qqmlglobal_p.h>
12#include <private/qobject_p.h>
13#include <private/qv4qobjectwrapper_p.h>
14
15#include <QtCore/qcoreapplication.h>
16#include <QtCore/qhash.h>
17#include <QtCore/qlist.h>
18#include <QtCore/qvarlengtharray.h>
19
20QT_BEGIN_NAMESPACE
21
22class QQmlObjectModelPrivate : public QObjectPrivate
23{
24 Q_DECLARE_PUBLIC(QQmlObjectModel)
25public:
26 class Item {
27 public:
28 Item(QObject *i = nullptr) : m_item(i) {}
29
30 void addRef() { ++m_ref; }
31 bool deref() { return --m_ref == 0; }
32 int refCount() const { return m_ref; }
33
34 QObject *item() const { return m_item.data(); }
35
36 private:
37 QPointer<QObject> m_item;
38 int m_ref = 0;
39 };
40
41 QQmlObjectModelPrivate() : QObjectPrivate(), moveId(0) {}
42
43 static QQmlObjectModelPrivate *get(QQmlObjectModel *q) { return q->d_func(); }
44
45 static void children_append(QQmlListProperty<QObject> *prop, QObject *item) {
46 qsizetype index = static_cast<QQmlObjectModelPrivate *>(prop->data)->children.size();
47 static_cast<QQmlObjectModelPrivate *>(prop->data)->insert(index, item);
48 }
49
50 static qsizetype children_count(QQmlListProperty<QObject> *prop) {
51 return static_cast<QQmlObjectModelPrivate *>(prop->data)->children.size();
52 }
53
54 static QObject *children_at(QQmlListProperty<QObject> *prop, qsizetype index) {
55 return static_cast<QQmlObjectModelPrivate *>(prop->data)->children.at(i: index).item();
56 }
57
58 static void children_clear(QQmlListProperty<QObject> *prop) {
59 static_cast<QQmlObjectModelPrivate *>(prop->data)->clear();
60 }
61
62 static void children_replace(QQmlListProperty<QObject> *prop, qsizetype index, QObject *item) {
63 static_cast<QQmlObjectModelPrivate *>(prop->data)->replace(index, item);
64 }
65
66 static void children_removeLast(QQmlListProperty<QObject> *prop) {
67 auto data = static_cast<QQmlObjectModelPrivate *>(prop->data);
68 data->remove(index: data->children.size() - 1, n: 1);
69 }
70
71 void markNewChild(QQmlObjectModel *q, QObject *item)
72 {
73 if (QJSEngine *engine = qjsEngine(q)) {
74 QV4::WriteBarrier::markCustom(engine: engine->handle(), markFunction: [&](QV4::MarkStack *markStack) {
75 QV4::QObjectWrapper::markWrapper(object: item, markStack);
76 });
77 }
78 }
79
80 void insert(int index, QObject *item) {
81 Q_Q(QQmlObjectModel);
82 children.insert(i: index, t: Item(item));
83 markNewChild(q, item);
84 for (int i = index, end = children.size(); i < end; ++i)
85 setIndex(i);
86 QQmlChangeSet changeSet;
87 changeSet.insert(index, count: 1);
88 emit q->modelUpdated(changeSet, reset: false);
89 emit q->countChanged();
90 emit q->childrenChanged();
91 }
92
93 void replace(int index, QObject *item) {
94 Q_Q(QQmlObjectModel);
95 clearIndex(child: index);
96 children.replace(i: index, t: Item(item));
97 markNewChild(q, item);
98 setIndex(index);
99 QQmlChangeSet changeSet;
100 changeSet.change(index, count: 1);
101 emit q->modelUpdated(changeSet, reset: false);
102 emit q->childrenChanged();
103 }
104
105 void move(int from, int to, int n) {
106 Q_Q(QQmlObjectModel);
107 if (from > to) {
108 // Only move forwards - flip if backwards moving
109 int tfrom = from;
110 int tto = to;
111 from = tto;
112 to = tto+n;
113 n = tfrom-tto;
114 }
115
116 QVarLengthArray<QQmlObjectModelPrivate::Item, 4> store;
117 for (int i = 0; i < to - from; ++i)
118 store.append(t: std::move(children[from + n + i]));
119 for (int i = 0; i < n; ++i)
120 store.append(t: std::move(children[from + i]));
121
122 for (int i = 0, end = store.count(); i < end; ++i) {
123 children[from + i] = std::move(store[i]);
124 setIndex(from + i);
125 }
126
127 QQmlChangeSet changeSet;
128 changeSet.move(from, to, count: n, moveId: ++moveId);
129 emit q->modelUpdated(changeSet, reset: false);
130 emit q->childrenChanged();
131 }
132
133 void remove(int index, int n) {
134 Q_Q(QQmlObjectModel);
135 for (int i = index; i < index + n; ++i)
136 clearIndex(child: i);
137 children.erase(begin: children.begin() + index, end: children.begin() + index + n);
138 for (int i = index, end = children.size(); i < end; ++i)
139 setIndex(i);
140 QQmlChangeSet changeSet;
141 changeSet.remove(index, count: n);
142 emit q->modelUpdated(changeSet, reset: false);
143 emit q->countChanged();
144 emit q->childrenChanged();
145 }
146
147 void clear() {
148 Q_Q(QQmlObjectModel);
149 const auto copy = children;
150 for (const Item &child : copy)
151 emit q->destroyingItem(object: child.item());
152 remove(index: 0, n: children.size());
153 }
154
155 int indexOf(QObject *item) const {
156 for (int i = 0; i < children.size(); ++i)
157 if (children.at(i).item() == item)
158 return i;
159 return -1;
160 }
161
162 void markChildren(QV4::MarkStack *markStack) const
163 {
164 for (const Item &child : children)
165 QV4::QObjectWrapper::markWrapper(object: child.item(), markStack);
166 }
167
168 quint64 _q_createJSWrapper(QQmlV4ExecutionEnginePtr engine);
169
170private:
171 void setIndex(int child, int index)
172 {
173 if (auto *attached = static_cast<QQmlObjectModelAttached *>(
174 qmlAttachedPropertiesObject<QQmlObjectModel>(obj: children.at(i: child).item()))) {
175 attached->setIndex(index);
176 }
177 }
178
179 void setIndex(int child) { setIndex(child, index: child); }
180 void clearIndex(int child) { setIndex(child, index: -1); }
181
182
183 uint moveId;
184 QList<Item> children;
185};
186
187namespace QV4 {
188namespace Heap {
189struct QQmlObjectModelWrapper : public QObjectWrapper {
190 static void markObjects(Base *that, MarkStack *markStack)
191 {
192 Q_ASSERT(QV4::Value::fromHeapObject(that).as<QV4::QObjectWrapper>());
193 QObject *object = static_cast<QObjectWrapper *>(that)->object();
194 if (!object)
195 return;
196
197 Q_ASSERT(qobject_cast<QQmlObjectModel *>(object));
198 QQmlObjectModelPrivate::get(q: static_cast<QQmlObjectModel *>(object))
199 ->markChildren(markStack);
200
201 QObjectWrapper::markObjects(that, markStack);
202 }
203};
204} // namespace Heap
205
206struct QQmlObjectModelWrapper : public QObjectWrapper {
207 V4_OBJECT2(QQmlObjectModelWrapper, QObjectWrapper)
208};
209
210} // namspace QV4
211
212DEFINE_OBJECT_VTABLE(QV4::QQmlObjectModelWrapper);
213
214quint64 QQmlObjectModelPrivate::_q_createJSWrapper(QQmlV4ExecutionEnginePtr engine)
215{
216 return (engine->memoryManager->allocate<QV4::QQmlObjectModelWrapper>(
217 args: q_func()))->asReturnedValue();
218}
219
220
221
222/*!
223 \qmltype ObjectModel
224 \nativetype QQmlObjectModel
225 \inqmlmodule QtQml.Models
226 \ingroup qtquick-models
227 \brief Defines a set of items to be used as a model.
228
229 An ObjectModel contains the visual items to be used in a view.
230 When an ObjectModel is used in a view, the view does not require
231 a delegate since the ObjectModel already contains the visual
232 delegate (items).
233
234 An item can determine its index within the
235 model via the \l{ObjectModel::index}{index} attached property.
236
237 The example below places three colored rectangles in a ListView.
238 \code
239 import QtQuick 2.0
240 import QtQml.Models 2.1
241
242 Rectangle {
243 ObjectModel {
244 id: itemModel
245 Rectangle { height: 30; width: 80; color: "red" }
246 Rectangle { height: 30; width: 80; color: "green" }
247 Rectangle { height: 30; width: 80; color: "blue" }
248 }
249
250 ListView {
251 anchors.fill: parent
252 model: itemModel
253 }
254 }
255 \endcode
256
257 \image objectmodel.png
258
259 \sa {Qt Quick Examples - Views}
260*/
261
262QQmlObjectModel::QQmlObjectModel(QObject *parent)
263 : QQmlInstanceModel(*(new QQmlObjectModelPrivate), parent)
264{
265}
266
267QQmlObjectModel::~QQmlObjectModel() = default;
268
269/*!
270 \qmlattachedproperty int QtQml.Models::ObjectModel::index
271 This attached property holds the index of this delegate's item within the model.
272
273 It is attached to each instance of the delegate.
274*/
275
276QQmlListProperty<QObject> QQmlObjectModel::children()
277{
278 Q_D(QQmlObjectModel);
279 return QQmlListProperty<QObject>(this, d,
280 QQmlObjectModelPrivate::children_append,
281 QQmlObjectModelPrivate::children_count,
282 QQmlObjectModelPrivate::children_at,
283 QQmlObjectModelPrivate::children_clear,
284 QQmlObjectModelPrivate::children_replace,
285 QQmlObjectModelPrivate::children_removeLast);
286}
287
288/*!
289 \qmlproperty int QtQml.Models::ObjectModel::count
290
291 The number of items in the model. This property is readonly.
292*/
293int QQmlObjectModel::count() const
294{
295 Q_D(const QQmlObjectModel);
296 return d->children.size();
297}
298
299bool QQmlObjectModel::isValid() const
300{
301 return true;
302}
303
304QObject *QQmlObjectModel::object(int index, QQmlIncubator::IncubationMode)
305{
306 Q_D(QQmlObjectModel);
307 QQmlObjectModelPrivate::Item &item = d->children[index];
308 item.addRef();
309 QObject *obj = item.item();
310 if (item.refCount() == 1) {
311 emit initItem(index, object: obj);
312 emit createdItem(index, object: obj);
313 }
314 return obj;
315}
316
317QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item, ReusableFlag)
318{
319 Q_D(QQmlObjectModel);
320 int idx = d->indexOf(item);
321 if (idx >= 0) {
322 if (!d->children[idx].deref())
323 return QQmlInstanceModel::Referenced;
324 }
325 return {};
326}
327
328QVariant QQmlObjectModel::variantValue(int index, const QString &role)
329{
330 Q_D(QQmlObjectModel);
331 if (index < 0 || index >= d->children.size())
332 return QString();
333 if (QObject *item = d->children.at(i: index).item())
334 return item->property(name: role.toUtf8().constData());
335 return QString();
336}
337
338QQmlIncubator::Status QQmlObjectModel::incubationStatus(int)
339{
340 return QQmlIncubator::Ready;
341}
342
343int QQmlObjectModel::indexOf(QObject *item, QObject *) const
344{
345 Q_D(const QQmlObjectModel);
346 return d->indexOf(item);
347}
348
349QQmlObjectModelAttached *QQmlObjectModel::qmlAttachedProperties(QObject *obj)
350{
351 return new QQmlObjectModelAttached(obj);
352}
353
354/*!
355 \qmlmethod object QtQml.Models::ObjectModel::get(int index)
356 \since 5.6
357
358 Returns the item at \a index in the model. This allows the item
359 to be accessed or modified from JavaScript:
360
361 \code
362 Component.onCompleted: {
363 objectModel.append(objectComponent.createObject())
364 console.log(objectModel.get(0).objectName);
365 objectModel.get(0).objectName = "first";
366 }
367 \endcode
368
369 The \a index must be an element in the list.
370
371 \sa append()
372*/
373QObject *QQmlObjectModel::get(int index) const
374{
375 Q_D(const QQmlObjectModel);
376 if (index < 0 || index >= d->children.size())
377 return nullptr;
378 return d->children.at(i: index).item();
379}
380
381/*!
382 \qmlmethod QtQml.Models::ObjectModel::append(object item)
383 \since 5.6
384
385 Appends a new \a item to the end of the model.
386
387 \code
388 objectModel.append(objectComponent.createObject())
389 \endcode
390
391 \sa insert(), remove()
392*/
393void QQmlObjectModel::append(QObject *object)
394{
395 Q_D(QQmlObjectModel);
396 d->insert(index: count(), item: object);
397}
398
399/*!
400 \qmlmethod QtQml.Models::ObjectModel::insert(int index, object item)
401 \since 5.6
402
403 Inserts a new \a item to the model at position \a index.
404
405 \code
406 objectModel.insert(2, objectComponent.createObject())
407 \endcode
408
409 The \a index must be to an existing item in the list, or one past
410 the end of the list (equivalent to append).
411
412 \sa append(), remove()
413*/
414void QQmlObjectModel::insert(int index, QObject *object)
415{
416 Q_D(QQmlObjectModel);
417 if (index < 0 || index > count()) {
418 qmlWarning(me: this) << tr(s: "insert: index %1 out of range").arg(a: index);
419 return;
420 }
421 d->insert(index, item: object);
422}
423
424/*!
425 \qmlmethod QtQml.Models::ObjectModel::move(int from, int to, int n = 1)
426 \since 5.6
427
428 Moves \a n items \a from one position \a to another.
429
430 The from and to ranges must exist; for example, to move the first 3 items
431 to the end of the model:
432
433 \code
434 objectModel.move(0, objectModel.count - 3, 3)
435 \endcode
436
437 \sa append()
438*/
439void QQmlObjectModel::move(int from, int to, int n)
440{
441 Q_D(QQmlObjectModel);
442 if (n <= 0 || from == to)
443 return;
444 if (from < 0 || to < 0 || from + n > count() || to + n > count()) {
445 qmlWarning(me: this) << tr(s: "move: out of range");
446 return;
447 }
448 d->move(from, to, n);
449}
450
451/*!
452 \qmlmethod QtQml.Models::ObjectModel::remove(int index, int n = 1)
453 \since 5.6
454
455 Removes \a n items at \a index from the model.
456
457 \sa clear()
458*/
459void QQmlObjectModel::remove(int index, int n)
460{
461 Q_D(QQmlObjectModel);
462 if (index < 0 || n <= 0 || index + n > count()) {
463 qmlWarning(me: this) << tr(s: "remove: indices [%1 - %2] out of range [0 - %3]").arg(a: index).arg(a: index+n).arg(a: count());
464 return;
465 }
466 d->remove(index, n);
467}
468
469/*!
470 \qmlmethod QtQml.Models::ObjectModel::clear()
471 \since 5.6
472
473 Clears all items from the model.
474
475 \sa append(), remove()
476*/
477void QQmlObjectModel::clear()
478{
479 Q_D(QQmlObjectModel);
480 d->clear();
481}
482
483bool QQmlInstanceModel::setRequiredProperty(int index, const QString &name, const QVariant &value)
484{
485 Q_UNUSED(index);
486 Q_UNUSED(name);
487 Q_UNUSED(value);
488 // The view should not call this function, unless
489 // it's actually handled in a subclass.
490 Q_UNREACHABLE_RETURN(false);
491}
492
493QT_END_NAMESPACE
494
495#include "moc_qqmlobjectmodel_p.cpp"
496

source code of qtdeclarative/src/qmlmodels/qqmlobjectmodel.cpp