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 "qquickpropertychanges_p.h" |
5 | |
6 | #include <private/qqmlopenmetaobject_p.h> |
7 | #include <private/qqmlengine_p.h> |
8 | |
9 | #include <qqmlinfo.h> |
10 | #include <private/qqmlcustomparser_p.h> |
11 | #include <qqmlexpression.h> |
12 | #include <private/qqmlbinding_p.h> |
13 | #include <qqmlcontext.h> |
14 | #include <private/qqmlproperty_p.h> |
15 | #include <private/qqmlcontext_p.h> |
16 | #include <private/qquickstate_p_p.h> |
17 | #include <private/qqmlboundsignal_p.h> |
18 | #include <private/qv4qmlcontext_p.h> |
19 | #include <private/qqmlpropertybinding_p.h> |
20 | #include <private/qqmlirbuilder_p.h> |
21 | |
22 | #include <QtCore/qdebug.h> |
23 | #include <QtQml/private/qqmlsignalnames_p.h> |
24 | |
25 | #include <private/qobject_p.h> |
26 | #include <QtCore/qpointer.h> |
27 | |
28 | QT_BEGIN_NAMESPACE |
29 | |
30 | /*! |
31 | \qmltype PropertyChanges |
32 | \inqmlmodule QtQuick |
33 | \ingroup qtquick-states |
34 | \brief Describes new property bindings or values for a state. |
35 | |
36 | PropertyChanges is used to define the property values or bindings in a |
37 | \l State. This enables an item's property values to be changed when it |
38 | \l {Qt Quick States}{changes between states}. |
39 | |
40 | To create a PropertyChanges object, bind to properties of the target |
41 | item like you would bind to local properties. This way you can define |
42 | the new property values or bindings. For example: |
43 | |
44 | \snippet qml/propertychanges.qml import |
45 | \codeline |
46 | \snippet qml/propertychanges.qml 0 |
47 | |
48 | When the mouse is pressed, the \l Rectangle changes to the \e resized |
49 | state. In this state, the PropertyChanges object sets the rectangle's |
50 | color to blue and the \c height value to that of \c container.height. |
51 | |
52 | Note this automatically binds \c rect.height to \c container.height |
53 | in the \e resized state. If a property binding should not be |
54 | established, and the height should just be set to the value of |
55 | \c container.height at the time of the state change, set the \l explicit |
56 | property to \c true. |
57 | |
58 | A PropertyChanges object can also override the default signal handler |
59 | for an object to implement a signal handler specific to the new state: |
60 | |
61 | \qml |
62 | PropertyChanges { |
63 | myMouseArea.onClicked: doSomethingDifferent() |
64 | } |
65 | \endqml |
66 | |
67 | \note PropertyChanges can be used to change anchor margins, but not other anchor |
68 | values; use AnchorChanges for this instead. Similarly, to change an \l Item's |
69 | \l {Item::}{parent} value, use ParentChange instead. |
70 | |
71 | |
72 | \section2 Resetting Property Values |
73 | |
74 | The \c undefined value can be used to reset the property value for a state. |
75 | In the following example, when \c myText changes to the \e widerText |
76 | state, its \c width property is reset, giving the text its natural width |
77 | and displaying the whole string on a single line. |
78 | |
79 | \snippet qml/propertychanges.qml reset |
80 | |
81 | |
82 | \section2 Immediate Property Changes in Transitions |
83 | |
84 | When \l{Animation and Transitions in Qt Quick}{Transitions} are used to animate |
85 | state changes, they animate properties from their values in the current |
86 | state to those defined in the new state (as defined by PropertyChanges |
87 | objects). However, it is sometimes desirable to set a property value |
88 | \e immediately during a \l Transition, without animation; in these cases, |
89 | the PropertyAction type can be used to force an immediate property |
90 | change. |
91 | |
92 | See the PropertyAction documentation for more details. |
93 | |
94 | \note The \l{Item::}{visible} and \l{Item::}{enabled} properties of \l Item do not behave |
95 | exactly the same as other properties in PropertyChanges. Since these properties can be |
96 | changed implicitly through their parent's state, they should be set explicitly in all PropertyChanges. |
97 | An item will still not be enabled/visible if one of its parents is not enabled or visible. |
98 | |
99 | \note For backwards compatibility with Qt 5, you can also specify PropertyChanges using |
100 | the \l target property and plain property names without IDs. For example: |
101 | \c {PropertyChanges { target: myItem; x: 15 }}. The form with ID instead of \l target |
102 | is recommended. You may also need to use the form with \l target if the file is to be |
103 | edited with \l{Qt Design Studio}. Mind that \l{Qt Design Studio} also imposes a number |
104 | of further restrictions on the files it can work with. |
105 | |
106 | \sa {Qt Quick Examples - Animation#States}{States example}, {Qt Quick States}, {Qt Qml} |
107 | */ |
108 | |
109 | /*! |
110 | \qmlproperty QtObject QtQuick::PropertyChanges::target |
111 | This property holds the object which contains the properties to be changed. |
112 | |
113 | \note You generally don't have to use this property. It only exists for |
114 | compatibility with Qt 5 and for compatibility with \l{Qt Design Studio}. |
115 | */ |
116 | |
117 | class QQuickReplaceSignalHandler : public QQuickStateActionEvent |
118 | { |
119 | public: |
120 | QQuickReplaceSignalHandler() {} |
121 | ~QQuickReplaceSignalHandler() {} |
122 | |
123 | EventType type() const override { return SignalHandler; } |
124 | |
125 | QQmlProperty property; |
126 | QQmlRefPointer<QQmlBoundSignalExpression> expression; |
127 | QQmlRefPointer<QQmlBoundSignalExpression> reverseExpression; |
128 | QQmlRefPointer<QQmlBoundSignalExpression> rewindExpression; |
129 | |
130 | void execute() override { |
131 | QQmlPropertyPrivate::setSignalExpression(that: property, expression.data()); |
132 | } |
133 | |
134 | bool isReversable() override { return true; } |
135 | void reverse() override { |
136 | QQmlPropertyPrivate::setSignalExpression(that: property, reverseExpression.data()); |
137 | } |
138 | |
139 | void saveOriginals() override { |
140 | saveCurrentValues(); |
141 | reverseExpression = rewindExpression; |
142 | } |
143 | |
144 | bool needsCopy() override { return true; } |
145 | void copyOriginals(QQuickStateActionEvent *other) override |
146 | { |
147 | QQuickReplaceSignalHandler *rsh = static_cast<QQuickReplaceSignalHandler*>(other); |
148 | saveCurrentValues(); |
149 | if (rsh == this) |
150 | return; |
151 | reverseExpression = rsh->reverseExpression; |
152 | } |
153 | |
154 | void rewind() override { |
155 | QQmlPropertyPrivate::setSignalExpression(that: property, rewindExpression.data()); |
156 | } |
157 | void saveCurrentValues() override { |
158 | rewindExpression = QQmlPropertyPrivate::signalExpression(that: property); |
159 | } |
160 | |
161 | bool mayOverride(QQuickStateActionEvent *other) override { |
162 | if (other == this) |
163 | return true; |
164 | if (other->type() != type()) |
165 | return false; |
166 | if (static_cast<QQuickReplaceSignalHandler*>(other)->property == property) |
167 | return true; |
168 | return false; |
169 | } |
170 | }; |
171 | |
172 | |
173 | class QQuickPropertyChangesPrivate : public QQuickStateOperationPrivate |
174 | { |
175 | Q_DECLARE_PUBLIC(QQuickPropertyChanges) |
176 | public: |
177 | QQuickPropertyChangesPrivate() : decoded(true), restore(true), |
178 | isExplicit(false) {} |
179 | |
180 | QPointer<QObject> object; |
181 | QList<const QV4::CompiledData::Binding *> bindings; |
182 | QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit; |
183 | |
184 | bool decoded : 1; |
185 | bool restore : 1; |
186 | bool isExplicit : 1; |
187 | |
188 | void decode(); |
189 | void decodeBinding(const QString &propertyPrefix, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &qmlUnit, const QV4::CompiledData::Binding *binding); |
190 | |
191 | class ExpressionChange { |
192 | public: |
193 | ExpressionChange(const QString &_name, |
194 | const QV4::CompiledData::Binding *_binding, |
195 | QQmlBinding::Identifier _id, |
196 | const QString& _expr, |
197 | const QUrl &_url, |
198 | int _line, |
199 | int _column) |
200 | : name(_name), binding(_binding), id(_id), expression(_expr), url(_url), line(_line), column(_column) {} |
201 | QString name; |
202 | const QV4::CompiledData::Binding *binding; |
203 | QQmlBinding::Identifier id; |
204 | QString expression; |
205 | QUrl url; |
206 | int line; |
207 | int column; |
208 | }; |
209 | |
210 | QList<QPair<QString, QVariant> > properties; |
211 | QList<ExpressionChange> expressions; |
212 | QList<QQuickReplaceSignalHandler*> signalReplacements; |
213 | |
214 | QQmlProperty property(const QString &); |
215 | }; |
216 | |
217 | void QQuickPropertyChangesParser::verifyList(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding) |
218 | { |
219 | switch (binding->type()) { |
220 | case QV4::CompiledData::Binding::Type_Object: |
221 | error(object: compilationUnit->objectAt(index: binding->value.objectIndex), |
222 | description: QQuickPropertyChanges::tr( |
223 | s: "PropertyChanges does not support creating state-specific objects.")); |
224 | break; |
225 | case QV4::CompiledData::Binding::Type_GroupProperty: |
226 | case QV4::CompiledData::Binding::Type_AttachedProperty: { |
227 | const QV4::CompiledData::Object *subObj = compilationUnit->objectAt(index: binding->value.objectIndex); |
228 | const QV4::CompiledData::Binding *subBinding = subObj->bindingTable(); |
229 | for (quint32 i = 0; i < subObj->nBindings; ++i, ++subBinding) |
230 | verifyList(compilationUnit, binding: subBinding); |
231 | break; |
232 | } |
233 | default: |
234 | break; |
235 | } |
236 | } |
237 | |
238 | void QQuickPropertyChangesPrivate::decode() |
239 | { |
240 | if (decoded) |
241 | return; |
242 | |
243 | for (const QV4::CompiledData::Binding *binding : std::as_const(t&: bindings)) |
244 | decodeBinding(propertyPrefix: QString(), qmlUnit: compilationUnit, binding); |
245 | |
246 | bindings.clear(); |
247 | |
248 | decoded = true; |
249 | } |
250 | |
251 | void QQuickPropertyChangesPrivate::decodeBinding(const QString &propertyPrefix, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding) |
252 | { |
253 | Q_Q(QQuickPropertyChanges); |
254 | |
255 | QString propertyName = propertyPrefix + compilationUnit->stringAt(index: binding->propertyNameIndex); |
256 | |
257 | switch (binding->type()) { |
258 | case QV4::CompiledData::Binding::Type_GroupProperty: |
259 | case QV4::CompiledData::Binding::Type_AttachedProperty: { |
260 | QString pre = propertyName + QLatin1Char('.'); |
261 | const QV4::CompiledData::Object *subObj = compilationUnit->objectAt(index: binding->value.objectIndex); |
262 | const QV4::CompiledData::Binding *subBinding = subObj->bindingTable(); |
263 | for (quint32 i = 0; i < subObj->nBindings; ++i, ++subBinding) { |
264 | decodeBinding(propertyPrefix: pre, compilationUnit, binding: subBinding); |
265 | } |
266 | return; |
267 | } |
268 | default: |
269 | break; |
270 | } |
271 | |
272 | if (binding->isSignalHandler() || QQmlSignalNames::isHandlerName(signalName: propertyName)) { |
273 | QQmlProperty prop = property(propertyName); |
274 | if (prop.isSignalProperty()) { |
275 | QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler; |
276 | handler->property = prop; |
277 | handler->expression.adopt( |
278 | new QQmlBoundSignalExpression( |
279 | prop.object(), QQmlPropertyPrivate::get(p: prop)->signalIndex(), |
280 | QQmlContextData::get(context: qmlContext(q)), prop.object(), |
281 | compilationUnit->runtimeFunctions.at(i: binding->value.compiledScriptIndex))); |
282 | signalReplacements << handler; |
283 | return; |
284 | } |
285 | } |
286 | |
287 | if (binding->type() == QV4::CompiledData::Binding::Type_Script |
288 | || binding->isTranslationBinding()) { |
289 | QUrl url = QUrl(); |
290 | int line = -1; |
291 | int column = -1; |
292 | |
293 | QQmlData *ddata = QQmlData::get(object: q); |
294 | if (ddata && ddata->outerContext && !ddata->outerContext->url().isEmpty()) { |
295 | url = ddata->outerContext->url(); |
296 | line = ddata->lineNumber; |
297 | column = ddata->columnNumber; |
298 | } |
299 | |
300 | QString expression; |
301 | QQmlBinding::Identifier id = QQmlBinding::Invalid; |
302 | |
303 | if (!binding->isTranslationBinding()) { |
304 | expression = compilationUnit->bindingValueAsString(binding); |
305 | id = binding->value.compiledScriptIndex; |
306 | } |
307 | expressions << ExpressionChange(propertyName, binding, id, expression, url, line, column); |
308 | return; |
309 | } |
310 | |
311 | QVariant var; |
312 | switch (binding->type()) { |
313 | case QV4::CompiledData::Binding::Type_Script: |
314 | case QV4::CompiledData::Binding::Type_Translation: |
315 | case QV4::CompiledData::Binding::Type_TranslationById: |
316 | Q_UNREACHABLE(); |
317 | case QV4::CompiledData::Binding::Type_String: |
318 | var = compilationUnit->bindingValueAsString(binding); |
319 | break; |
320 | case QV4::CompiledData::Binding::Type_Number: |
321 | var = compilationUnit->bindingValueAsNumber(binding); |
322 | break; |
323 | case QV4::CompiledData::Binding::Type_Boolean: |
324 | var = binding->valueAsBoolean(); |
325 | break; |
326 | case QV4::CompiledData::Binding::Type_Null: |
327 | var = QVariant::fromValue(value: nullptr); |
328 | break; |
329 | default: |
330 | break; |
331 | } |
332 | |
333 | properties << qMakePair(value1&: propertyName, value2&: var); |
334 | } |
335 | |
336 | void QQuickPropertyChangesParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props) |
337 | { |
338 | for (int ii = 0; ii < props.size(); ++ii) |
339 | verifyList(compilationUnit, binding: props.at(i: ii)); |
340 | } |
341 | |
342 | void QQuickPropertyChangesParser::applyBindings(QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings) |
343 | { |
344 | QQuickPropertyChangesPrivate *p = |
345 | static_cast<QQuickPropertyChangesPrivate *>(QObjectPrivate::get(o: obj)); |
346 | p->bindings = bindings; |
347 | p->compilationUnit = compilationUnit; |
348 | p->decoded = false; |
349 | |
350 | QQmlData *data = QQmlData::get(object: obj); |
351 | Q_ASSERT(data && !data->wasDeleted(obj)); |
352 | data->releaseDeferredData(); |
353 | } |
354 | |
355 | QQuickPropertyChanges::QQuickPropertyChanges() |
356 | : QQuickStateOperation(*(new QQuickPropertyChangesPrivate)) |
357 | { |
358 | } |
359 | |
360 | QQuickPropertyChanges::~QQuickPropertyChanges() |
361 | { |
362 | Q_D(QQuickPropertyChanges); |
363 | for(int ii = 0; ii < d->signalReplacements.size(); ++ii) |
364 | delete d->signalReplacements.at(i: ii); |
365 | } |
366 | |
367 | QObject *QQuickPropertyChanges::object() const |
368 | { |
369 | Q_D(const QQuickPropertyChanges); |
370 | return d->object; |
371 | } |
372 | |
373 | void QQuickPropertyChanges::setObject(QObject *o) |
374 | { |
375 | Q_D(QQuickPropertyChanges); |
376 | if (o != d->object) { |
377 | d->object = o; |
378 | emit objectChanged(); |
379 | } |
380 | } |
381 | |
382 | /*! |
383 | \qmlproperty bool QtQuick::PropertyChanges::restoreEntryValues |
384 | |
385 | This property holds whether the previous values should be restored when |
386 | leaving the state. |
387 | |
388 | The default value is \c true. Setting this value to \c false creates a |
389 | temporary state that has permanent effects on property values. |
390 | */ |
391 | bool QQuickPropertyChanges::restoreEntryValues() const |
392 | { |
393 | Q_D(const QQuickPropertyChanges); |
394 | return d->restore; |
395 | } |
396 | |
397 | void QQuickPropertyChanges::setRestoreEntryValues(bool v) |
398 | { |
399 | Q_D(QQuickPropertyChanges); |
400 | if (v != d->restore) { |
401 | d->restore = v; |
402 | emit restoreEntryValuesChanged(); |
403 | } |
404 | } |
405 | |
406 | QQmlProperty |
407 | QQuickPropertyChangesPrivate::property(const QString &property) |
408 | { |
409 | Q_Q(QQuickPropertyChanges); |
410 | QQmlData *ddata = QQmlData::get(object: q); |
411 | QQmlProperty prop = QQmlPropertyPrivate::create( |
412 | target: object, propertyName: property, context: ddata ? ddata->outerContext : QQmlRefPointer<QQmlContextData>(), |
413 | flags: QQmlPropertyPrivate::InitFlag::AllowId | QQmlPropertyPrivate::InitFlag::AllowSignal); |
414 | if (!prop.isValid()) { |
415 | qmlWarning(me: q) << QQuickPropertyChanges::tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: property); |
416 | return QQmlProperty(); |
417 | } else if (!(prop.type() & QQmlProperty::SignalProperty) && !prop.isWritable()) { |
418 | qmlWarning(me: q) << QQuickPropertyChanges::tr(s: "Cannot assign to read-only property \"%1\"").arg(a: property); |
419 | return QQmlProperty(); |
420 | } |
421 | return prop; |
422 | } |
423 | |
424 | QQuickPropertyChanges::ActionList QQuickPropertyChanges::actions() |
425 | { |
426 | Q_D(QQuickPropertyChanges); |
427 | |
428 | d->decode(); |
429 | |
430 | ActionList list; |
431 | |
432 | for (int ii = 0; ii < d->properties.size(); ++ii) { |
433 | QQmlProperty prop = d->property(property: d->properties.at(i: ii).first); |
434 | |
435 | QQuickStateAction a(d->object, prop, d->properties.at(i: ii).first, |
436 | d->properties.at(i: ii).second); |
437 | |
438 | if (a.property.isValid()) { |
439 | a.restore = restoreEntryValues(); |
440 | list << a; |
441 | } |
442 | } |
443 | |
444 | for (int ii = 0; ii < d->signalReplacements.size(); ++ii) { |
445 | QQuickReplaceSignalHandler *handler = d->signalReplacements.at(i: ii); |
446 | |
447 | if (handler->property.isValid()) { |
448 | QQuickStateAction a; |
449 | a.event = handler; |
450 | list << a; |
451 | } |
452 | } |
453 | |
454 | for (int ii = 0; ii < d->expressions.size(); ++ii) { |
455 | |
456 | QQuickPropertyChangesPrivate::ExpressionChange e = d->expressions.at(i: ii); |
457 | const QString &property = e.name; |
458 | QQmlProperty prop = d->property(property); |
459 | |
460 | if (prop.isValid()) { |
461 | QQuickStateAction a; |
462 | a.restore = restoreEntryValues(); |
463 | a.property = prop; |
464 | a.fromValue = a.property.read(); |
465 | a.specifiedObject = d->object; |
466 | a.specifiedProperty = property; |
467 | |
468 | QQmlRefPointer<QQmlContextData> context = QQmlContextData::get(context: qmlContext(this)); |
469 | QV4::Scope scope(qmlEngine(this)->handle()); |
470 | QV4::Scoped<QV4::QmlContext> qmlCtxt(scope, QV4::QmlContext::create(parent: scope.engine->rootContext(), context, scopeObject: object())); |
471 | |
472 | if (d->isExplicit) { |
473 | // in this case, we don't want to assign a binding, per se, |
474 | // so we evaluate the expression and assign the result. |
475 | // XXX TODO: add a static QQmlJavaScriptExpression::evaluate(QString) |
476 | // so that we can avoid creating then destroying the binding in this case. |
477 | std::unique_ptr<QQmlBinding> newBinding = nullptr; |
478 | if (e.binding && e.binding->isTranslationBinding()) { |
479 | newBinding.reset(p: QQmlBinding::createTranslationBinding(unit: d->compilationUnit, binding: e.binding, obj: object(), ctxt: context)); |
480 | } else if (e.id != QQmlBinding::Invalid) { |
481 | newBinding.reset(p: QQmlBinding::create(property: &QQmlPropertyPrivate::get(p: prop)->core, function: d->compilationUnit->runtimeFunctions.at(i: e.id), obj: object(), ctxt: context, scope: qmlCtxt)); |
482 | } else { |
483 | newBinding.reset(p: QQmlBinding::create(&QQmlPropertyPrivate::get(p: prop)->core, e.expression, object(), context, url: e.url.toString(), lineNumber: e.line)); |
484 | } |
485 | a.toValue = newBinding->evaluate(); |
486 | } else { |
487 | QQmlAnyBinding newBinding = nullptr; |
488 | if (e.binding && e.binding->isTranslationBinding()) { |
489 | newBinding = QQmlAnyBinding::createTranslationBinding(prop, compilationUnit: d->compilationUnit, translationBinding: e.binding, scopeObject: object(), context); |
490 | } else if (e.id != QQmlBinding::Invalid) { |
491 | newBinding = QQmlAnyBinding::createFromFunction(prop, |
492 | function: d->compilationUnit->runtimeFunctions.at(i: e.id), |
493 | obj: object(), ctxt: context, scope: qmlCtxt); |
494 | } else { |
495 | newBinding = QQmlAnyBinding::createFromCodeString(prop, code: e.expression, obj: object(), ctxt: context, url: e.url.toString(), lineNumber: e.line); |
496 | } |
497 | |
498 | a.toBinding = newBinding; |
499 | a.deletableToBinding = true; |
500 | } |
501 | |
502 | list << a; |
503 | } |
504 | } |
505 | |
506 | return list; |
507 | } |
508 | |
509 | /*! |
510 | \qmlproperty bool QtQuick::PropertyChanges::explicit |
511 | |
512 | If explicit is set to true, any potential bindings will be interpreted as |
513 | once-off assignments that occur when the state is entered. |
514 | |
515 | In the following example, the addition of explicit prevents \c myItem.width from |
516 | being bound to \c parent.width. Instead, it is assigned the value of \c parent.width |
517 | at the time of the state change. |
518 | \qml |
519 | PropertyChanges { |
520 | target: myItem |
521 | explicit: true |
522 | width: parent.width |
523 | } |
524 | \endqml |
525 | |
526 | By default, explicit is false. |
527 | */ |
528 | bool QQuickPropertyChanges::isExplicit() const |
529 | { |
530 | Q_D(const QQuickPropertyChanges); |
531 | return d->isExplicit; |
532 | } |
533 | |
534 | void QQuickPropertyChanges::setIsExplicit(bool e) |
535 | { |
536 | Q_D(QQuickPropertyChanges); |
537 | if (e != d->isExplicit) { |
538 | d->isExplicit = e; |
539 | emit isExplicitChanged(); |
540 | } |
541 | } |
542 | |
543 | bool QQuickPropertyChanges::containsValue(const QString &name) const |
544 | { |
545 | Q_D(const QQuickPropertyChanges); |
546 | typedef QPair<QString, QVariant> PropertyEntry; |
547 | |
548 | for (const PropertyEntry &entry : d->properties) { |
549 | if (entry.first == name) { |
550 | return true; |
551 | } |
552 | } |
553 | |
554 | return false; |
555 | } |
556 | |
557 | bool QQuickPropertyChanges::containsExpression(const QString &name) const |
558 | { |
559 | Q_D(const QQuickPropertyChanges); |
560 | typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry; |
561 | |
562 | for (const ExpressionEntry &entry : d->expressions) { |
563 | if (entry.name == name) { |
564 | return true; |
565 | } |
566 | } |
567 | |
568 | return false; |
569 | } |
570 | |
571 | bool QQuickPropertyChanges::containsProperty(const QString &name) const |
572 | { |
573 | return containsValue(name) || containsExpression(name); |
574 | } |
575 | |
576 | void QQuickPropertyChanges::changeValue(const QString &name, const QVariant &value) |
577 | { |
578 | Q_D(QQuickPropertyChanges); |
579 | typedef QPair<QString, QVariant> PropertyEntry; |
580 | |
581 | for (auto it = d->expressions.begin(), end = d->expressions.end(); it != end; ++it) { |
582 | if (it->name == name) { |
583 | d->expressions.erase(pos: it); |
584 | if (state() && state()->isStateActive()) { |
585 | QQmlPropertyPrivate::removeBinding(that: d->property(property: name)); |
586 | d->property(property: name).write(value); |
587 | } |
588 | |
589 | d->properties.append(t: PropertyEntry(name, value)); |
590 | return; |
591 | } |
592 | } |
593 | |
594 | for (auto it = d->properties.begin(), end = d->properties.end(); it != end; ++it) { |
595 | if (it->first == name) { |
596 | it->second = value; |
597 | if (state() && state()->isStateActive()) |
598 | d->property(property: name).write(value); |
599 | return; |
600 | } |
601 | } |
602 | |
603 | QQuickStateAction action; |
604 | action.restore = restoreEntryValues(); |
605 | action.property = d->property(property: name); |
606 | action.fromValue = action.property.read(); |
607 | action.specifiedObject = object(); |
608 | action.specifiedProperty = name; |
609 | action.toValue = value; |
610 | |
611 | d->properties.append(t: PropertyEntry(name, value)); |
612 | if (state() && state()->isStateActive()) { |
613 | state()->addEntryToRevertList(action); |
614 | QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(that: action.property); |
615 | if (oldBinding) |
616 | oldBinding->setEnabled(e: false, f: QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor); |
617 | d->property(property: name).write(value); |
618 | } |
619 | } |
620 | |
621 | void QQuickPropertyChanges::changeExpression(const QString &name, const QString &expression) |
622 | { |
623 | Q_D(QQuickPropertyChanges); |
624 | typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry; |
625 | |
626 | bool hadValue = false; |
627 | |
628 | for (auto it = d->properties.begin(), end = d->properties.end(); it != end; ++it) { |
629 | if (it->first == name) { |
630 | d->properties.erase(pos: it); |
631 | hadValue = true; |
632 | break; |
633 | } |
634 | } |
635 | |
636 | for (auto it = d->expressions.begin(), end = d->expressions.end(); it != end; ++it) { |
637 | if (it->name == name) { |
638 | it->expression = expression; |
639 | if (state() && state()->isStateActive()) { |
640 | auto prop = d->property(property: name); |
641 | auto context = QQmlContextData::get(context: qmlContext(this)); |
642 | QString url; |
643 | int lineNumber = 0; |
644 | QQmlAnyBinding::createFromCodeString(prop, code: expression, obj: object(), ctxt: context, url, lineNumber).installOn(target: prop); |
645 | } |
646 | return; |
647 | } |
648 | } |
649 | |
650 | // adding a new expression. |
651 | d->expressions.append(t: ExpressionEntry(name, nullptr, QQmlBinding::Invalid, expression, QUrl(), -1, -1)); |
652 | |
653 | const QString url; |
654 | const quint16 lineNumber = 0; |
655 | if (state() && state()->isStateActive()) { |
656 | if (hadValue) { |
657 | auto prop = d->property(property: name); |
658 | QQmlAnyBinding oldBinding = QQmlAnyBinding::takeFrom(prop); |
659 | if (oldBinding) |
660 | state()->changeBindingInRevertList(target: object(), name, binding: oldBinding); |
661 | |
662 | QQmlAnyBinding::createFromCodeString(prop, code: expression, obj: object(), ctxt: QQmlContextData::get(context: qmlContext(this)), url, lineNumber).installOn(target: prop); |
663 | } else { |
664 | QQuickStateAction action; |
665 | action.restore = restoreEntryValues(); |
666 | action.property = d->property(property: name); |
667 | action.fromValue = action.property.read(); |
668 | action.specifiedObject = object(); |
669 | action.specifiedProperty = name; |
670 | |
671 | QQmlAnyBinding newBinding; |
672 | if (d->isExplicit) { |
673 | newBinding = QQmlBinding::create( |
674 | &QQmlPropertyPrivate::get(p: action.property)->core, expression, |
675 | object(), QQmlContextData::get(context: qmlContext(this))); |
676 | } else { |
677 | const auto prop = action.property; |
678 | const auto context = QQmlContextData::get(context: qmlContext(this)); |
679 | newBinding = QQmlAnyBinding::createFromCodeString(prop, code: expression, obj: object(), ctxt: context, url, lineNumber); |
680 | } |
681 | if (d->isExplicit) { |
682 | // don't assign the binding, merely evaluate the expression. |
683 | // XXX TODO: add a static QQmlJavaScriptExpression::evaluate(QString) |
684 | // so that we can avoid creating then destroying the binding in this case. |
685 | action.toValue = static_cast<QQmlBinding *>(newBinding.asAbstractBinding())->evaluate(); |
686 | } else { |
687 | // TODO: replace binding would be more efficient for new-style properties |
688 | QQmlAnyBinding::removeBindingFrom(prop&: action.property); |
689 | newBinding.installOn(target: action.property); |
690 | action.toBinding = newBinding; |
691 | action.deletableToBinding = true; |
692 | state()->addEntryToRevertList(action); |
693 | } |
694 | } |
695 | } |
696 | // what about the signal handler? |
697 | } |
698 | |
699 | QVariant QQuickPropertyChanges::property(const QString &name) const |
700 | { |
701 | Q_D(const QQuickPropertyChanges); |
702 | typedef QPair<QString, QVariant> PropertyEntry; |
703 | typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry; |
704 | |
705 | for (const PropertyEntry &entry : d->properties) { |
706 | if (entry.first == name) { |
707 | return entry.second; |
708 | } |
709 | } |
710 | |
711 | for (const ExpressionEntry &entry : d->expressions) { |
712 | if (entry.name == name) { |
713 | return QVariant(entry.expression); |
714 | } |
715 | } |
716 | |
717 | return QVariant(); |
718 | } |
719 | |
720 | void QQuickPropertyChanges::removeProperty(const QString &name) |
721 | { |
722 | Q_D(QQuickPropertyChanges); |
723 | |
724 | for (auto it = d->expressions.begin(), end = d->expressions.end(); it != end; ++it) { |
725 | if (it->name == name) { |
726 | d->expressions.erase(pos: it); |
727 | state()->removeEntryFromRevertList(target: object(), name); |
728 | return; |
729 | } |
730 | } |
731 | |
732 | for (auto it = d->properties.begin(), end = d->properties.end(); it != end; ++it) { |
733 | if (it->first == name) { |
734 | d->properties.erase(pos: it); |
735 | state()->removeEntryFromRevertList(target: object(), name); |
736 | return; |
737 | } |
738 | } |
739 | } |
740 | |
741 | QVariant QQuickPropertyChanges::value(const QString &name) const |
742 | { |
743 | Q_D(const QQuickPropertyChanges); |
744 | typedef QPair<QString, QVariant> PropertyEntry; |
745 | |
746 | for (const PropertyEntry &entry : d->properties) { |
747 | if (entry.first == name) { |
748 | return entry.second; |
749 | } |
750 | } |
751 | |
752 | return QVariant(); |
753 | } |
754 | |
755 | QString QQuickPropertyChanges::expression(const QString &name) const |
756 | { |
757 | Q_D(const QQuickPropertyChanges); |
758 | typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry; |
759 | |
760 | for (const ExpressionEntry &entry : d->expressions) { |
761 | if (entry.name == name) { |
762 | return entry.expression; |
763 | } |
764 | } |
765 | |
766 | return QString(); |
767 | } |
768 | |
769 | void QQuickPropertyChanges::detachFromState() |
770 | { |
771 | if (state()) |
772 | state()->removeAllEntriesFromRevertList(target: object()); |
773 | } |
774 | |
775 | void QQuickPropertyChanges::attachToState() |
776 | { |
777 | if (state()) |
778 | state()->addEntriesToRevertList(actions: actions()); |
779 | } |
780 | |
781 | QT_END_NAMESPACE |
782 | |
783 | #include "moc_qquickpropertychanges_p.cpp" |
784 |
Definitions
- QQuickReplaceSignalHandler
- QQuickReplaceSignalHandler
- ~QQuickReplaceSignalHandler
- type
- execute
- isReversable
- reverse
- saveOriginals
- needsCopy
- copyOriginals
- rewind
- saveCurrentValues
- mayOverride
- QQuickPropertyChangesPrivate
- QQuickPropertyChangesPrivate
- ExpressionChange
- ExpressionChange
- verifyList
- decode
- decodeBinding
- verifyBindings
- applyBindings
- QQuickPropertyChanges
- ~QQuickPropertyChanges
- object
- setObject
- restoreEntryValues
- setRestoreEntryValues
- property
- actions
- isExplicit
- setIsExplicit
- containsValue
- containsExpression
- containsProperty
- changeValue
- changeExpression
- property
- removeProperty
- value
- expression
- detachFromState
Start learning QML with our Intro Training
Find out more