src/declarative/util/qdeclarativepropertychanges.cpp
changeset 30 5dc02b23752f
child 33 3e2da88830cd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/declarative/util/qdeclarativepropertychanges.cpp	Tue Jul 06 15:10:48 2010 +0300
@@ -0,0 +1,513 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qdeclarativepropertychanges_p.h"
+
+#include "private/qdeclarativeopenmetaobject_p.h"
+
+#include <qdeclarativeinfo.h>
+#include <qdeclarativecustomparser_p.h>
+#include <qdeclarativeparser_p.h>
+#include <qdeclarativeexpression.h>
+#include <qdeclarativebinding_p.h>
+#include <qdeclarativecontext.h>
+#include <qdeclarativeguard_p.h>
+#include <qdeclarativeproperty_p.h>
+#include <qdeclarativecontext_p.h>
+
+#include <QtCore/qdebug.h>
+
+#include <private/qobject_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \qmlclass PropertyChanges QDeclarativePropertyChanges
+    \since 4.7
+    \brief The PropertyChanges element describes new property values for a state.
+
+    PropertyChanges provides a state change that modifies the properties of an item.
+
+    Here is a property change that modifies the text and color of a Text element
+    when it is clicked:
+    
+    \qml
+    Text {
+        id: myText
+        width: 100; height: 100
+        text: "Hello"
+        color: "blue"
+
+        states: State {
+            name: "myState"
+
+            PropertyChanges {
+                target: myText
+                text: "Goodbye"
+                color: "red"
+            }
+        }
+
+        MouseArea { anchors.fill: parent; onClicked: myText.state = 'myState' }
+    }
+    \endqml
+    
+    State-specific script for signal handlers can also be specified:
+
+    \qml
+    PropertyChanges {
+        target: myMouseArea
+        onClicked: doSomethingDifferent()
+    }
+    \endqml
+
+    You can reset a property in a state change by assigning \c undefined. In the following
+    example we reset \c theText's width when we enter state1. This will give the text its
+    natural width (which is the whole string on one line).
+
+    \qml
+    import Qt 4.7
+
+    Rectangle {
+        width: 640
+        height: 480
+        Text {
+            id: theText
+            width: 50
+            wrapMode: Text.WordWrap
+            text: "a text string that is longer than 50 pixels"
+        }
+
+        states: State {
+            name: "state1"
+            PropertyChanges {
+                target: theText
+                width: undefined
+            }
+        }
+    }
+    \endqml
+
+    Anchor margins should be changed with PropertyChanges, but other anchor changes or changes to
+    an Item's parent should be done using the associated change elements
+    (ParentChange and AnchorChanges, respectively).
+
+    \sa {qmlstate}{States}, QtDeclarative
+*/
+
+/*!
+    \internal
+    \class QDeclarativePropertyChanges
+    \brief The QDeclarativePropertyChanges class describes new property values for a state.
+*/
+
+/*!
+    \qmlproperty Object PropertyChanges::target
+    This property holds the object which contains the properties to be changed.
+*/
+
+class QDeclarativeReplaceSignalHandler : public QDeclarativeActionEvent
+{
+public:
+    QDeclarativeReplaceSignalHandler() : expression(0), reverseExpression(0),
+                                rewindExpression(0), ownedExpression(0) {}
+    ~QDeclarativeReplaceSignalHandler() {
+        delete ownedExpression;
+    }
+
+    virtual QString typeName() const { return QLatin1String("ReplaceSignalHandler"); }
+
+    QDeclarativeProperty property;
+    QDeclarativeExpression *expression;
+    QDeclarativeExpression *reverseExpression;
+    QDeclarativeExpression *rewindExpression;
+    QDeclarativeGuard<QDeclarativeExpression> ownedExpression;
+
+    virtual void execute(Reason) {
+        ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, expression);
+        if (ownedExpression == expression)
+            ownedExpression = 0;
+    }
+
+    virtual bool isReversable() { return true; }
+    virtual void reverse(Reason) {
+        ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, reverseExpression);
+        if (ownedExpression == reverseExpression)
+            ownedExpression = 0;
+    }
+
+    virtual void saveOriginals() {
+        saveCurrentValues();
+        reverseExpression = rewindExpression;
+    }
+
+    /*virtual void copyOriginals(QDeclarativeActionEvent *other)
+    {
+        QDeclarativeReplaceSignalHandler *rsh = static_cast<QDeclarativeReplaceSignalHandler*>(other);
+        saveCurrentValues();
+        if (rsh == this)
+            return;
+        reverseExpression = rsh->reverseExpression;
+        if (rsh->ownedExpression == reverseExpression) {
+            ownedExpression = rsh->ownedExpression;
+            rsh->ownedExpression = 0;
+        }
+    }*/
+
+    virtual void rewind() {
+        ownedExpression = QDeclarativePropertyPrivate::setSignalExpression(property, rewindExpression);
+        if (ownedExpression == rewindExpression)
+            ownedExpression = 0;
+    }
+    virtual void saveCurrentValues() { 
+        rewindExpression = QDeclarativePropertyPrivate::signalExpression(property);
+    }
+
+    virtual bool override(QDeclarativeActionEvent*other) {
+        if (other == this)
+            return true;
+        if (other->typeName() != typeName())
+            return false;
+        if (static_cast<QDeclarativeReplaceSignalHandler*>(other)->property == property)
+            return true;
+        return false;
+    }
+};
+
+
+class QDeclarativePropertyChangesPrivate : public QObjectPrivate
+{
+    Q_DECLARE_PUBLIC(QDeclarativePropertyChanges)
+public:
+    QDeclarativePropertyChangesPrivate() : object(0), decoded(true), restore(true),
+                                isExplicit(false) {}
+
+    QObject *object;
+    QByteArray data;
+
+    bool decoded : 1;
+    bool restore : 1;
+    bool isExplicit : 1;
+
+    void decode();
+
+    QList<QPair<QByteArray, QVariant> > properties;
+    QList<QPair<QByteArray, QDeclarativeExpression *> > expressions;
+    QList<QDeclarativeReplaceSignalHandler*> signalReplacements;
+
+    QDeclarativeProperty property(const QByteArray &);
+};
+
+void
+QDeclarativePropertyChangesParser::compileList(QList<QPair<QByteArray, QVariant> > &list,
+                                     const QByteArray &pre,
+                                     const QDeclarativeCustomParserProperty &prop)
+{
+    QByteArray propName = pre + prop.name();
+
+    QList<QVariant> values = prop.assignedValues();
+    for (int ii = 0; ii < values.count(); ++ii) {
+        const QVariant &value = values.at(ii);
+
+        if (value.userType() == qMetaTypeId<QDeclarativeCustomParserNode>()) {
+            error(qvariant_cast<QDeclarativeCustomParserNode>(value),
+                  QDeclarativePropertyChanges::tr("PropertyChanges does not support creating state-specific objects."));
+            continue;
+        } else if(value.userType() == qMetaTypeId<QDeclarativeCustomParserProperty>()) {
+
+            QDeclarativeCustomParserProperty prop =
+                qvariant_cast<QDeclarativeCustomParserProperty>(value);
+            QByteArray pre = propName + '.';
+            compileList(list, pre, prop);
+
+        } else {
+            list << qMakePair(propName, value);
+        }
+    }
+}
+
+QByteArray
+QDeclarativePropertyChangesParser::compile(const QList<QDeclarativeCustomParserProperty> &props)
+{
+    QList<QPair<QByteArray, QVariant> > data;
+    for(int ii = 0; ii < props.count(); ++ii)
+        compileList(data, QByteArray(), props.at(ii));
+
+    QByteArray rv;
+    QDataStream ds(&rv, QIODevice::WriteOnly);
+
+    ds << data.count();
+    for(int ii = 0; ii < data.count(); ++ii) {
+        QDeclarativeParser::Variant v = qvariant_cast<QDeclarativeParser::Variant>(data.at(ii).second);
+        QVariant var;
+        bool isScript = v.isScript();
+        switch(v.type()) {
+        case QDeclarativeParser::Variant::Boolean:
+            var = QVariant(v.asBoolean());
+            break;
+        case QDeclarativeParser::Variant::Number:
+            var = QVariant(v.asNumber());
+            break;
+        case QDeclarativeParser::Variant::String:
+            var = QVariant(v.asString());
+            break;
+        case QDeclarativeParser::Variant::Invalid:
+        case QDeclarativeParser::Variant::Script:
+            var = QVariant(v.asScript());
+            break;
+        }
+
+        ds << data.at(ii).first << isScript << var;
+    }
+
+    return rv;
+}
+
+void QDeclarativePropertyChangesPrivate::decode()
+{
+    Q_Q(QDeclarativePropertyChanges);
+    if (decoded)
+        return;
+
+    QDataStream ds(&data, QIODevice::ReadOnly);
+
+    int count;
+    ds >> count;
+    for (int ii = 0; ii < count; ++ii) {
+        QByteArray name;
+        bool isScript;
+        QVariant data;
+        ds >> name;
+        ds >> isScript;
+        ds >> data;
+
+        QDeclarativeProperty prop = property(name);      //### better way to check for signal property?
+        if (prop.type() & QDeclarativeProperty::SignalProperty) {
+            QDeclarativeExpression *expression = new QDeclarativeExpression(qmlContext(q), object, data.toString());
+            QDeclarativeData *ddata = QDeclarativeData::get(q);
+            if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty())
+                expression->setSourceLocation(ddata->outerContext->url.toString(), ddata->lineNumber);
+            QDeclarativeReplaceSignalHandler *handler = new QDeclarativeReplaceSignalHandler;
+            handler->property = prop;
+            handler->expression = expression;
+            signalReplacements << handler;
+        } else if (isScript) {
+            QDeclarativeExpression *expression = new QDeclarativeExpression(qmlContext(q), object, data.toString());
+            QDeclarativeData *ddata = QDeclarativeData::get(q);
+            if (ddata && ddata->outerContext && !ddata->outerContext->url.isEmpty())
+                expression->setSourceLocation(ddata->outerContext->url.toString(), ddata->lineNumber);
+            expressions << qMakePair(name, expression);
+        } else {
+            properties << qMakePair(name, data);
+        }
+    }
+
+    decoded = true;
+    data.clear();
+}
+
+void QDeclarativePropertyChangesParser::setCustomData(QObject *object,
+                                            const QByteArray &data)
+{
+    QDeclarativePropertyChangesPrivate *p =
+        static_cast<QDeclarativePropertyChangesPrivate *>(QObjectPrivate::get(object));
+    p->data = data;
+    p->decoded = false;
+}
+
+QDeclarativePropertyChanges::QDeclarativePropertyChanges()
+: QDeclarativeStateOperation(*(new QDeclarativePropertyChangesPrivate))
+{
+}
+
+QDeclarativePropertyChanges::~QDeclarativePropertyChanges()
+{
+    Q_D(QDeclarativePropertyChanges);
+    for(int ii = 0; ii < d->expressions.count(); ++ii)
+        delete d->expressions.at(ii).second;
+    for(int ii = 0; ii < d->signalReplacements.count(); ++ii)
+        delete d->signalReplacements.at(ii);
+}
+
+QObject *QDeclarativePropertyChanges::object() const
+{
+    Q_D(const QDeclarativePropertyChanges);
+    return d->object;
+}
+
+void QDeclarativePropertyChanges::setObject(QObject *o)
+{
+    Q_D(QDeclarativePropertyChanges);
+    d->object = o;
+}
+
+/*!
+    \qmlproperty bool PropertyChanges::restoreEntryValues
+    
+    Whether or not the previous values should be restored when
+    leaving the state. By default, restoreEntryValues is true.
+
+    By setting restoreEntryValues to false, you can create a temporary state
+    that has permanent effects on property values.
+*/
+bool QDeclarativePropertyChanges::restoreEntryValues() const
+{
+    Q_D(const QDeclarativePropertyChanges);
+    return d->restore;
+}
+
+void QDeclarativePropertyChanges::setRestoreEntryValues(bool v)
+{
+    Q_D(QDeclarativePropertyChanges);
+    d->restore = v;
+}
+
+QDeclarativeProperty
+QDeclarativePropertyChangesPrivate::property(const QByteArray &property)
+{
+    Q_Q(QDeclarativePropertyChanges);
+    QDeclarativeProperty prop(object, QString::fromUtf8(property), qmlContext(q));
+    if (!prop.isValid()) {
+        qmlInfo(q) << QDeclarativePropertyChanges::tr("Cannot assign to non-existent property \"%1\"").arg(QString::fromUtf8(property));
+        return QDeclarativeProperty();
+    } else if (!(prop.type() & QDeclarativeProperty::SignalProperty) && !prop.isWritable()) {
+        qmlInfo(q) << QDeclarativePropertyChanges::tr("Cannot assign to read-only property \"%1\"").arg(QString::fromUtf8(property));
+        return QDeclarativeProperty();
+    }
+    return prop;
+}
+
+QDeclarativePropertyChanges::ActionList QDeclarativePropertyChanges::actions()
+{
+    Q_D(QDeclarativePropertyChanges);
+
+    d->decode();
+
+    ActionList list;
+
+    for (int ii = 0; ii < d->properties.count(); ++ii) {
+
+        QByteArray property = d->properties.at(ii).first;
+
+        QDeclarativeAction a(d->object, QString::fromUtf8(property),
+                 qmlContext(this), d->properties.at(ii).second);
+
+        if (a.property.isValid()) {
+            a.restore = restoreEntryValues();
+            list << a;
+        }
+    }
+
+    for (int ii = 0; ii < d->signalReplacements.count(); ++ii) {
+
+        QDeclarativeReplaceSignalHandler *handler = d->signalReplacements.at(ii);
+
+        if (handler->property.isValid()) {
+            QDeclarativeAction a;
+            a.event = handler;
+            list << a;
+        }
+    }
+
+    for (int ii = 0; ii < d->expressions.count(); ++ii) {
+
+        QByteArray property = d->expressions.at(ii).first;
+        QDeclarativeProperty prop = d->property(property);
+
+        if (prop.isValid()) {
+            QDeclarativeAction a;
+            a.restore = restoreEntryValues();
+            a.property = prop;
+            a.fromValue = a.property.read();
+            a.specifiedObject = d->object;
+            a.specifiedProperty = QString::fromUtf8(property);
+
+            if (d->isExplicit) {
+                a.toValue = d->expressions.at(ii).second->evaluate();
+            } else {
+                QDeclarativeExpression *e = d->expressions.at(ii).second;
+                QDeclarativeBinding *newBinding = 
+                    new QDeclarativeBinding(e->expression(), object(), qmlContext(this));
+                newBinding->setTarget(prop);
+                newBinding->setSourceLocation(e->sourceFile(), e->lineNumber());
+                a.toBinding = newBinding;
+                a.deletableToBinding = true;
+            }
+
+            list << a;
+        }
+    }
+
+    return list;
+}
+
+/*!
+    \qmlproperty bool PropertyChanges::explicit
+
+    If explicit is set to true, any potential bindings will be interpreted as
+    once-off assignments that occur when the state is entered.
+
+    In the following example, the addition of explicit prevents myItem.width from
+    being bound to parent.width. Instead, it is assigned the value of parent.width
+    at the time of the state change.
+    \qml
+    PropertyChanges {
+        target: myItem
+        explicit: true
+        width: parent.width
+    }
+    \endqml
+
+    By default, explicit is false.
+*/
+bool QDeclarativePropertyChanges::isExplicit() const
+{
+    Q_D(const QDeclarativePropertyChanges);
+    return d->isExplicit;
+}
+
+void QDeclarativePropertyChanges::setIsExplicit(bool e)
+{
+    Q_D(QDeclarativePropertyChanges);
+    d->isExplicit = e;
+}
+
+QT_END_NAMESPACE