1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "private/qdeclarativepropertychanges_p.h"
44 #include "private/qdeclarativeopenmetaobject_p.h"
45 #include "private/qdeclarativerewrite_p.h"
46 #include "private/qdeclarativeengine_p.h"
47 #include "private/qdeclarativecompiler_p.h"
49 #include <qdeclarativeinfo.h>
50 #include <qdeclarativecustomparser_p.h>
51 #include <qdeclarativeparser_p.h>
52 #include <qdeclarativeexpression.h>
53 #include <qdeclarativebinding_p.h>
54 #include <qdeclarativecontext.h>
55 #include <qdeclarativeguard_p.h>
56 #include <qdeclarativeproperty_p.h>
57 #include <qdeclarativecontext_p.h>
58 #include <qdeclarativestate_p_p.h>
60 #include <QtCore/qdebug.h>
62 #include <private/qobject_p.h>
67 \qmlclass PropertyChanges QDeclarativePropertyChanges
68 \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 {QML 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/declarative/propertychanges.qml import
82 \snippet doc/src/snippets/declarative/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 theText 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/declarative/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}, QtDeclarative
135 \qmlproperty Object PropertyChanges::target
136 This property holds the object which contains the properties to be changed.
139 class QDeclarativeReplaceSignalHandler : public QDeclarativeActionEvent
142 QDeclarativeReplaceSignalHandler() : expression(0), reverseExpression(0),
143 rewindExpression(0), ownedExpression(0) {}
144 ~QDeclarativeReplaceSignalHandler() {
145 delete ownedExpression;
148 virtual QString typeName() const { return QLatin1String("ReplaceSignalHandler"); }
150 QDeclarativeProperty property;
151 QDeclarativeExpression *expression;
152 QDeclarativeExpression *reverseExpression;
153 QDeclarativeExpression *rewindExpression;
154 QDeclarativeGuard<QDeclarativeExpression> ownedExpression;
156 virtual void execute(Reason) {
157 ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, expression);
158 if (ownedExpression == expression)
162 virtual bool isReversable() { return true; }
163 virtual void reverse(Reason) {
164 ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, reverseExpression);
165 if (ownedExpression == reverseExpression)
169 virtual void saveOriginals() {
171 reverseExpression = rewindExpression;
174 /*virtual void copyOriginals(QDeclarativeActionEvent *other)
176 QDeclarativeReplaceSignalHandler *rsh = static_cast<QDeclarativeReplaceSignalHandler*>(other);
180 reverseExpression = rsh->reverseExpression;
181 if (rsh->ownedExpression == reverseExpression) {
182 ownedExpression = rsh->ownedExpression;
183 rsh->ownedExpression = 0;
187 virtual void rewind() {
188 ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, rewindExpression);
189 if (ownedExpression == rewindExpression)
192 virtual void saveCurrentValues() {
193 rewindExpression = QDeclarativePropertyPrivate::signalExpression(property);
196 virtual bool override(QDeclarativeActionEvent*other) {
199 if (other->typeName() != typeName())
201 if (static_cast<QDeclarativeReplaceSignalHandler*>(other)->property == property)
208 class QDeclarativePropertyChangesPrivate : public QDeclarativeStateOperationPrivate
210 Q_DECLARE_PUBLIC(QDeclarativePropertyChanges)
212 QDeclarativePropertyChangesPrivate() : decoded(true), restore(true),
215 QDeclarativeGuard<QObject> object;
224 class ExpressionChange {
226 ExpressionChange(const QString &_name,
227 QDeclarativeBinding::Identifier _id,
228 QDeclarativeExpression *_expr)
229 : name(_name), id(_id), expression(_expr) {}
231 QDeclarativeBinding::Identifier id;
232 QDeclarativeExpression *expression;
235 QList<QPair<QString, QVariant> > properties;
236 QList<ExpressionChange> expressions;
237 QList<QDeclarativeReplaceSignalHandler*> signalReplacements;
239 QDeclarativeProperty property(const QString &);
243 QDeclarativePropertyChangesParser::compileList(QList<QPair<QByteArray, QVariant> > &list,
244 const QByteArray &pre,
245 const QDeclarativeCustomParserProperty &prop)
247 QByteArray propName = pre + prop.name();
249 QList<QVariant> values = prop.assignedValues();
250 for (int ii = 0; ii < values.count(); ++ii) {
251 const QVariant &value = values.at(ii);
253 if (value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
254 error(qvariant_cast<QDeclarativeCustomParserNode>(value),
255 QDeclarativePropertyChanges::tr("PropertyChanges does not support creating state-specific objects."));
257 } else if(value.userType() == qMetaTypeId<QDeclarativeCustomParserProperty>()) {
259 QDeclarativeCustomParserProperty prop =
260 qvariant_cast<QDeclarativeCustomParserProperty>(value);
261 QByteArray pre = propName + '.';
262 compileList(list, pre, prop);
265 list << qMakePair(propName, value);
271 QDeclarativePropertyChangesParser::compile(const QList<QDeclarativeCustomParserProperty> &props)
273 QList<QPair<QByteArray, QVariant> > data;
274 for(int ii = 0; ii < props.count(); ++ii)
275 compileList(data, QByteArray(), props.at(ii));
278 QDataStream ds(&rv, QIODevice::WriteOnly);
281 for(int ii = 0; ii < data.count(); ++ii) {
282 QDeclarativeParser::Variant v = qvariant_cast<QDeclarativeParser::Variant>(data.at(ii).second);
284 bool isScript = v.isScript();
285 QDeclarativeBinding::Identifier id = 0;
287 case QDeclarativeParser::Variant::Boolean:
288 var = QVariant(v.asBoolean());
290 case QDeclarativeParser::Variant::Number:
291 var = QVariant(v.asNumber());
293 case QDeclarativeParser::Variant::String:
294 var = QVariant(v.asString());
296 case QDeclarativeParser::Variant::Invalid:
297 case QDeclarativeParser::Variant::Script:
298 var = QVariant(v.asScript());
300 // Pre-rewrite the expression
301 QString expression = v.asScript();
302 id = rewriteBinding(expression, data.at(ii).first); //### recreates the AST, which is slow
307 ds << QString::fromUtf8(data.at(ii).first) << isScript << var;
315 void QDeclarativePropertyChangesPrivate::decode()
317 Q_Q(QDeclarativePropertyChanges);
321 QDataStream ds(&data, QIODevice::ReadOnly);
325 for (int ii = 0; ii < count; ++ii) {
329 QDeclarativeBinding::Identifier id = QDeclarativeBinding::Invalid;
336 QDeclarativeProperty prop = property(name); //### better way to check for signal property?
337 if (prop.type() & QDeclarativeProperty::SignalProperty) {
338 QDeclarativeExpression *expression = new QDeclarativeExpression(qmlContext(q), object, data.toString());
339 QDeclarativeData *ddata = QDeclarativeData::get(q);
340 if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty())
341 expression->setSourceLocation(ddata->outerContext->url.toString(), ddata->lineNumber);
342 QDeclarativeReplaceSignalHandler *handler = new QDeclarativeReplaceSignalHandler;
343 handler->property = prop;
344 handler->expression = expression;
345 signalReplacements << handler;
346 } else if (isScript) {
347 QDeclarativeExpression *expression = new QDeclarativeExpression(qmlContext(q), object, data.toString());
348 QDeclarativeData *ddata = QDeclarativeData::get(q);
349 if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty())
350 expression->setSourceLocation(ddata->outerContext->url.toString(), ddata->lineNumber);
351 expressions << ExpressionChange(name, id, expression);
353 properties << qMakePair(name, data);
361 void QDeclarativePropertyChangesParser::setCustomData(QObject *object,
362 const QByteArray &data)
364 QDeclarativePropertyChangesPrivate *p =
365 static_cast<QDeclarativePropertyChangesPrivate *>(QObjectPrivate::get(object));
370 QDeclarativePropertyChanges::QDeclarativePropertyChanges()
371 : QDeclarativeStateOperation(*(new QDeclarativePropertyChangesPrivate))
375 QDeclarativePropertyChanges::~QDeclarativePropertyChanges()
377 Q_D(QDeclarativePropertyChanges);
378 for(int ii = 0; ii < d->expressions.count(); ++ii)
379 delete d->expressions.at(ii).expression;
380 for(int ii = 0; ii < d->signalReplacements.count(); ++ii)
381 delete d->signalReplacements.at(ii);
384 QObject *QDeclarativePropertyChanges::object() const
386 Q_D(const QDeclarativePropertyChanges);
390 void QDeclarativePropertyChanges::setObject(QObject *o)
392 Q_D(QDeclarativePropertyChanges);
397 \qmlproperty bool PropertyChanges::restoreEntryValues
399 This property holds whether the previous values should be restored when
402 The default value is \c true. Setting this value to \c false creates a
403 temporary state that has permanent effects on property values.
405 bool QDeclarativePropertyChanges::restoreEntryValues() const
407 Q_D(const QDeclarativePropertyChanges);
411 void QDeclarativePropertyChanges::setRestoreEntryValues(bool v)
413 Q_D(QDeclarativePropertyChanges);
418 QDeclarativePropertyChangesPrivate::property(const QString &property)
420 Q_Q(QDeclarativePropertyChanges);
421 QDeclarativeProperty prop(object, property, qmlContext(q));
422 if (!prop.isValid()) {
423 qmlInfo(q) << QDeclarativePropertyChanges::tr("Cannot assign to non-existent property \"%1\"").arg(property);
424 return QDeclarativeProperty();
425 } else if (!(prop.type() & QDeclarativeProperty::SignalProperty) && !prop.isWritable()) {
426 qmlInfo(q) << QDeclarativePropertyChanges::tr("Cannot assign to read-only property \"%1\"").arg(property);
427 return QDeclarativeProperty();
432 QDeclarativePropertyChanges::ActionList QDeclarativePropertyChanges::actions()
434 Q_D(QDeclarativePropertyChanges);
440 for (int ii = 0; ii < d->properties.count(); ++ii) {
442 QDeclarativeAction a(d->object, d->properties.at(ii).first,
443 qmlContext(this), d->properties.at(ii).second);
445 if (a.property.isValid()) {
446 a.restore = restoreEntryValues();
451 for (int ii = 0; ii < d->signalReplacements.count(); ++ii) {
453 QDeclarativeReplaceSignalHandler *handler = d->signalReplacements.at(ii);
455 if (handler->property.isValid()) {
456 QDeclarativeAction a;
462 for (int ii = 0; ii < d->expressions.count(); ++ii) {
464 const QString &property = d->expressions.at(ii).name;
465 QDeclarativeProperty prop = d->property(property);
467 if (prop.isValid()) {
468 QDeclarativeAction a;
469 a.restore = restoreEntryValues();
471 a.fromValue = a.property.read();
472 a.specifiedObject = d->object;
473 a.specifiedProperty = property;
476 a.toValue = d->expressions.at(ii).expression->evaluate();
478 QDeclarativeExpression *e = d->expressions.at(ii).expression;
480 QDeclarativeBinding::Identifier id = d->expressions.at(ii).id;
481 QDeclarativeBinding *newBinding = id != QDeclarativeBinding::Invalid ? QDeclarativeBinding::createBinding(id, object(), qmlContext(this), e->sourceFile(), e->lineNumber()) : 0;
483 newBinding = new QDeclarativeBinding(e->expression(), object(), qmlContext(this));
484 newBinding->setSourceLocation(e->sourceFile(), e->lineNumber());
486 newBinding->setTarget(prop);
487 a.toBinding = newBinding;
488 a.deletableToBinding = true;
499 \qmlproperty bool PropertyChanges::explicit
501 If explicit is set to true, any potential bindings will be interpreted as
502 once-off assignments that occur when the state is entered.
504 In the following example, the addition of explicit prevents \c myItem.width from
505 being bound to \c parent.width. Instead, it is assigned the value of \c parent.width
506 at the time of the state change.
515 By default, explicit is false.
517 bool QDeclarativePropertyChanges::isExplicit() const
519 Q_D(const QDeclarativePropertyChanges);
520 return d->isExplicit;
523 void QDeclarativePropertyChanges::setIsExplicit(bool e)
525 Q_D(QDeclarativePropertyChanges);
529 bool QDeclarativePropertyChanges::containsValue(const QString &name) const
531 Q_D(const QDeclarativePropertyChanges);
532 typedef QPair<QString, QVariant> PropertyEntry;
534 QListIterator<PropertyEntry> propertyIterator(d->properties);
535 while (propertyIterator.hasNext()) {
536 const PropertyEntry &entry = propertyIterator.next();
537 if (entry.first == name) {
545 bool QDeclarativePropertyChanges::containsExpression(const QString &name) const
547 Q_D(const QDeclarativePropertyChanges);
548 typedef QDeclarativePropertyChangesPrivate::ExpressionChange ExpressionEntry;
550 QListIterator<ExpressionEntry> expressionIterator(d->expressions);
551 while (expressionIterator.hasNext()) {
552 const ExpressionEntry &entry = expressionIterator.next();
553 if (entry.name == name) {
561 bool QDeclarativePropertyChanges::containsProperty(const QString &name) const
563 return containsValue(name) || containsExpression(name);
566 void QDeclarativePropertyChanges::changeValue(const QString &name, const QVariant &value)
568 Q_D(QDeclarativePropertyChanges);
569 typedef QPair<QString, QVariant> PropertyEntry;
570 typedef QDeclarativePropertyChangesPrivate::ExpressionChange ExpressionEntry;
572 QMutableListIterator<ExpressionEntry> expressionIterator(d->expressions);
573 while (expressionIterator.hasNext()) {
574 const ExpressionEntry &entry = expressionIterator.next();
575 if (entry.name == name) {
576 expressionIterator.remove();
577 if (state() && state()->isStateActive()) {
578 QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::binding(d->property(name));
580 QDeclarativePropertyPrivate::setBinding(d->property(name), 0);
581 oldBinding->destroy();
583 d->property(name).write(value);
586 d->properties.append(PropertyEntry(name, value));
591 QMutableListIterator<PropertyEntry> propertyIterator(d->properties);
592 while (propertyIterator.hasNext()) {
593 PropertyEntry &entry = propertyIterator.next();
594 if (entry.first == name) {
595 entry.second = value;
596 if (state() && state()->isStateActive())
597 d->property(name).write(value);
602 QDeclarativeAction action;
603 action.restore = restoreEntryValues();
604 action.property = d->property(name);
605 action.fromValue = action.property.read();
606 action.specifiedObject = object();
607 action.specifiedProperty = name;
608 action.toValue = value;
610 propertyIterator.insert(PropertyEntry(name, value));
611 if (state() && state()->isStateActive()) {
612 state()->addEntryToRevertList(action);
613 QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::binding(action.property);
615 oldBinding->setEnabled(false, QDeclarativePropertyPrivate::DontRemoveBinding | QDeclarativePropertyPrivate::BypassInterceptor);
616 d->property(name).write(value);
620 void QDeclarativePropertyChanges::changeExpression(const QString &name, const QString &expression)
622 Q_D(QDeclarativePropertyChanges);
623 typedef QPair<QString, QVariant> PropertyEntry;
624 typedef QDeclarativePropertyChangesPrivate::ExpressionChange ExpressionEntry;
626 bool hadValue = false;
628 QMutableListIterator<PropertyEntry> propertyIterator(d->properties);
629 while (propertyIterator.hasNext()) {
630 PropertyEntry &entry = propertyIterator.next();
631 if (entry.first == name) {
632 propertyIterator.remove();
638 QMutableListIterator<ExpressionEntry> expressionIterator(d->expressions);
639 while (expressionIterator.hasNext()) {
640 const ExpressionEntry &entry = expressionIterator.next();
641 if (entry.name == name) {
642 entry.expression->setExpression(expression);
643 if (state() && state()->isStateActive()) {
644 QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::binding(d->property(name));
646 QDeclarativePropertyPrivate::setBinding(d->property(name), 0);
647 oldBinding->destroy();
650 QDeclarativeBinding *newBinding = new QDeclarativeBinding(expression, object(), qmlContext(this));
651 newBinding->setTarget(d->property(name));
652 QDeclarativePropertyPrivate::setBinding(d->property(name), newBinding, QDeclarativePropertyPrivate::DontRemoveBinding | QDeclarativePropertyPrivate::BypassInterceptor);
658 QDeclarativeExpression *newExpression = new QDeclarativeExpression(qmlContext(this), d->object, expression);
659 expressionIterator.insert(ExpressionEntry(name, QDeclarativeBinding::Invalid, newExpression));
661 if (state() && state()->isStateActive()) {
663 QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::binding(d->property(name));
665 oldBinding->setEnabled(false, QDeclarativePropertyPrivate::DontRemoveBinding | QDeclarativePropertyPrivate::BypassInterceptor);
666 state()->changeBindingInRevertList(object(), name, oldBinding);
669 QDeclarativeBinding *newBinding = new QDeclarativeBinding(expression, object(), qmlContext(this));
670 newBinding->setTarget(d->property(name));
671 QDeclarativePropertyPrivate::setBinding(d->property(name), newBinding, QDeclarativePropertyPrivate::DontRemoveBinding | QDeclarativePropertyPrivate::BypassInterceptor);
673 QDeclarativeAction action;
674 action.restore = restoreEntryValues();
675 action.property = d->property(name);
676 action.fromValue = action.property.read();
677 action.specifiedObject = object();
678 action.specifiedProperty = name;
682 action.toValue = newExpression->evaluate();
684 QDeclarativeBinding *newBinding = new QDeclarativeBinding(newExpression->expression(), object(), qmlContext(this));
685 newBinding->setTarget(d->property(name));
686 action.toBinding = newBinding;
687 action.deletableToBinding = true;
689 state()->addEntryToRevertList(action);
690 QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::binding(action.property);
692 oldBinding->setEnabled(false, QDeclarativePropertyPrivate::DontRemoveBinding | QDeclarativePropertyPrivate::BypassInterceptor);
694 QDeclarativePropertyPrivate::setBinding(action.property, newBinding, QDeclarativePropertyPrivate::DontRemoveBinding | QDeclarativePropertyPrivate::BypassInterceptor);
698 // what about the signal handler?
701 QVariant QDeclarativePropertyChanges::property(const QString &name) const
703 Q_D(const QDeclarativePropertyChanges);
704 typedef QPair<QString, QVariant> PropertyEntry;
705 typedef QDeclarativePropertyChangesPrivate::ExpressionChange ExpressionEntry;
707 QListIterator<PropertyEntry> propertyIterator(d->properties);
708 while (propertyIterator.hasNext()) {
709 const PropertyEntry &entry = propertyIterator.next();
710 if (entry.first == name) {
715 QListIterator<ExpressionEntry> expressionIterator(d->expressions);
716 while (expressionIterator.hasNext()) {
717 const ExpressionEntry &entry = expressionIterator.next();
718 if (entry.name == name) {
719 return QVariant(entry.expression->expression());
726 void QDeclarativePropertyChanges::removeProperty(const QString &name)
728 Q_D(QDeclarativePropertyChanges);
729 typedef QPair<QString, QVariant> PropertyEntry;
730 typedef QDeclarativePropertyChangesPrivate::ExpressionChange ExpressionEntry;
732 QMutableListIterator<ExpressionEntry> expressionIterator(d->expressions);
733 while (expressionIterator.hasNext()) {
734 const ExpressionEntry &entry = expressionIterator.next();
735 if (entry.name == name) {
736 expressionIterator.remove();
737 state()->removeEntryFromRevertList(object(), name);
742 QMutableListIterator<PropertyEntry> propertyIterator(d->properties);
743 while (propertyIterator.hasNext()) {
744 const PropertyEntry &entry = propertyIterator.next();
745 if (entry.first == name) {
746 propertyIterator.remove();
747 state()->removeEntryFromRevertList(object(), name);
753 QVariant QDeclarativePropertyChanges::value(const QString &name) const
755 Q_D(const QDeclarativePropertyChanges);
756 typedef QPair<QString, QVariant> PropertyEntry;
758 QListIterator<PropertyEntry> propertyIterator(d->properties);
759 while (propertyIterator.hasNext()) {
760 const PropertyEntry &entry = propertyIterator.next();
761 if (entry.first == name) {
769 QString QDeclarativePropertyChanges::expression(const QString &name) const
771 Q_D(const QDeclarativePropertyChanges);
772 typedef QDeclarativePropertyChangesPrivate::ExpressionChange ExpressionEntry;
774 QListIterator<ExpressionEntry> expressionIterator(d->expressions);
775 while (expressionIterator.hasNext()) {
776 const ExpressionEntry &entry = expressionIterator.next();
777 if (entry.name == name) {
778 return entry.expression->expression();
785 void QDeclarativePropertyChanges::detachFromState()
788 state()->removeAllEntriesFromRevertList(object());
791 void QDeclarativePropertyChanges::attachToState()
794 state()->addEntriesToRevertList(actions());