diff options
Diffstat (limited to 'src/declarative/util/qmlpropertychanges.cpp')
-rw-r--r-- | src/declarative/util/qmlpropertychanges.cpp | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/declarative/util/qmlpropertychanges.cpp b/src/declarative/util/qmlpropertychanges.cpp new file mode 100644 index 0000000..68fc5cc --- /dev/null +++ b/src/declarative/util/qmlpropertychanges.cpp @@ -0,0 +1,468 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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 "qmlpropertychanges_p.h" + +#include "qmlopenmetaobject_p.h" + +#include <qmlinfo.h> +#include <qmlcustomparser_p.h> +#include <qmlparser_p.h> +#include <qmlexpression.h> +#include <qmlbinding.h> +#include <qmlcontext.h> +#include <qmlguard_p.h> + +#include <QtCore/qdebug.h> + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmlclass PropertyChanges QmlPropertyChanges + \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" + } + } + + MouseRegion { anchors.fill: parent; onClicked: myText.state = 'myState' } + } + \endqml + + State-specific script for signal handlers can also be specified: + + \qml + PropertyChanges { + target: myMouseRegion + onClicked: doSomethingDifferent() + } + \endqml + + Changes to an Item's parent or anchors should be done using the associated change elements + (ParentChange and AnchorChanges, respectively) rather than PropertyChanges. + + \sa {qmlstate}{States} +*/ + +/*! + \internal + \class QmlPropertyChanges + \brief The QmlPropertyChanges 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 QmlReplaceSignalHandler : public QmlActionEvent +{ +public: + QmlReplaceSignalHandler() : expression(0), reverseExpression(0), + rewindExpression(0), ownedExpression(0) {} + ~QmlReplaceSignalHandler() { + delete ownedExpression; + } + + virtual QString typeName() const { return QLatin1String("ReplaceSignalHandler"); } + + QmlMetaProperty property; + QmlExpression *expression; + QmlExpression *reverseExpression; + QmlExpression *rewindExpression; + QmlGuard<QmlExpression> ownedExpression; + + virtual void execute() { + ownedExpression = property.setSignalExpression(expression); + } + + virtual bool isReversable() { return true; } + virtual void reverse() { + ownedExpression = property.setSignalExpression(reverseExpression); + } + + virtual void saveOriginals() { + saveCurrentValues(); + reverseExpression = rewindExpression; + } + + virtual void rewind() { + ownedExpression = property.setSignalExpression(rewindExpression); + } + virtual void saveCurrentValues() { rewindExpression = property.signalExpression(); } + + virtual bool override(QmlActionEvent*other) { + if (other == this) + return true; + if (other->typeName() != typeName()) + return false; + if (static_cast<QmlReplaceSignalHandler*>(other)->property == property) + return true; + return false; + } +}; + + +class QmlPropertyChangesPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QmlPropertyChanges) +public: + QmlPropertyChangesPrivate() : 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, QmlExpression *> > expressions; + QList<QmlReplaceSignalHandler*> signalReplacements; + + QmlMetaProperty property(const QByteArray &); +}; + +class QmlPropertyChangesParser : public QmlCustomParser +{ +public: + void compileList(QList<QPair<QByteArray, QVariant> > &list, const QByteArray &pre, const QmlCustomParserProperty &prop); + + virtual QByteArray compile(const QList<QmlCustomParserProperty> &); + virtual void setCustomData(QObject *, const QByteArray &); +}; + +void +QmlPropertyChangesParser::compileList(QList<QPair<QByteArray, QVariant> > &list, + const QByteArray &pre, + const QmlCustomParserProperty &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<QmlCustomParserNode>()) { + continue; + } else if(value.userType() == qMetaTypeId<QmlCustomParserProperty>()) { + + QmlCustomParserProperty prop = + qvariant_cast<QmlCustomParserProperty>(value); + QByteArray pre = propName + '.'; + compileList(list, pre, prop); + + } else { + list << qMakePair(propName, value); + } + } +} + +QByteArray +QmlPropertyChangesParser::compile(const QList<QmlCustomParserProperty> &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) { + QmlParser::Variant v = qvariant_cast<QmlParser::Variant>(data.at(ii).second); + QVariant var; + bool isScript = v.isScript(); + switch(v.type()) { + case QmlParser::Variant::Boolean: + var = QVariant(v.asBoolean()); + break; + case QmlParser::Variant::Number: + var = QVariant(v.asNumber()); + break; + case QmlParser::Variant::String: + var = QVariant(v.asString()); + break; + case QmlParser::Variant::Invalid: + case QmlParser::Variant::Script: + var = QVariant(v.asScript()); + break; + } + + ds << data.at(ii).first << isScript << var; + } + + return rv; +} + +void QmlPropertyChangesPrivate::decode() +{ + Q_Q(QmlPropertyChanges); + 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; + + QmlMetaProperty prop = property(name); //### better way to check for signal property? + if (prop.type() & QmlMetaProperty::SignalProperty) { + QmlExpression *expression = new QmlExpression(qmlContext(q), data.toString(), object); + expression->setTrackChange(false); + QmlReplaceSignalHandler *handler = new QmlReplaceSignalHandler; + handler->property = prop; + handler->expression = expression; + signalReplacements << handler; + } else if (isScript) { + QmlExpression *expression = new QmlExpression(qmlContext(q), data.toString(), object); + expression->setTrackChange(false); + expressions << qMakePair(name, expression); + } else { + properties << qMakePair(name, data); + } + } + + decoded = true; + data.clear(); +} + +void QmlPropertyChangesParser::setCustomData(QObject *object, + const QByteArray &data) +{ + QmlPropertyChangesPrivate *p = + static_cast<QmlPropertyChangesPrivate *>(QObjectPrivate::get(object)); + p->data = data; + p->decoded = false; +} + +QmlPropertyChanges::QmlPropertyChanges() +: QmlStateOperation(*(new QmlPropertyChangesPrivate)) +{ +} + +QmlPropertyChanges::~QmlPropertyChanges() +{ + Q_D(QmlPropertyChanges); + 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 *QmlPropertyChanges::object() const +{ + Q_D(const QmlPropertyChanges); + return d->object; +} + +void QmlPropertyChanges::setObject(QObject *o) +{ + Q_D(QmlPropertyChanges); + 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 QmlPropertyChanges::restoreEntryValues() const +{ + Q_D(const QmlPropertyChanges); + return d->restore; +} + +void QmlPropertyChanges::setRestoreEntryValues(bool v) +{ + Q_D(QmlPropertyChanges); + d->restore = v; +} + +QmlMetaProperty +QmlPropertyChangesPrivate::property(const QByteArray &property) +{ + Q_Q(QmlPropertyChanges); + QmlMetaProperty prop = QmlMetaProperty::createProperty(object, QString::fromUtf8(property)); + if (!prop.isValid()) { + qmlInfo(q) << QmlPropertyChanges::tr("Cannot assign to non-existant property \"%1\"").arg(QString::fromUtf8(property)); + return QmlMetaProperty(); + } else if (!(prop.type() & QmlMetaProperty::SignalProperty) && !prop.isWritable()) { + qmlInfo(q) << QmlPropertyChanges::tr("Cannot assign to read-only property \"%1\"").arg(QString::fromUtf8(property)); + return QmlMetaProperty(); + } + return prop; +} + +QmlPropertyChanges::ActionList QmlPropertyChanges::actions() +{ + Q_D(QmlPropertyChanges); + + d->decode(); + + ActionList list; + + for (int ii = 0; ii < d->properties.count(); ++ii) { + + QByteArray property = d->properties.at(ii).first; + + QmlAction a(d->object, QString::fromLatin1(property), + d->properties.at(ii).second); + + if (a.property.isValid()) { + a.restore = restoreEntryValues(); + + if (a.property.propertyType() == QVariant::Url && + (a.toValue.userType() == QVariant::String || a.toValue.userType() == QVariant::ByteArray) && !a.toValue.isNull()) + a.toValue.setValue(qmlContext(this)->resolvedUrl(QUrl(a.toValue.toString()))); + + list << a; + } + } + + for (int ii = 0; ii < d->signalReplacements.count(); ++ii) { + + QmlReplaceSignalHandler *handler = d->signalReplacements.at(ii); + + if (handler->property.isValid()) { + QmlAction a; + a.event = handler; + list << a; + } + } + + for (int ii = 0; ii < d->expressions.count(); ++ii) { + + QByteArray property = d->expressions.at(ii).first; + QmlMetaProperty prop = d->property(property); + + if (prop.isValid()) { + QmlAction a; + a.restore = restoreEntryValues(); + a.property = prop; + a.fromValue = a.property.read(); + a.specifiedObject = d->object; + a.specifiedProperty = QString::fromLatin1(property); + + if (d->isExplicit) { + a.toValue = d->expressions.at(ii).second->value(); + } else { + QmlBinding *newBinding = new QmlBinding(d->expressions.at(ii).second->expression(), object(), qmlContext(this)); + newBinding->setTarget(prop); + 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 QmlPropertyChanges::isExplicit() const +{ + Q_D(const QmlPropertyChanges); + return d->isExplicit; +} + +void QmlPropertyChanges::setIsExplicit(bool e) +{ + Q_D(QmlPropertyChanges); + d->isExplicit = e; +} + +QML_DEFINE_CUSTOM_TYPE(Qt, 4,6, PropertyChanges, QmlPropertyChanges, QmlPropertyChangesParser) + +QT_END_NAMESPACE |