1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickpropertychanges_p.h"
44 #include <private/qqmlopenmetaobject_p.h>
45 #include <private/qqmlrewrite_p.h>
46 #include <private/qqmlengine_p.h>
49 #include <private/qqmlcustomparser_p.h>
50 #include <private/qqmlscript_p.h>
51 #include <qqmlexpression.h>
52 #include <private/qqmlbinding_p.h>
53 #include <qqmlcontext.h>
54 #include <private/qqmlguard_p.h>
55 #include <private/qqmlproperty_p.h>
56 #include <private/qqmlcontext_p.h>
57 #include <private/qquickstate_p_p.h>
58 #include <private/qqmlboundsignal_p.h>
60 #include <QtCore/qdebug.h>
62 #include <private/qobject_p.h>
67 \qmlclass PropertyChanges QQuickPropertyChanges
68 \inqmlmodule QtQuick 2
69 \ingroup qml-state-elements
70 \brief The PropertyChanges element describes new property bindings or values for a state.
72 PropertyChanges is used to define the property values or bindings in a
73 \l State. This enables an item's property values to be changed when it
74 \l {States}{changes between states}.
76 To create a PropertyChanges object, specify the \l target item whose
77 properties are to be modified, and define the new property values or
78 bindings. For example:
80 \snippet doc/src/snippets/qml/propertychanges.qml import
82 \snippet doc/src/snippets/qml/propertychanges.qml 0
84 When the mouse is pressed, the \l Rectangle changes to the \e resized
85 state. In this state, the PropertyChanges object sets the rectangle's
86 color to blue and the \c height value to that of \c container.height.
88 Note this automatically binds \c rect.height to \c container.height
89 in the \e resized state. If a property binding should not be
90 established, and the height should just be set to the value of
91 \c container.height at the time of the state change, set the \l explicit
94 A PropertyChanges object can also override the default signal handler
95 for an object to implement a signal handler specific to the new state:
100 onClicked: doSomethingDifferent()
104 \note PropertyChanges can be used to change anchor margins, but not other anchor
105 values; use AnchorChanges for this instead. Similarly, to change an \l Item's
106 \l {Item::}{parent} value, use ParentChanges instead.
109 \section2 Resetting property values
111 The \c undefined value can be used to reset the property value for a state.
112 In the following example, when \c myText changes to the \e widerText
113 state, its \c width property is reset, giving the text its natural width
114 and displaying the whole string on a single line.
116 \snippet doc/src/snippets/qml/propertychanges.qml reset
119 \section2 Immediate property changes in transitions
121 When \l{QML Animation and Transitions}{Transitions} are used to animate
122 state changes, they animate properties from their values in the current
123 state to those defined in the new state (as defined by PropertyChanges
124 objects). However, it is sometimes desirable to set a property value
125 \e immediately during a \l Transition, without animation; in these cases,
126 the PropertyAction element can be used to force an immediate property
129 See the PropertyAction documentation for more details.
131 \sa {declarative/animation/states}{states example}, {qmlstate}{States}, QtQml
135 \qmlproperty Object QtQuick2::PropertyChanges::target
136 This property holds the object which contains the properties to be changed.
139 class QQuickReplaceSignalHandler : public QQuickActionEvent
142 QQuickReplaceSignalHandler() {}
143 ~QQuickReplaceSignalHandler() {}
145 virtual EventType type() const { return SignalHandler; }
147 QQmlProperty property;
148 QQmlBoundSignalExpressionPointer expression;
149 QQmlBoundSignalExpressionPointer reverseExpression;
150 QQmlBoundSignalExpressionPointer rewindExpression;
152 virtual void execute(Reason) {
153 QQmlPropertyPrivate::setSignalExpression(property, expression);
156 virtual bool isReversable() { return true; }
157 virtual void reverse(Reason) {
158 QQmlPropertyPrivate::setSignalExpression(property, reverseExpression);
161 virtual void saveOriginals() {
163 reverseExpression = rewindExpression;
166 virtual bool needsCopy() { return true; }
167 virtual void copyOriginals(QQuickActionEvent *other)
169 QQuickReplaceSignalHandler *rsh = static_cast<QQuickReplaceSignalHandler*>(other);
173 reverseExpression = rsh->reverseExpression;
176 virtual void rewind() {
177 QQmlPropertyPrivate::setSignalExpression(property, rewindExpression);
179 virtual void saveCurrentValues() {
180 rewindExpression = QQmlPropertyPrivate::signalExpression(property);
183 virtual bool override(QQuickActionEvent*other) {
186 if (other->type() != type())
188 if (static_cast<QQuickReplaceSignalHandler*>(other)->property == property)
195 class QQuickPropertyChangesPrivate : public QQuickStateOperationPrivate
197 Q_DECLARE_PUBLIC(QQuickPropertyChanges)
199 QQuickPropertyChangesPrivate() : decoded(true), restore(true),
202 QQmlGuard<QObject> object;
211 class ExpressionChange {
213 ExpressionChange(const QString &_name,
214 QQmlBinding::Identifier _id,
215 const QString& _expr,
219 : name(_name), id(_id), expression(_expr), url(_url), line(_line), column(_column) {}
221 QQmlBinding::Identifier id;
228 QList<QPair<QString, QVariant> > properties;
229 QList<ExpressionChange> expressions;
230 QList<QQuickReplaceSignalHandler*> signalReplacements;
232 QQmlProperty property(const QString &);
236 QQuickPropertyChangesParser::compileList(QList<QPair<QString, QVariant> > &list,
238 const QQmlCustomParserProperty &prop)
240 QString propName = pre + prop.name();
242 QList<QVariant> values = prop.assignedValues();
243 for (int ii = 0; ii < values.count(); ++ii) {
244 const QVariant &value = values.at(ii);
246 if (value.userType() == qMetaTypeId<QQmlCustomParserNode>()) {
247 error(qvariant_cast<QQmlCustomParserNode>(value),
248 QQuickPropertyChanges::tr("PropertyChanges does not support creating state-specific objects."));
250 } else if(value.userType() == qMetaTypeId<QQmlCustomParserProperty>()) {
252 QQmlCustomParserProperty prop =
253 qvariant_cast<QQmlCustomParserProperty>(value);
254 QString pre = propName + QLatin1Char('.');
255 compileList(list, pre, prop);
258 list << qMakePair(propName, value);
264 QQuickPropertyChangesParser::compile(const QList<QQmlCustomParserProperty> &props)
266 QList<QPair<QString, QVariant> > data;
267 for(int ii = 0; ii < props.count(); ++ii)
268 compileList(data, QString(), props.at(ii));
271 QDataStream ds(&rv, QIODevice::WriteOnly);
274 for(int ii = 0; ii < data.count(); ++ii) {
275 QQmlScript::Variant v = qvariant_cast<QQmlScript::Variant>(data.at(ii).second);
277 bool isScript = v.isScript();
278 QQmlBinding::Identifier id = 0;
280 case QQmlScript::Variant::Boolean:
281 var = QVariant(v.asBoolean());
283 case QQmlScript::Variant::Number:
284 var = QVariant(v.asNumber());
286 case QQmlScript::Variant::String:
287 var = QVariant(v.asString());
289 case QQmlScript::Variant::Invalid:
290 case QQmlScript::Variant::Script:
291 var = QVariant(v.asScript());
293 // Pre-rewrite the expression
294 id = rewriteBinding(v, data.at(ii).first);
299 ds << data.at(ii).first << isScript << var;
307 void QQuickPropertyChangesPrivate::decode()
309 Q_Q(QQuickPropertyChanges);
313 QDataStream ds(&data, QIODevice::ReadOnly);
317 for (int ii = 0; ii < count; ++ii) {
321 QQmlBinding::Identifier id = QQmlBinding::Invalid;
328 QQmlProperty prop = property(name); //### better way to check for signal property?
329 if (prop.type() & QQmlProperty::SignalProperty) {
330 QString expression = data.toString();
335 QQmlData *ddata = QQmlData::get(q);
336 if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty()) {
337 url = ddata->outerContext->url;
338 line = ddata->lineNumber;
339 column = ddata->columnNumber;
342 QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler;
343 handler->property = prop;
344 handler->expression.take(new QQmlBoundSignalExpression(QQmlContextData::get(qmlContext(q)), object, expression, false, url.toString(), line, column));
345 signalReplacements << handler;
346 } else if (isScript) { // binding
347 QString expression = data.toString();
352 QQmlData *ddata = QQmlData::get(q);
353 if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty()) {
354 url = ddata->outerContext->url;
355 line = ddata->lineNumber;
356 column = ddata->columnNumber;
359 expressions << ExpressionChange(name, id, expression, url, line, column);
361 properties << qMakePair(name, data);
368 void QQuickPropertyChangesParser::setCustomData(QObject *object,
369 const QByteArray &data)
371 QQuickPropertyChangesPrivate *p =
372 static_cast<QQuickPropertyChangesPrivate *>(QObjectPrivate::get(object));
377 QQuickPropertyChanges::QQuickPropertyChanges()
378 : QQuickStateOperation(*(new QQuickPropertyChangesPrivate))
382 QQuickPropertyChanges::~QQuickPropertyChanges()
384 Q_D(QQuickPropertyChanges);
385 for(int ii = 0; ii < d->signalReplacements.count(); ++ii)
386 delete d->signalReplacements.at(ii);
389 QObject *QQuickPropertyChanges::object() const
391 Q_D(const QQuickPropertyChanges);
395 void QQuickPropertyChanges::setObject(QObject *o)
397 Q_D(QQuickPropertyChanges);
402 \qmlproperty bool QtQuick2::PropertyChanges::restoreEntryValues
404 This property holds whether the previous values should be restored when
407 The default value is \c true. Setting this value to \c false creates a
408 temporary state that has permanent effects on property values.
410 bool QQuickPropertyChanges::restoreEntryValues() const
412 Q_D(const QQuickPropertyChanges);
416 void QQuickPropertyChanges::setRestoreEntryValues(bool v)
418 Q_D(QQuickPropertyChanges);
423 QQuickPropertyChangesPrivate::property(const QString &property)
425 Q_Q(QQuickPropertyChanges);
426 QQmlProperty prop(object, property, qmlContext(q));
427 if (!prop.isValid()) {
428 qmlInfo(q) << QQuickPropertyChanges::tr("Cannot assign to non-existent property \"%1\"").arg(property);
429 return QQmlProperty();
430 } else if (!(prop.type() & QQmlProperty::SignalProperty) && !prop.isWritable()) {
431 qmlInfo(q) << QQuickPropertyChanges::tr("Cannot assign to read-only property \"%1\"").arg(property);
432 return QQmlProperty();
437 QQuickPropertyChanges::ActionList QQuickPropertyChanges::actions()
439 Q_D(QQuickPropertyChanges);
445 for (int ii = 0; ii < d->properties.count(); ++ii) {
447 QQuickAction a(d->object, d->properties.at(ii).first,
448 qmlContext(this), d->properties.at(ii).second);
450 if (a.property.isValid()) {
451 a.restore = restoreEntryValues();
456 for (int ii = 0; ii < d->signalReplacements.count(); ++ii) {
458 QQuickReplaceSignalHandler *handler = d->signalReplacements.at(ii);
460 if (handler->property.isValid()) {
467 for (int ii = 0; ii < d->expressions.count(); ++ii) {
469 QQuickPropertyChangesPrivate::ExpressionChange e = d->expressions.at(ii);
470 const QString &property = e.name;
471 QQmlProperty prop = d->property(property);
473 if (prop.isValid()) {
475 a.restore = restoreEntryValues();
477 a.fromValue = a.property.read();
478 a.specifiedObject = d->object;
479 a.specifiedProperty = property;
481 QQmlBinding *newBinding = e.id != QQmlBinding::Invalid ? QQmlBinding::createBinding(e.id, object(), qmlContext(this), e.url.toString(), e.column) : 0;
483 newBinding = new QQmlBinding(e.expression, false, object(), QQmlContextData::get(qmlContext(this)), e.url.toString(), e.line, e.column);
486 // in this case, we don't want to assign a binding, per se,
487 // so we evaluate the expression and assign the result.
488 // XXX TODO: add a static QQmlJavaScriptExpression::evaluate(QString)
489 // so that we can avoid creating then destroying the binding in this case.
490 a.toValue = newBinding->evaluate();
491 newBinding->destroy();
493 newBinding->setTarget(prop);
494 a.toBinding = QQmlAbstractBinding::getPointer(newBinding);
495 a.deletableToBinding = true;
506 \qmlproperty bool QtQuick2::PropertyChanges::explicit
508 If explicit is set to true, any potential bindings will be interpreted as
509 once-off assignments that occur when the state is entered.
511 In the following example, the addition of explicit prevents \c myItem.width from
512 being bound to \c parent.width. Instead, it is assigned the value of \c parent.width
513 at the time of the state change.
522 By default, explicit is false.
524 bool QQuickPropertyChanges::isExplicit() const
526 Q_D(const QQuickPropertyChanges);
527 return d->isExplicit;
530 void QQuickPropertyChanges::setIsExplicit(bool e)
532 Q_D(QQuickPropertyChanges);
536 bool QQuickPropertyChanges::containsValue(const QString &name) const
538 Q_D(const QQuickPropertyChanges);
539 typedef QPair<QString, QVariant> PropertyEntry;
541 QListIterator<PropertyEntry> propertyIterator(d->properties);
542 while (propertyIterator.hasNext()) {
543 const PropertyEntry &entry = propertyIterator.next();
544 if (entry.first == name) {
552 bool QQuickPropertyChanges::containsExpression(const QString &name) const
554 Q_D(const QQuickPropertyChanges);
555 typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry;
557 QListIterator<ExpressionEntry> expressionIterator(d->expressions);
558 while (expressionIterator.hasNext()) {
559 const ExpressionEntry &entry = expressionIterator.next();
560 if (entry.name == name) {
568 bool QQuickPropertyChanges::containsProperty(const QString &name) const
570 return containsValue(name) || containsExpression(name);
573 void QQuickPropertyChanges::changeValue(const QString &name, const QVariant &value)
575 Q_D(QQuickPropertyChanges);
576 typedef QPair<QString, QVariant> PropertyEntry;
577 typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry;
579 QMutableListIterator<ExpressionEntry> expressionIterator(d->expressions);
580 while (expressionIterator.hasNext()) {
581 const ExpressionEntry &entry = expressionIterator.next();
582 if (entry.name == name) {
583 expressionIterator.remove();
584 if (state() && state()->isStateActive()) {
585 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(d->property(name));
587 QQmlPropertyPrivate::setBinding(d->property(name), 0);
588 oldBinding->destroy();
590 d->property(name).write(value);
593 d->properties.append(PropertyEntry(name, value));
598 QMutableListIterator<PropertyEntry> propertyIterator(d->properties);
599 while (propertyIterator.hasNext()) {
600 PropertyEntry &entry = propertyIterator.next();
601 if (entry.first == name) {
602 entry.second = value;
603 if (state() && state()->isStateActive())
604 d->property(name).write(value);
610 action.restore = restoreEntryValues();
611 action.property = d->property(name);
612 action.fromValue = action.property.read();
613 action.specifiedObject = object();
614 action.specifiedProperty = name;
615 action.toValue = value;
617 propertyIterator.insert(PropertyEntry(name, value));
618 if (state() && state()->isStateActive()) {
619 state()->addEntryToRevertList(action);
620 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(action.property);
622 oldBinding->setEnabled(false, QQmlPropertyPrivate::DontRemoveBinding | QQmlPropertyPrivate::BypassInterceptor);
623 d->property(name).write(value);
627 void QQuickPropertyChanges::changeExpression(const QString &name, const QString &expression)
629 Q_D(QQuickPropertyChanges);
630 typedef QPair<QString, QVariant> PropertyEntry;
631 typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry;
633 bool hadValue = false;
635 QMutableListIterator<PropertyEntry> propertyIterator(d->properties);
636 while (propertyIterator.hasNext()) {
637 PropertyEntry &entry = propertyIterator.next();
638 if (entry.first == name) {
639 propertyIterator.remove();
645 QMutableListIterator<ExpressionEntry> expressionIterator(d->expressions);
646 while (expressionIterator.hasNext()) {
647 ExpressionEntry &entry = expressionIterator.next();
648 if (entry.name == name) {
649 entry.expression = expression;
650 if (state() && state()->isStateActive()) {
651 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(d->property(name));
653 QQmlPropertyPrivate::setBinding(d->property(name), 0);
654 oldBinding->destroy();
657 QQmlBinding *newBinding = new QQmlBinding(expression, object(), qmlContext(this));
658 newBinding->setTarget(d->property(name));
659 QQmlPropertyPrivate::setBinding(d->property(name), newBinding, QQmlPropertyPrivate::DontRemoveBinding | QQmlPropertyPrivate::BypassInterceptor);
665 // adding a new expression.
666 expressionIterator.insert(ExpressionEntry(name, QQmlBinding::Invalid, expression, QUrl(), -1, -1));
668 if (state() && state()->isStateActive()) {
670 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(d->property(name));
672 oldBinding->setEnabled(false, QQmlPropertyPrivate::DontRemoveBinding | QQmlPropertyPrivate::BypassInterceptor);
673 state()->changeBindingInRevertList(object(), name, oldBinding);
676 QQmlBinding *newBinding = new QQmlBinding(expression, object(), qmlContext(this));
677 newBinding->setTarget(d->property(name));
678 QQmlPropertyPrivate::setBinding(d->property(name), newBinding, QQmlPropertyPrivate::DontRemoveBinding | QQmlPropertyPrivate::BypassInterceptor);
681 action.restore = restoreEntryValues();
682 action.property = d->property(name);
683 action.fromValue = action.property.read();
684 action.specifiedObject = object();
685 action.specifiedProperty = name;
687 QQmlBinding *newBinding = new QQmlBinding(expression, object(), qmlContext(this));
689 // don't assign the binding, merely evaluate the expression.
690 // XXX TODO: add a static QQmlJavaScriptExpression::evaluate(QString)
691 // so that we can avoid creating then destroying the binding in this case.
692 action.toValue = newBinding->evaluate();
693 newBinding->destroy();
695 newBinding->setTarget(d->property(name));
696 action.toBinding = QQmlAbstractBinding::getPointer(newBinding);
697 action.deletableToBinding = true;
699 state()->addEntryToRevertList(action);
700 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(action.property);
702 oldBinding->setEnabled(false, QQmlPropertyPrivate::DontRemoveBinding | QQmlPropertyPrivate::BypassInterceptor);
704 QQmlPropertyPrivate::setBinding(action.property, newBinding, QQmlPropertyPrivate::DontRemoveBinding | QQmlPropertyPrivate::BypassInterceptor);
708 // what about the signal handler?
711 QVariant QQuickPropertyChanges::property(const QString &name) const
713 Q_D(const QQuickPropertyChanges);
714 typedef QPair<QString, QVariant> PropertyEntry;
715 typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry;
717 QListIterator<PropertyEntry> propertyIterator(d->properties);
718 while (propertyIterator.hasNext()) {
719 const PropertyEntry &entry = propertyIterator.next();
720 if (entry.first == name) {
725 QListIterator<ExpressionEntry> expressionIterator(d->expressions);
726 while (expressionIterator.hasNext()) {
727 const ExpressionEntry &entry = expressionIterator.next();
728 if (entry.name == name) {
729 return QVariant(entry.expression);
736 void QQuickPropertyChanges::removeProperty(const QString &name)
738 Q_D(QQuickPropertyChanges);
739 typedef QPair<QString, QVariant> PropertyEntry;
740 typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry;
742 QMutableListIterator<ExpressionEntry> expressionIterator(d->expressions);
743 while (expressionIterator.hasNext()) {
744 const ExpressionEntry &entry = expressionIterator.next();
745 if (entry.name == name) {
746 expressionIterator.remove();
747 state()->removeEntryFromRevertList(object(), name);
752 QMutableListIterator<PropertyEntry> propertyIterator(d->properties);
753 while (propertyIterator.hasNext()) {
754 const PropertyEntry &entry = propertyIterator.next();
755 if (entry.first == name) {
756 propertyIterator.remove();
757 state()->removeEntryFromRevertList(object(), name);
763 QVariant QQuickPropertyChanges::value(const QString &name) const
765 Q_D(const QQuickPropertyChanges);
766 typedef QPair<QString, QVariant> PropertyEntry;
768 QListIterator<PropertyEntry> propertyIterator(d->properties);
769 while (propertyIterator.hasNext()) {
770 const PropertyEntry &entry = propertyIterator.next();
771 if (entry.first == name) {
779 QString QQuickPropertyChanges::expression(const QString &name) const
781 Q_D(const QQuickPropertyChanges);
782 typedef QQuickPropertyChangesPrivate::ExpressionChange ExpressionEntry;
784 QListIterator<ExpressionEntry> expressionIterator(d->expressions);
785 while (expressionIterator.hasNext()) {
786 const ExpressionEntry &entry = expressionIterator.next();
787 if (entry.name == name) {
788 return entry.expression;
795 void QQuickPropertyChanges::detachFromState()
798 state()->removeAllEntriesFromRevertList(object());
801 void QQuickPropertyChanges::attachToState()
804 state()->addEntriesToRevertList(actions());