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 "qqmllistaccessor_p.h" |
5 | |
6 | #include <private/qqmlmetatype_p.h> |
7 | |
8 | #include <QtCore/qdebug.h> |
9 | #include <QtCore/qsequentialiterable.h> |
10 | #include <QtCore/qstringlist.h> |
11 | #include <QtCore/qurl.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | QQmlListAccessor::QQmlListAccessor() |
16 | : m_type(Invalid) |
17 | { |
18 | } |
19 | |
20 | QQmlListAccessor::~QQmlListAccessor() |
21 | { |
22 | } |
23 | |
24 | QVariant QQmlListAccessor::list() const |
25 | { |
26 | return d; |
27 | } |
28 | |
29 | void QQmlListAccessor::setList(const QVariant &v) |
30 | { |
31 | d = v; |
32 | |
33 | // An incoming JS array as model is treated as a variant list, so we need to |
34 | // convert it first with toVariant(). |
35 | QMetaType variantsType = d.metaType(); |
36 | if (variantsType == QMetaType::fromType<QJSValue>()) { |
37 | d = d.value<QJSValue>().toVariant(); |
38 | variantsType = d.metaType(); |
39 | } |
40 | |
41 | if (!d.isValid()) { |
42 | m_type = Invalid; |
43 | return; |
44 | } |
45 | |
46 | if (variantsType == QMetaType::fromType<QStringList>()) { |
47 | m_type = StringList; |
48 | return; |
49 | } |
50 | |
51 | if (variantsType == QMetaType::fromType<QList<QUrl>>()) { |
52 | m_type = UrlList; |
53 | return; |
54 | } |
55 | |
56 | if (variantsType == QMetaType::fromType<QVariantList>()) { |
57 | m_type = VariantList; |
58 | return; |
59 | } |
60 | |
61 | if (variantsType == QMetaType::fromType<QList<QObject *>>()) { |
62 | m_type = ObjectList; |
63 | return; |
64 | } |
65 | |
66 | if (variantsType.flags() & QMetaType::IsQmlList) { |
67 | d = QVariant::fromValue(value: QQmlListReference(d)); |
68 | m_type = ListProperty; |
69 | return; |
70 | } |
71 | |
72 | if (variantsType == QMetaType::fromType<QQmlListReference>()) { |
73 | m_type = ListProperty; |
74 | return; |
75 | } |
76 | |
77 | if (variantsType.flags() & QMetaType::PointerToQObject) { |
78 | m_type = Instance; |
79 | return; |
80 | } |
81 | |
82 | if (int i = 0; [&](){bool ok = false; i = v.toInt(ok: &ok); return ok;}()) { |
83 | // Here we have to check for an upper limit, because down the line code might (well, will) |
84 | // allocate memory depending on the number of elements. The upper limit cannot be INT_MAX: |
85 | // QVector<QPointer<QQuickItem>> something; |
86 | // something.resize(count()); |
87 | // (See e.g. QQuickRepeater::regenerate()) |
88 | // This will allocate data along the lines of: |
89 | // sizeof(QPointer<QQuickItem>) * count() + QVector::headerSize |
90 | // So, doing an approximate round-down-to-nice-number, we get: |
91 | const int upperLimit = 100 * 1000 * 1000; |
92 | |
93 | if (i < 0) { |
94 | qWarning(msg: "Model size of %d is less than 0" , i); |
95 | m_type = Invalid; |
96 | return; |
97 | } |
98 | |
99 | if (i > upperLimit) { |
100 | qWarning(msg: "Model size of %d is bigger than the upper limit %d" , i, upperLimit); |
101 | m_type = Invalid; |
102 | return; |
103 | } |
104 | |
105 | m_type = Integer; |
106 | d = i; |
107 | return; |
108 | } |
109 | |
110 | const QQmlType type = QQmlMetaType::qmlListType(metaType: variantsType); |
111 | if (type.isSequentialContainer()) { |
112 | m_metaSequence = type.listMetaSequence(); |
113 | m_type = Sequence; |
114 | return; |
115 | } |
116 | |
117 | QSequentialIterable iterable; |
118 | if (QMetaType::convert( |
119 | fromType: variantsType, from: d.constData(), |
120 | toType: QMetaType::fromType<QSequentialIterable>(), to: &iterable)) { |
121 | const QMetaSequence sequence = iterable.metaContainer(); |
122 | |
123 | if (sequence.hasSize() && sequence.canGetValueAtIndex()) { |
124 | // If the resulting iterable is useful for anything, use it. |
125 | m_metaSequence = sequence; |
126 | m_type = Sequence; |
127 | return; |
128 | } |
129 | |
130 | if (sequence.hasConstIterator() && sequence.canGetValueAtConstIterator()) { |
131 | // As a last resort, try to read the contents of the container via an iterator |
132 | // and build a QVariantList from them. |
133 | QVariantList variantList; |
134 | for (auto it = iterable.constBegin(), end = iterable.constEnd(); it != end; ++it) |
135 | variantList.push_back(t: *it); |
136 | d = std::move(variantList); |
137 | m_type = VariantList; |
138 | return; |
139 | } |
140 | } |
141 | |
142 | m_type = Instance; |
143 | return; |
144 | } |
145 | |
146 | qsizetype QQmlListAccessor::count() const |
147 | { |
148 | switch(m_type) { |
149 | case StringList: |
150 | Q_ASSERT(d.metaType() == QMetaType::fromType<QStringList>()); |
151 | return reinterpret_cast<const QStringList *>(d.constData())->size(); |
152 | case UrlList: |
153 | Q_ASSERT(d.metaType() == QMetaType::fromType<QList<QUrl>>()); |
154 | return reinterpret_cast<const QList<QUrl> *>(d.constData())->size(); |
155 | case VariantList: |
156 | Q_ASSERT(d.metaType() == QMetaType::fromType<QVariantList>()); |
157 | return reinterpret_cast<const QVariantList *>(d.constData())->size(); |
158 | case ObjectList: |
159 | Q_ASSERT(d.metaType() == QMetaType::fromType<QList<QObject *>>()); |
160 | return reinterpret_cast<const QList<QObject *> *>(d.constData())->size(); |
161 | case ListProperty: |
162 | Q_ASSERT(d.metaType() == QMetaType::fromType<QQmlListReference>()); |
163 | return reinterpret_cast<const QQmlListReference *>(d.constData())->count(); |
164 | case Sequence: |
165 | Q_ASSERT(m_metaSequence != QMetaSequence()); |
166 | return m_metaSequence.size(container: d.constData()); |
167 | case Instance: |
168 | return 1; |
169 | case Integer: |
170 | return *reinterpret_cast<const int *>(d.constData()); |
171 | case Invalid: |
172 | return 0; |
173 | } |
174 | Q_UNREACHABLE_RETURN(0); |
175 | } |
176 | |
177 | QVariant QQmlListAccessor::at(qsizetype idx) const |
178 | { |
179 | Q_ASSERT(idx >= 0 && idx < count()); |
180 | switch(m_type) { |
181 | case StringList: |
182 | Q_ASSERT(d.metaType() == QMetaType::fromType<QStringList>()); |
183 | return QVariant::fromValue(value: reinterpret_cast<const QStringList *>(d.constData())->at(i: idx)); |
184 | case UrlList: |
185 | Q_ASSERT(d.metaType() == QMetaType::fromType<QList<QUrl>>()); |
186 | return QVariant::fromValue(value: reinterpret_cast<const QList<QUrl> *>(d.constData())->at(i: idx)); |
187 | case VariantList: |
188 | Q_ASSERT(d.metaType() == QMetaType::fromType<QVariantList>()); |
189 | return reinterpret_cast<const QVariantList *>(d.constData())->at(i: idx); |
190 | case ObjectList: |
191 | Q_ASSERT(d.metaType() == QMetaType::fromType<QList<QObject *>>()); |
192 | return QVariant::fromValue(value: reinterpret_cast<const QList<QObject *> *>(d.constData())->at(i: idx)); |
193 | case ListProperty: |
194 | Q_ASSERT(d.metaType() == QMetaType::fromType<QQmlListReference>()); |
195 | return QVariant::fromValue(value: reinterpret_cast<const QQmlListReference *>(d.constData())->at(idx)); |
196 | case Sequence: { |
197 | Q_ASSERT(m_metaSequence != QMetaSequence()); |
198 | QVariant result; |
199 | const QMetaType valueMetaType = m_metaSequence.valueMetaType(); |
200 | if (valueMetaType == QMetaType::fromType<QVariant>()) { |
201 | m_metaSequence.valueAtIndex(container: d.constData(), index: idx, result: &result); |
202 | } else { |
203 | result = QVariant(valueMetaType); |
204 | m_metaSequence.valueAtIndex(container: d.constData(), index: idx, result: result.data()); |
205 | } |
206 | return result; |
207 | } |
208 | case Instance: |
209 | return d; |
210 | case Integer: |
211 | return QVariant(idx); |
212 | case Invalid: |
213 | return QVariant(); |
214 | } |
215 | Q_UNREACHABLE_RETURN(QVariant()); |
216 | } |
217 | |
218 | void QQmlListAccessor::set(qsizetype idx, const QVariant &value) |
219 | { |
220 | Q_ASSERT(idx >= 0 && idx < count()); |
221 | switch (m_type) { |
222 | case StringList: |
223 | Q_ASSERT(d.metaType() == QMetaType::fromType<QStringList>()); |
224 | (*static_cast<QStringList *>(d.data()))[idx] = value.toString(); |
225 | break; |
226 | case UrlList: |
227 | Q_ASSERT(d.metaType() == QMetaType::fromType<QList<QUrl>>()); |
228 | (*static_cast<QList<QUrl> *>(d.data()))[idx] = value.value<QUrl>(); |
229 | break; |
230 | case VariantList: |
231 | Q_ASSERT(d.metaType() == QMetaType::fromType<QVariantList>()); |
232 | (*static_cast<QVariantList *>(d.data()))[idx] = value; |
233 | break; |
234 | case ObjectList: |
235 | Q_ASSERT(d.metaType() == QMetaType::fromType<QList<QObject *>>()); |
236 | (*static_cast<QList<QObject *> *>(d.data()))[idx] = value.value<QObject *>(); |
237 | break; |
238 | case ListProperty: |
239 | Q_ASSERT(d.metaType() == QMetaType::fromType<QQmlListReference>()); |
240 | static_cast<QQmlListReference *>(d.data())->replace(idx, value.value<QObject *>()); |
241 | break; |
242 | case Sequence: { |
243 | Q_ASSERT(m_metaSequence != QMetaSequence()); |
244 | const QMetaType valueMetaType = m_metaSequence.valueMetaType(); |
245 | if (valueMetaType == QMetaType::fromType<QVariant>()) { |
246 | m_metaSequence.setValueAtIndex(container: d.data(), index: idx, value: &value); |
247 | } else if (valueMetaType == value.metaType()) { |
248 | m_metaSequence.setValueAtIndex(container: d.data(), index: idx, value: value.constData()); |
249 | } else { |
250 | QVariant converted = value; |
251 | converted.convert(type: valueMetaType); |
252 | m_metaSequence.setValueAtIndex(container: d.data(), index: idx, value: converted.constData()); |
253 | } |
254 | break; |
255 | } |
256 | case Instance: |
257 | d = value; |
258 | break; |
259 | case Integer: |
260 | break;; |
261 | case Invalid: |
262 | break; |
263 | } |
264 | } |
265 | |
266 | bool QQmlListAccessor::isValid() const |
267 | { |
268 | return m_type != Invalid; |
269 | } |
270 | |
271 | QT_END_NAMESPACE |
272 | |