diff options
author | Chris Adams <christopher.adams@nokia.com> | 2012-02-06 14:24:42 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-03-05 09:01:47 +0100 |
commit | b6e78b38367a23f0b053bbd2abe4ef161e4053b9 (patch) | |
tree | 022bb82553af5b96a31f6e1ba56944d0e7060437 /src/qml/qml/v8 | |
parent | 0284817d6cd7e17afa8da26ee6e9199100754446 (diff) |
Improve support for var properties
This commit changes the semantics of function assignment in QML.
Previously, function assignment was interpreted as binding assignment.
Now, function assignment is interpreted as function assignment, and
therefore fails for all property types other than "var" properties.
To support imperative binding assignment, a new function was added to
the Qt object: Qt.binding(function) which takes a single function
parameter and returns a function object which will be interpreted as
an assignable binding expression by the QML engine.
Finally, this commit also slightly changes the semantics of var
properties in that the "special" JavaScript values of null and
undefined may be assigned to var properties, rather than being
interpreted as reset requests.
Task-number: QTBUG-21842
Change-Id: Iee99a878b9badf0fb76e983da7ebfa493f55ceb5
Reviewed-by: Matthew Vogt <matthew.vogt@nokia.com>
Diffstat (limited to 'src/qml/qml/v8')
-rw-r--r-- | src/qml/qml/v8/qqmlbuiltinfunctions.cpp | 59 | ||||
-rw-r--r-- | src/qml/qml/v8/qqmlbuiltinfunctions_p.h | 1 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8engine.cpp | 5 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8engine_p.h | 10 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8qobjectwrapper.cpp | 52 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8valuetypewrapper.cpp | 7 |
6 files changed, 116 insertions, 18 deletions
diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp index 7d7aaa2148..8101408ced 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp @@ -1316,6 +1316,65 @@ v8::Handle<v8::Value> locale(const v8::Arguments &args) return QQmlLocale::locale(v8engine, code); } +/*! + \qmlmethod Qt::binding(function) + + Returns a JS object representing a binding expression which may be + assigned to any property in imperative code to cause a binding + assignment. + + There are two main use-cases for the function: firstly, in imperative + JavaScript code to cause a binding assignment: + + \snippet doc/src/snippets/declarative/qtBinding.1.qml 0 + + and secondly, when defining initial property values of dynamically + constructed objects (via Component.createObject() or + Loader.setSource()) as being bound to the result of an expression. + + For example, assuming the existence of a DynamicText component: + \snippet doc/src/snippets/declarative/DynamicText.qml 0 + + the output from: + \snippet doc/src/snippets/declarative/qtBinding.2.qml 0 + + and from: + \snippet doc/src/snippets/declarative/qtBinding.3.qml 0 + + should both be: + \code + Root text extra text + Modified root text extra text + Dynamic text extra text + Modified dynamic text extra text + \endcode + + This function cannot be used in property binding declarations + (see the documentation on \l{qml-javascript-assignment}{binding + declarations and binding assignments}) except when the result is + stored in an array bound to a var property. + + \snippet doc/src/snippets/declarative/qtBinding.4.qml 0 + + Note: in QtQuick 1.x, all function assignment was treated as + binding assignment, so the Qt.binding() function is new in + QtQuick 2.0. + + \since QtQuick 2.0 +*/ +v8::Handle<v8::Value> binding(const v8::Arguments &args) +{ + QString code; + if (args.Length() != 1) + V8THROW_ERROR("binding() requires 1 argument"); + if (!args[0]->IsFunction()) + V8THROW_TYPE("binding(): argument (binding expression) must be a function"); + + v8::Handle<v8::Object> rv = args[0]->ToObject()->Clone(); + rv->SetHiddenValue(V8ENGINE()->bindingFlagKey(), v8::Boolean::New(true)); + return rv; +} + } // namespace QQmlBuiltinFunctions QT_END_NAMESPACE diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions_p.h b/src/qml/qml/v8/qqmlbuiltinfunctions_p.h index ddb1c64243..bbfe88a292 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions_p.h +++ b/src/qml/qml/v8/qqmlbuiltinfunctions_p.h @@ -103,6 +103,7 @@ v8::Handle<v8::Value> qsTrId(const v8::Arguments &args); v8::Handle<v8::Value> qsTrIdNoOp(const v8::Arguments &args); v8::Handle<v8::Value> stringArg(const v8::Arguments &args); v8::Handle<v8::Value> locale(const v8::Arguments &args); +v8::Handle<v8::Value> binding(const v8::Arguments &args); } QT_END_NAMESPACE diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp index 8e8223fea1..e49bc62917 100644 --- a/src/qml/qml/v8/qv8engine.cpp +++ b/src/qml/qml/v8/qv8engine.cpp @@ -147,6 +147,8 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership) QV8GCCallback::registerGcPrologueCallback(); m_strongReferencer = qPersistentNew(v8::Object::New()); + m_bindingFlagKey = qPersistentNew(v8::String::New("qml::binding")); + m_stringWrapper.init(); m_contextWrapper.init(this); m_qobjectWrapper.init(this); @@ -191,6 +193,8 @@ QV8Engine::~QV8Engine() m_contextWrapper.destroy(); m_stringWrapper.destroy(); + qPersistentDispose(m_bindingFlagKey); + m_originalGlobalObject.destroy(); if (m_ownsV8Context) @@ -598,6 +602,7 @@ void QV8Engine::initializeGlobal(v8::Handle<v8::Object> global) qt->Set(v8::String::New("atob"), V8FUNCTION(atob, this)); qt->Set(v8::String::New("resolvedUrl"), V8FUNCTION(resolvedUrl, this)); qt->Set(v8::String::New("locale"), V8FUNCTION(locale, this)); + qt->Set(v8::String::New("binding"), V8FUNCTION(binding, this)); if (m_engine) { qt->Set(v8::String::New("application"), newQObject(new QQuickApplication(m_engine))); diff --git a/src/qml/qml/v8/qv8engine_p.h b/src/qml/qml/v8/qv8engine_p.h index bc57b27085..825d7a3583 100644 --- a/src/qml/qml/v8/qv8engine_p.h +++ b/src/qml/qml/v8/qv8engine_p.h @@ -357,6 +357,9 @@ public: // a QVariant wrapper inline v8::Handle<v8::Value> newQVariant(const QVariant &); + // Return the JS string key for the "function is a binding" flag + inline v8::Handle<v8::String> bindingFlagKey() const; + // Return the network access manager for this engine. By default this returns the network // access manager of the QQmlEngine. It is overridden in the case of a threaded v8 // instance (like in WorkerScript). @@ -461,6 +464,8 @@ protected: v8::Persistent<v8::Context> m_context; QScriptOriginalGlobalObject m_originalGlobalObject; + v8::Persistent<v8::String> m_bindingFlagKey; + QV8StringWrapper m_stringWrapper; QV8ContextWrapper m_contextWrapper; QV8QObjectWrapper m_qobjectWrapper; @@ -609,6 +614,11 @@ v8::Handle<v8::Value> QV8Engine::newSequence(int sequenceType, QObject *object, return m_sequenceWrapper.newSequence(sequenceType, object, property, succeeded); } +v8::Handle<v8::String> QV8Engine::bindingFlagKey() const +{ + return m_bindingFlagKey; +} + // XXX Can this be made more optimal? It is called prior to resolving each and every // unqualified name in QV8ContextWrapper. bool QV8Engine::startsWithUpper(v8::Handle<v8::String> string) diff --git a/src/qml/qml/v8/qv8qobjectwrapper.cpp b/src/qml/qml/v8/qv8qobjectwrapper.cpp index 78b2cb719c..59b58ca503 100644 --- a/src/qml/qml/v8/qv8qobjectwrapper.cpp +++ b/src/qml/qml/v8/qv8qobjectwrapper.cpp @@ -519,7 +519,7 @@ v8::Handle<v8::Value> QV8QObjectWrapper::GetProperty(QV8Engine *engine, QObject return v8::Handle<v8::Value>(); } - if (result->isFunction()) { + if (result->isFunction() && !result->isVMEProperty()) { if (result->isVMEFunction()) { return ((QQmlVMEMetaObject *)(object->metaObject()))->vmeMethod(result->coreIndex); } else if (result->isV8Function()) { @@ -579,24 +579,34 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropert v8::Handle<v8::Value> value) { QQmlBinding *newBinding = 0; - if (value->IsFunction()) { - QQmlContextData *context = engine->callingContext(); - v8::Handle<v8::Function> function = v8::Handle<v8::Function>::Cast(value); - - v8::Local<v8::StackTrace> trace = - v8::StackTrace::CurrentStackTrace(1, (v8::StackTrace::StackTraceOptions)(v8::StackTrace::kLineNumber | - v8::StackTrace::kScriptName)); - v8::Local<v8::StackFrame> frame = trace->GetFrame(0); - int lineNumber = frame->GetLineNumber(); - int columNumber = frame->GetColumn(); - QString url = engine->toString(frame->GetScriptName()); - - newBinding = new QQmlBinding(&function, object, context); - newBinding->setSourceLocation(url, lineNumber, columNumber); - newBinding->setTarget(object, *property, context); - newBinding->setEvaluateFlags(newBinding->evaluateFlags() | - QQmlBinding::RequiresThisObject); + if (value->ToObject()->GetHiddenValue(engine->bindingFlagKey()).IsEmpty()) { + if (!property->isVMEProperty()) { + // assigning a JS function to a non-var-property is not allowed. + QString error = QLatin1String("Cannot assign JavaScript function to ") + + QLatin1String(QMetaType::typeName(property->propType)); + v8::ThrowException(v8::Exception::Error(engine->toString(error))); + return; + } + } else { + // binding assignment. + QQmlContextData *context = engine->callingContext(); + v8::Handle<v8::Function> function = v8::Handle<v8::Function>::Cast(value); + + v8::Local<v8::StackTrace> trace = + v8::StackTrace::CurrentStackTrace(1, (v8::StackTrace::StackTraceOptions)(v8::StackTrace::kLineNumber | + v8::StackTrace::kScriptName)); + v8::Local<v8::StackFrame> frame = trace->GetFrame(0); + int lineNumber = frame->GetLineNumber(); + int columNumber = frame->GetColumn(); + QString url = engine->toString(frame->GetScriptName()); + + newBinding = new QQmlBinding(&function, object, context); + newBinding->setSourceLocation(url, lineNumber, columNumber); + newBinding->setTarget(object, *property, context); + newBinding->setEvaluateFlags(newBinding->evaluateFlags() | + QQmlBinding::RequiresThisObject); + } } QQmlAbstractBinding *oldBinding = @@ -604,6 +614,12 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropert if (oldBinding) oldBinding->destroy(); + if (!newBinding && property->isVMEProperty()) { + // allow assignment of "special" values (null, undefined, function) to var properties + static_cast<QQmlVMEMetaObject *>(const_cast<QMetaObject *>(object->metaObject()))->setVMEProperty(property->coreIndex, value); + return; + } + #define PROPERTY_STORE(cpptype, value) \ cpptype o = value; \ int status = -1; \ diff --git a/src/qml/qml/v8/qv8valuetypewrapper.cpp b/src/qml/qml/v8/qv8valuetypewrapper.cpp index 54d871d5f0..d54f04a9b0 100644 --- a/src/qml/qml/v8/qv8valuetypewrapper.cpp +++ b/src/qml/qml/v8/qv8valuetypewrapper.cpp @@ -324,6 +324,13 @@ v8::Handle<v8::Value> QV8ValueTypeWrapper::Setter(v8::Local<v8::String> property QQmlBinding *newBinding = 0; if (value->IsFunction()) { + if (value->ToObject()->GetHiddenValue(r->engine->bindingFlagKey()).IsEmpty()) { + // assigning a JS function to a non-var-property is not allowed. + QString error = QLatin1String("Cannot assign JavaScript function to value-type property"); + v8::ThrowException(v8::Exception::Error(r->engine->toString(error))); + return value; + } + QQmlContextData *context = r->engine->callingContext(); v8::Handle<v8::Function> function = v8::Handle<v8::Function>::Cast(value); |