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