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 "qqmlpropertymap.h"
5
6#include <private/qmetaobjectbuilder_p.h>
7#include <private/qqmlopenmetaobject_p.h>
8
9#include <QDebug>
10
11QT_BEGIN_NAMESPACE
12
13//QQmlPropertyMapMetaObject lets us listen for changes coming from QML
14//so we can emit the changed signal.
15class QQmlPropertyMapMetaObject : public QQmlOpenMetaObject
16{
17public:
18 QQmlPropertyMapMetaObject(QQmlPropertyMap *obj, QQmlPropertyMapPrivate *objPriv, const QMetaObject *staticMetaObject);
19
20protected:
21 QVariant propertyWriteValue(int, const QVariant &) override;
22 void propertyWritten(int index) override;
23 void propertyCreated(int, QMetaPropertyBuilder &) override;
24
25 const QString &propertyName(int index);
26
27private:
28 QQmlPropertyMap *map;
29 QQmlPropertyMapPrivate *priv;
30};
31
32class QQmlPropertyMapPrivate : public QObjectPrivate
33{
34 Q_DECLARE_PUBLIC(QQmlPropertyMap)
35public:
36 QQmlPropertyMapMetaObject *mo;
37 QStringList keys;
38
39 QVariant updateValue(const QString &key, const QVariant &input);
40 void emitChanged(const QString &key, const QVariant &value);
41 bool validKeyName(const QString& name);
42
43 const QString &propertyName(int index) const;
44};
45
46bool QQmlPropertyMapPrivate::validKeyName(const QString& name)
47{
48 //The following strings shouldn't be used as property names
49 return name != QLatin1String("keys")
50 && name != QLatin1String("valueChanged")
51 && name != QLatin1String("QObject")
52 && name != QLatin1String("destroyed")
53 && name != QLatin1String("deleteLater");
54}
55
56QVariant QQmlPropertyMapPrivate::updateValue(const QString &key, const QVariant &input)
57{
58 Q_Q(QQmlPropertyMap);
59 return q->updateValue(key, input);
60}
61
62void QQmlPropertyMapPrivate::emitChanged(const QString &key, const QVariant &value)
63{
64 Q_Q(QQmlPropertyMap);
65 emit q->valueChanged(key, value);
66}
67
68const QString &QQmlPropertyMapPrivate::propertyName(int index) const
69{
70 Q_ASSERT(index < keys.size());
71 return keys[index];
72}
73
74QQmlPropertyMapMetaObject::QQmlPropertyMapMetaObject(QQmlPropertyMap *obj, QQmlPropertyMapPrivate *objPriv, const QMetaObject *staticMetaObject)
75 : QQmlOpenMetaObject(obj, staticMetaObject)
76{
77 map = obj;
78 priv = objPriv;
79}
80
81QVariant QQmlPropertyMapMetaObject::propertyWriteValue(int index, const QVariant &input)
82{
83 return priv->updateValue(key: priv->propertyName(index), input);
84}
85
86void QQmlPropertyMapMetaObject::propertyWritten(int index)
87{
88 priv->emitChanged(key: priv->propertyName(index), value: value(index));
89}
90
91void QQmlPropertyMapMetaObject::propertyCreated(int, QMetaPropertyBuilder &b)
92{
93 priv->keys.append(t: QString::fromUtf8(ba: b.name()));
94}
95
96/*!
97 \class QQmlPropertyMap
98 \brief The QQmlPropertyMap class allows you to set key-value pairs that can be used in QML bindings.
99 \inmodule QtQml
100
101 QQmlPropertyMap provides a convenient way to expose domain data to the UI layer.
102 The following example shows how you might declare data in C++ and then
103 access it in QML.
104
105 In the C++ file:
106 \code
107 // create our data
108 QQmlPropertyMap ownerData;
109 ownerData.insert("name", QVariant(QString("John Smith")));
110 ownerData.insert("phone", QVariant(QString("555-5555")));
111
112 // expose it to the UI layer
113 QQuickView view;
114 QQmlContext *ctxt = view.rootContext();
115 ctxt->setContextProperty("owner", &ownerData);
116
117 view.setSource(QUrl::fromLocalFile("main.qml"));
118 view.show();
119 \endcode
120
121 Then, in \c main.qml:
122 \code
123 Text { text: owner.name + " " + owner.phone }
124 \endcode
125
126 The binding is dynamic - whenever a key's value is updated, anything bound to that
127 key will be updated as well.
128
129 To detect value changes made in the UI layer you can connect to the valueChanged() signal.
130 However, note that valueChanged() is \b NOT emitted when changes are made by calling insert()
131 or clear() - it is only emitted when a value is updated from QML.
132
133 \note It is not possible to remove keys from the map; once a key has been added, you can only
134 modify or clear its associated value.
135
136 \note When deriving a class from QQmlPropertyMap, use the
137 \l {QQmlPropertyMap::QQmlPropertyMap(DerivedType *derived, QObject *parent)} {protected two-argument constructor}
138 which ensures that the class is correctly registered with the Qt \l {Meta-Object System}.
139
140 \note The QMetaObject of a QQmlPropertyMap is dynamically generated and modified.
141 Operations on that meta object are not thread safe, so applications need to take
142 care to explicitly synchronize access to the meta object.
143*/
144
145/*!
146 Constructs a bindable map with parent object \a parent.
147*/
148QQmlPropertyMap::QQmlPropertyMap(QObject *parent)
149: QQmlPropertyMap(&staticMetaObject, parent)
150{
151}
152
153/*!
154 Destroys the bindable map.
155*/
156QQmlPropertyMap::~QQmlPropertyMap()
157{
158}
159
160/*!
161 Clears the value (if any) associated with \a key.
162*/
163void QQmlPropertyMap::clear(const QString &key)
164{
165 Q_D(QQmlPropertyMap);
166 if (d->validKeyName(name: key))
167 d->mo->setValue(key.toUtf8(), QVariant());
168}
169
170/*!
171 \since 6.1
172
173 Disallows any further properties to be added to this property map.
174 Existing properties can be modified or cleared.
175
176 In turn, an internal cache is turned on for the existing properties, which
177 may result in faster access from QML.
178 */
179void QQmlPropertyMap::freeze()
180{
181 Q_D(QQmlPropertyMap);
182 d->mo->setAutoCreatesProperties(false);
183 d->mo->setCached(true);
184}
185
186/*!
187 Returns the value associated with \a key.
188
189 If no value has been set for this key (or if the value has been cleared),
190 an invalid QVariant is returned.
191*/
192QVariant QQmlPropertyMap::value(const QString &key) const
193{
194 Q_D(const QQmlPropertyMap);
195 return d->mo->value(key.toUtf8());
196}
197
198/*!
199 Sets the value associated with \a key to \a value.
200
201 If the key doesn't exist, it is automatically created.
202*/
203void QQmlPropertyMap::insert(const QString &key, const QVariant &value)
204{
205 Q_D(QQmlPropertyMap);
206
207 if (d->validKeyName(name: key)) {
208 d->mo->setValue(key.toUtf8(), value);
209 } else {
210 qWarning() << "Creating property with name"
211 << key
212 << "is not permitted, conflicts with internal symbols.";
213 }
214}
215
216/*!
217 \since 6.1
218
219 Inserts the \a values into the QQmlPropertyMap.
220
221 Keys that don't exist are automatically created.
222
223 This method is substantially faster than calling \c{insert(key, value)}
224 many times in a row.
225*/
226void QQmlPropertyMap::insert(const QVariantHash &values)
227{
228 Q_D(QQmlPropertyMap);
229
230 QHash<QByteArray, QVariant> checkedValues;
231 for (auto it = values.begin(), end = values.end(); it != end; ++it) {
232 const QString &key = it.key();
233 if (!d->validKeyName(name: key)) {
234 qWarning() << "Creating property with name"
235 << key
236 << "is not permitted, conflicts with internal symbols.";
237 return;
238 }
239
240 checkedValues.insert(key: key.toUtf8(), value: it.value());
241 }
242 d->mo->setValues(checkedValues);
243
244}
245
246/*!
247 Returns the list of keys.
248
249 Keys that have been cleared will still appear in this list, even though their
250 associated values are invalid QVariants.
251*/
252QStringList QQmlPropertyMap::keys() const
253{
254 Q_D(const QQmlPropertyMap);
255 return d->keys;
256}
257
258/*!
259 \overload
260
261 Same as size().
262*/
263int QQmlPropertyMap::count() const
264{
265 Q_D(const QQmlPropertyMap);
266 return d->keys.size();
267}
268
269/*!
270 Returns the number of keys in the map.
271
272 \sa isEmpty(), count()
273*/
274int QQmlPropertyMap::size() const
275{
276 Q_D(const QQmlPropertyMap);
277 return d->keys.size();
278}
279
280/*!
281 Returns true if the map contains no keys; otherwise returns
282 false.
283
284 \sa size()
285*/
286bool QQmlPropertyMap::isEmpty() const
287{
288 Q_D(const QQmlPropertyMap);
289 return d->keys.isEmpty();
290}
291
292/*!
293 Returns true if the map contains \a key.
294
295 \sa size()
296*/
297bool QQmlPropertyMap::contains(const QString &key) const
298{
299 Q_D(const QQmlPropertyMap);
300 return d->keys.contains(str: key);
301}
302
303/*!
304 Returns the value associated with the key \a key as a modifiable
305 reference.
306
307 If the map contains no item with key \a key, the function inserts
308 an invalid QVariant into the map with key \a key, and
309 returns a reference to it.
310
311 \sa insert(), value()
312*/
313QVariant &QQmlPropertyMap::operator[](const QString &key)
314{
315 //### optimize
316 Q_D(QQmlPropertyMap);
317 QByteArray utf8key = key.toUtf8();
318 if (!d->keys.contains(str: key))
319 insert(key, value: QVariant());//force creation -- needed below
320
321 return d->mo->valueRef(utf8key);
322}
323
324/*!
325 \overload
326
327 Same as value().
328*/
329QVariant QQmlPropertyMap::operator[](const QString &key) const
330{
331 return value(key);
332}
333
334/*!
335 Returns the new value to be stored for the key \a key. This function is provided
336 to intercept updates to a property from QML, where the value provided from QML is \a input.
337
338 Override this function to manipulate the property value as it is updated. Note that
339 this function is only invoked when the value is updated from QML.
340*/
341QVariant QQmlPropertyMap::updateValue(const QString &key, const QVariant &input)
342{
343 Q_UNUSED(key);
344 return input;
345}
346
347/*! \internal */
348QQmlPropertyMap::QQmlPropertyMap(const QMetaObject *staticMetaObject, QObject *parent)
349 : QObject(*(new QQmlPropertyMapPrivate), parent)
350{
351 Q_D(QQmlPropertyMap);
352 d->mo = new QQmlPropertyMapMetaObject(this, d, staticMetaObject);
353}
354
355/*!
356 \fn void QQmlPropertyMap::valueChanged(const QString &key, const QVariant &value)
357 This signal is emitted whenever one of the values in the map is changed. \a key
358 is the key corresponding to the \a value that was changed.
359
360 \note valueChanged() is \b NOT emitted when changes are made by calling insert()
361 or clear() - it is only emitted when a value is updated from QML.
362*/
363
364/*!
365 \fn template<class DerivedType> QQmlPropertyMap::QQmlPropertyMap(DerivedType *derived, QObject *parent)
366
367 Constructs a bindable map with parent object \a parent. Use this constructor
368 in classes derived from QQmlPropertyMap.
369
370 The type of \a derived is used to register the property map with the \l {Meta-Object System},
371 which is necessary to ensure that properties of the derived class are accessible.
372 This type must be derived from QQmlPropertyMap.
373*/
374
375QT_END_NAMESPACE
376
377#include "moc_qqmlpropertymap.cpp"
378

source code of qtdeclarative/src/qml/util/qqmlpropertymap.cpp