/**************************************************************************** | |
** | |
** Copyright (C) 2011 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$ | |
** GNU Lesser General Public License Usage | |
** 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. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "private/qdeclarativeobjectscriptclass_p.h" | |
#include "private/qdeclarativeengine_p.h" | |
#include "private/qdeclarativecontext_p.h" | |
#include "private/qdeclarativedata_p.h" | |
#include "private/qdeclarativetypenamescriptclass_p.h" | |
#include "private/qdeclarativelistscriptclass_p.h" | |
#include "private/qdeclarativebinding_p.h" | |
#include "private/qdeclarativeguard_p.h" | |
#include "private/qdeclarativevmemetaobject_p.h" | |
#include <QtCore/qtimer.h> | |
#include <QtCore/qvarlengtharray.h> | |
#include <QtScript/qscriptcontextinfo.h> | |
Q_DECLARE_METATYPE(QScriptValue); | |
QT_BEGIN_NAMESPACE | |
struct ObjectData : public QScriptDeclarativeClass::Object { | |
ObjectData(QObject *o, int t) : object(o), type(t) { | |
if (o) { | |
QDeclarativeData *ddata = QDeclarativeData::get(object, true); | |
if (ddata) ddata->objectDataRefCount++; | |
} | |
} | |
virtual ~ObjectData() { | |
if (object && !object->parent()) { | |
QDeclarativeData *ddata = QDeclarativeData::get(object, false); | |
if (ddata && !ddata->indestructible && 0 == --ddata->objectDataRefCount) | |
object->deleteLater(); | |
} | |
} | |
QDeclarativeGuard<QObject> object; | |
int type; | |
}; | |
/* | |
The QDeclarativeObjectScriptClass handles property access for QObjects | |
via QtScript. It is also used to provide a more useful API in | |
QtScript for QML. | |
*/ | |
QDeclarativeObjectScriptClass::QDeclarativeObjectScriptClass(QDeclarativeEngine *bindEngine) | |
: QScriptDeclarativeClass(QDeclarativeEnginePrivate::getScriptEngine(bindEngine)), | |
methods(bindEngine), lastData(0), engine(bindEngine) | |
{ | |
QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); | |
m_destroy = scriptEngine->newFunction(destroy); | |
m_destroyId = createPersistentIdentifier(QLatin1String("destroy")); | |
m_toString = scriptEngine->newFunction(tostring); | |
m_toStringId = createPersistentIdentifier(QLatin1String("toString")); | |
} | |
QDeclarativeObjectScriptClass::~QDeclarativeObjectScriptClass() | |
{ | |
} | |
QScriptValue QDeclarativeObjectScriptClass::newQObject(QObject *object, int type) | |
{ | |
QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); | |
if (!object) | |
return scriptEngine->nullValue(); | |
// return newObject(scriptEngine, this, new ObjectData(object, type)); | |
if (QObjectPrivate::get(object)->wasDeleted) | |
return scriptEngine->undefinedValue(); | |
QDeclarativeData *ddata = QDeclarativeData::get(object, true); | |
if (!ddata) { | |
return scriptEngine->undefinedValue(); | |
} else if (!ddata->indestructible && !object->parent()) { | |
return newObject(scriptEngine, this, new ObjectData(object, type)); | |
} else if (!ddata->scriptValue) { | |
ddata->scriptValue = new QScriptValue(newObject(scriptEngine, this, new ObjectData(object, type))); | |
return *ddata->scriptValue; | |
} else if (ddata->scriptValue->engine() == QDeclarativeEnginePrivate::getScriptEngine(engine)) { | |
return *ddata->scriptValue; | |
} else { | |
return newObject(scriptEngine, this, new ObjectData(object, type)); | |
} | |
} | |
QObject *QDeclarativeObjectScriptClass::toQObject(const QScriptValue &value) const | |
{ | |
return value.toQObject(); | |
} | |
int QDeclarativeObjectScriptClass::objectType(const QScriptValue &value) const | |
{ | |
if (scriptClass(value) != this) | |
return QVariant::Invalid; | |
Object *o = object(value); | |
return ((ObjectData*)(o))->type; | |
} | |
QScriptClass::QueryFlags | |
QDeclarativeObjectScriptClass::queryProperty(Object *object, const Identifier &name, | |
QScriptClass::QueryFlags flags) | |
{ | |
return queryProperty(toQObject(object), name, flags, 0); | |
} | |
QScriptClass::QueryFlags | |
QDeclarativeObjectScriptClass::queryProperty(QObject *obj, const Identifier &name, | |
QScriptClass::QueryFlags flags, QDeclarativeContextData *evalContext, | |
QueryHints hints) | |
{ | |
Q_UNUSED(flags); | |
lastData = 0; | |
lastTNData = 0; | |
if (name == m_destroyId.identifier || | |
name == m_toStringId.identifier) | |
return QScriptClass::HandlesReadAccess; | |
if (!obj) | |
return 0; | |
QDeclarativeEnginePrivate *enginePrivate = QDeclarativeEnginePrivate::get(engine); | |
lastData = QDeclarativePropertyCache::property(engine, obj, name, local); | |
if ((hints & ImplicitObject) && lastData && lastData->revision != 0) { | |
QDeclarativeData *ddata = QDeclarativeData::get(obj); | |
if (ddata && ddata->propertyCache && !ddata->propertyCache->isAllowedInRevision(lastData)) | |
return 0; | |
} | |
if (lastData) | |
return QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess; | |
if (!(hints & SkipAttachedProperties)) { | |
if (!evalContext && context()) { | |
// Global object, QScriptContext activation object, QDeclarativeContext object | |
QScriptValue scopeNode = scopeChainValue(context(), -3); | |
if (scopeNode.isValid()) { | |
Q_ASSERT(scriptClass(scopeNode) == enginePrivate->contextClass); | |
evalContext = enginePrivate->contextClass->contextFromValue(scopeNode); | |
} | |
} | |
if (evalContext && evalContext->imports) { | |
QDeclarativeTypeNameCache::Data *data = evalContext->imports->data(name); | |
if (data) { | |
lastTNData = data; | |
return QScriptClass::HandlesReadAccess; | |
} | |
} | |
} | |
if (!(hints & ImplicitObject)) { | |
local.coreIndex = -1; | |
lastData = &local; | |
return QScriptClass::HandlesWriteAccess; | |
} | |
return 0; | |
} | |
QDeclarativeObjectScriptClass::Value | |
QDeclarativeObjectScriptClass::property(Object *object, const Identifier &name) | |
{ | |
return property(toQObject(object), name); | |
} | |
QDeclarativeObjectScriptClass::Value | |
QDeclarativeObjectScriptClass::property(QObject *obj, const Identifier &name) | |
{ | |
QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); | |
if (name == m_destroyId.identifier) | |
return Value(scriptEngine, m_destroy); | |
else if (name == m_toStringId.identifier) | |
return Value(scriptEngine, m_toString); | |
if (lastData && !lastData->isValid()) | |
return Value(); | |
Q_ASSERT(obj); | |
QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(engine); | |
if (lastTNData) { | |
if (lastTNData->type) | |
return Value(scriptEngine, enginePriv->typeNameClass->newObject(obj, lastTNData->type)); | |
else | |
return Value(scriptEngine, enginePriv->typeNameClass->newObject(obj, lastTNData->typeNamespace)); | |
} else if (lastData->flags & QDeclarativePropertyCache::Data::IsFunction) { | |
if (lastData->flags & QDeclarativePropertyCache::Data::IsVMEFunction) { | |
return Value(scriptEngine, ((QDeclarativeVMEMetaObject *)(obj->metaObject()))->vmeMethod(lastData->coreIndex)); | |
} else { | |
// Uncomment to use QtScript method call logic | |
// QScriptValue sobj = scriptEngine->newQObject(obj); | |
// return Value(scriptEngine, sobj.property(toString(name))); | |
return Value(scriptEngine, methods.newMethod(obj, lastData)); | |
} | |
} else { | |
if (enginePriv->captureProperties && !(lastData->flags & QDeclarativePropertyCache::Data::IsConstant)) { | |
if (lastData->coreIndex == 0) { | |
enginePriv->capturedProperties << | |
QDeclarativeEnginePrivate::CapturedProperty(QDeclarativeData::get(obj, true)->objectNameNotifier()); | |
} else { | |
enginePriv->capturedProperties << | |
QDeclarativeEnginePrivate::CapturedProperty(obj, lastData->coreIndex, lastData->notifyIndex); | |
} | |
} | |
if (QDeclarativeValueTypeFactory::isValueType((uint)lastData->propType)) { | |
QDeclarativeValueType *valueType = enginePriv->valueTypes[lastData->propType]; | |
if (valueType) | |
return Value(scriptEngine, enginePriv->valueTypeClass->newObject(obj, lastData->coreIndex, valueType)); | |
} | |
if (lastData->flags & QDeclarativePropertyCache::Data::IsQList) { | |
return Value(scriptEngine, enginePriv->listClass->newList(obj, lastData->coreIndex, lastData->propType)); | |
} else if (lastData->flags & QDeclarativePropertyCache::Data::IsQObjectDerived) { | |
QObject *rv = 0; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, newQObject(rv, lastData->propType)); | |
} else if (lastData->flags & QDeclarativePropertyCache::Data::IsQScriptValue) { | |
QScriptValue rv = scriptEngine->nullValue(); | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::QReal) { | |
qreal rv = 0; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::Int || lastData->flags & QDeclarativePropertyCache::Data::IsEnumType) { | |
int rv = 0; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::Bool) { | |
bool rv = false; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::QString) { | |
QString rv; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::UInt) { | |
uint rv = 0; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::Float) { | |
float rv = 0; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else if (lastData->propType == QMetaType::Double) { | |
double rv = 0; | |
void *args[] = { &rv, 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ReadProperty, lastData->coreIndex, args); | |
return Value(scriptEngine, rv); | |
} else { | |
QVariant var = obj->metaObject()->property(lastData->coreIndex).read(obj); | |
return Value(scriptEngine, enginePriv->scriptValueFromVariant(var)); | |
} | |
} | |
} | |
void QDeclarativeObjectScriptClass::setProperty(Object *object, | |
const Identifier &name, | |
const QScriptValue &value) | |
{ | |
return setProperty(toQObject(object), name, value, context()); | |
} | |
void QDeclarativeObjectScriptClass::setProperty(QObject *obj, | |
const Identifier &name, | |
const QScriptValue &value, | |
QScriptContext *context, | |
QDeclarativeContextData *evalContext) | |
{ | |
Q_UNUSED(name); | |
Q_ASSERT(obj); | |
Q_ASSERT(lastData); | |
Q_ASSERT(context); | |
if (!lastData->isValid()) { | |
QString error = QLatin1String("Cannot assign to non-existent property \"") + | |
toString(name) + QLatin1Char('\"'); | |
context->throwError(error); | |
return; | |
} | |
if (!(lastData->flags & QDeclarativePropertyCache::Data::IsWritable) && | |
!(lastData->flags & QDeclarativePropertyCache::Data::IsQList)) { | |
QString error = QLatin1String("Cannot assign to read-only property \"") + | |
toString(name) + QLatin1Char('\"'); | |
context->throwError(error); | |
return; | |
} | |
QDeclarativeEnginePrivate *enginePriv = QDeclarativeEnginePrivate::get(engine); | |
if (!evalContext) { | |
// Global object, QScriptContext activation object, QDeclarativeContext object | |
QScriptValue scopeNode = scopeChainValue(context, -3); | |
if (scopeNode.isValid()) { | |
Q_ASSERT(scriptClass(scopeNode) == enginePriv->contextClass); | |
evalContext = enginePriv->contextClass->contextFromValue(scopeNode); | |
} | |
} | |
QDeclarativeBinding *newBinding = 0; | |
if (value.isFunction() && !value.isRegExp()) { | |
QScriptContextInfo ctxtInfo(context); | |
QDeclarativePropertyCache::ValueTypeData valueTypeData; | |
newBinding = new QDeclarativeBinding(value, obj, evalContext); | |
newBinding->setSourceLocation(ctxtInfo.fileName(), ctxtInfo.functionStartLineNumber()); | |
newBinding->setTarget(QDeclarativePropertyPrivate::restore(*lastData, valueTypeData, obj, evalContext)); | |
if (newBinding->expression().contains(QLatin1String("this"))) | |
newBinding->setEvaluateFlags(newBinding->evaluateFlags() | QDeclarativeBinding::RequiresThisObject); | |
} | |
QDeclarativeAbstractBinding *delBinding = | |
QDeclarativePropertyPrivate::setBinding(obj, lastData->coreIndex, -1, newBinding); | |
if (delBinding) | |
delBinding->destroy(); | |
if (value.isNull() && lastData->flags & QDeclarativePropertyCache::Data::IsQObjectDerived) { | |
QObject *o = 0; | |
int status = -1; | |
int flags = 0; | |
void *argv[] = { &o, 0, &status, &flags }; | |
QMetaObject::metacall(obj, QMetaObject::WriteProperty, lastData->coreIndex, argv); | |
} else if (value.isUndefined() && lastData->flags & QDeclarativePropertyCache::Data::IsResettable) { | |
void *a[] = { 0 }; | |
QMetaObject::metacall(obj, QMetaObject::ResetProperty, lastData->coreIndex, a); | |
} else if (value.isUndefined() && lastData->propType == qMetaTypeId<QVariant>()) { | |
QDeclarativePropertyPrivate::write(obj, *lastData, QVariant(), evalContext); | |
} else if (value.isUndefined()) { | |
QString error = QLatin1String("Cannot assign [undefined] to ") + | |
QLatin1String(QMetaType::typeName(lastData->propType)); | |
context->throwError(error); | |
} else if (value.isFunction() && !value.isRegExp()) { | |
// this is handled by the binding creation above | |
} else { | |
QVariant v; | |
if (lastData->flags & QDeclarativePropertyCache::Data::IsQList) | |
v = enginePriv->scriptValueToVariant(value, qMetaTypeId<QList<QObject *> >()); | |
else | |
v = enginePriv->scriptValueToVariant(value, lastData->propType); | |
if (!QDeclarativePropertyPrivate::write(obj, *lastData, v, evalContext)) { | |
const char *valueType = 0; | |
if (v.userType() == QVariant::Invalid) valueType = "null"; | |
else valueType = QMetaType::typeName(v.userType()); | |
QString error = QLatin1String("Cannot assign ") + | |
QLatin1String(valueType) + | |
QLatin1String(" to ") + | |
QLatin1String(QMetaType::typeName(lastData->propType)); | |
context->throwError(error); | |
} | |
} | |
} | |
bool QDeclarativeObjectScriptClass::isQObject() const | |
{ | |
return true; | |
} | |
QObject *QDeclarativeObjectScriptClass::toQObject(Object *object, bool *ok) | |
{ | |
if (ok) *ok = true; | |
ObjectData *data = (ObjectData*)object; | |
return data->object.data(); | |
} | |
QScriptValue QDeclarativeObjectScriptClass::tostring(QScriptContext *context, QScriptEngine *) | |
{ | |
QObject* obj = context->thisObject().toQObject(); | |
QString ret; | |
if(obj){ | |
QString objectName = obj->objectName(); | |
ret += QString::fromUtf8(obj->metaObject()->className()); | |
ret += QLatin1String("(0x"); | |
ret += QString::number((quintptr)obj,16); | |
if (!objectName.isEmpty()) { | |
ret += QLatin1String(", \""); | |
ret += objectName; | |
ret += QLatin1Char('\"'); | |
} | |
ret += QLatin1Char(')'); | |
}else{ | |
ret += QLatin1String("null"); | |
} | |
return QScriptValue(ret); | |
} | |
QScriptValue QDeclarativeObjectScriptClass::destroy(QScriptContext *context, QScriptEngine *engine) | |
{ | |
QDeclarativeEnginePrivate *p = QDeclarativeEnginePrivate::get(engine); | |
QScriptValue that = context->thisObject(); | |
if (scriptClass(that) != p->objectClass) | |
return engine->undefinedValue(); | |
ObjectData *data = (ObjectData *)p->objectClass->object(that); | |
if (!data->object) | |
return engine->undefinedValue(); | |
QDeclarativeData *ddata = QDeclarativeData::get(data->object, false); | |
if (!ddata || ddata->indestructible) | |
return engine->currentContext()->throwError(QLatin1String("Invalid attempt to destroy() an indestructible object")); | |
QObject *obj = data->object; | |
int delay = 0; | |
if (context->argumentCount() > 0) | |
delay = context->argument(0).toInt32(); | |
if (delay > 0) | |
QTimer::singleShot(delay, obj, SLOT(deleteLater())); | |
else | |
obj->deleteLater(); | |
return engine->undefinedValue(); | |
} | |
QStringList QDeclarativeObjectScriptClass::propertyNames(Object *object) | |
{ | |
QObject *obj = toQObject(object); | |
if (!obj) | |
return QStringList(); | |
QDeclarativeEnginePrivate *enginePrivate = QDeclarativeEnginePrivate::get(engine); | |
QDeclarativePropertyCache *cache = 0; | |
QDeclarativeData *ddata = QDeclarativeData::get(obj); | |
if (ddata) | |
cache = ddata->propertyCache; | |
if (!cache) { | |
cache = enginePrivate->cache(obj); | |
if (cache) { | |
if (ddata) { cache->addref(); ddata->propertyCache = cache; } | |
} else { | |
// Not cachable - fall back to QMetaObject (eg. dynamic meta object) | |
// XXX QDeclarativeOpenMetaObject has a cache, so this is suboptimal. | |
// XXX This is a workaround for QTBUG-9420. | |
const QMetaObject *mo = obj->metaObject(); | |
QStringList r; | |
int pc = mo->propertyCount(); | |
int po = mo->propertyOffset(); | |
for (int i=po; i<pc; ++i) | |
r += QString::fromUtf8(mo->property(i).name()); | |
return r; | |
} | |
} | |
return cache->propertyNames(); | |
} | |
bool QDeclarativeObjectScriptClass::compare(Object *o1, Object *o2) | |
{ | |
ObjectData *d1 = (ObjectData *)o1; | |
ObjectData *d2 = (ObjectData *)o2; | |
return d1 == d2 || d1->object == d2->object; | |
} | |
struct MethodData : public QScriptDeclarativeClass::Object { | |
MethodData(QObject *o, const QDeclarativePropertyCache::Data &d) : object(o), data(d) {} | |
QDeclarativeGuard<QObject> object; | |
QDeclarativePropertyCache::Data data; | |
}; | |
QDeclarativeObjectMethodScriptClass::QDeclarativeObjectMethodScriptClass(QDeclarativeEngine *bindEngine) | |
: QScriptDeclarativeClass(QDeclarativeEnginePrivate::getScriptEngine(bindEngine)), | |
engine(bindEngine) | |
{ | |
qRegisterMetaType<QList<QObject *> >("QList<QObject *>"); | |
setSupportsCall(true); | |
QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); | |
m_connect = scriptEngine->newFunction(connect); | |
m_connectId = createPersistentIdentifier(QLatin1String("connect")); | |
m_disconnect = scriptEngine->newFunction(disconnect); | |
m_disconnectId = createPersistentIdentifier(QLatin1String("disconnect")); | |
} | |
QDeclarativeObjectMethodScriptClass::~QDeclarativeObjectMethodScriptClass() | |
{ | |
} | |
QScriptValue QDeclarativeObjectMethodScriptClass::newMethod(QObject *object, const QDeclarativePropertyCache::Data *method) | |
{ | |
QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); | |
return newObject(scriptEngine, this, new MethodData(object, *method)); | |
} | |
QScriptValue QDeclarativeObjectMethodScriptClass::connect(QScriptContext *context, QScriptEngine *engine) | |
{ | |
QDeclarativeEnginePrivate *p = QDeclarativeEnginePrivate::get(engine); | |
QScriptValue that = context->thisObject(); | |
if (&p->objectClass->methods != scriptClass(that)) | |
return engine->undefinedValue(); | |
MethodData *data = (MethodData *)object(that); | |
if (!data->object || context->argumentCount() == 0) | |
return engine->undefinedValue(); | |
QByteArray signal("2"); | |
signal.append(data->object->metaObject()->method(data->data.coreIndex).signature()); | |
if (context->argumentCount() == 1) { | |
qScriptConnect(data->object, signal.constData(), QScriptValue(), context->argument(0)); | |
} else { | |
qScriptConnect(data->object, signal.constData(), context->argument(0), context->argument(1)); | |
} | |
return engine->undefinedValue(); | |
} | |
QScriptValue QDeclarativeObjectMethodScriptClass::disconnect(QScriptContext *context, QScriptEngine *engine) | |
{ | |
QDeclarativeEnginePrivate *p = QDeclarativeEnginePrivate::get(engine); | |
QScriptValue that = context->thisObject(); | |
if (&p->objectClass->methods != scriptClass(that)) | |
return engine->undefinedValue(); | |
MethodData *data = (MethodData *)object(that); | |
if (!data->object || context->argumentCount() == 0) | |
return engine->undefinedValue(); | |
QByteArray signal("2"); | |
signal.append(data->object->metaObject()->method(data->data.coreIndex).signature()); | |
if (context->argumentCount() == 1) { | |
qScriptDisconnect(data->object, signal.constData(), QScriptValue(), context->argument(0)); | |
} else { | |
qScriptDisconnect(data->object, signal.constData(), context->argument(0), context->argument(1)); | |
} | |
return engine->undefinedValue(); | |
} | |
QScriptClass::QueryFlags | |
QDeclarativeObjectMethodScriptClass::queryProperty(Object *, const Identifier &name, | |
QScriptClass::QueryFlags flags) | |
{ | |
Q_UNUSED(flags); | |
if (name == m_connectId.identifier || name == m_disconnectId.identifier) | |
return QScriptClass::HandlesReadAccess; | |
else | |
return 0; | |
} | |
QDeclarativeObjectMethodScriptClass::Value | |
QDeclarativeObjectMethodScriptClass::property(Object *, const Identifier &name) | |
{ | |
QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine); | |
if (name == m_connectId.identifier) | |
return Value(scriptEngine, m_connect); | |
else if (name == m_disconnectId.identifier) | |
return Value(scriptEngine, m_disconnect); | |
else | |
return Value(); | |
} | |
namespace { | |
struct MetaCallArgument { | |
inline MetaCallArgument(); | |
inline ~MetaCallArgument(); | |
inline void *dataPtr(); | |
inline void initAsType(int type, QDeclarativeEngine *); | |
void fromScriptValue(int type, QDeclarativeEngine *, const QScriptValue &); | |
inline QScriptDeclarativeClass::Value toValue(QDeclarativeEngine *); | |
private: | |
MetaCallArgument(const MetaCallArgument &); | |
inline void cleanup(); | |
char data[4 * sizeof(void *)]; | |
int type; | |
bool isObjectType; | |
}; | |
} | |
MetaCallArgument::MetaCallArgument() | |
: type(QVariant::Invalid), isObjectType(false) | |
{ | |
} | |
MetaCallArgument::~MetaCallArgument() | |
{ | |
cleanup(); | |
} | |
void MetaCallArgument::cleanup() | |
{ | |
if (type == QMetaType::QString) { | |
((QString *)&data)->~QString(); | |
} else if (type == -1 || type == qMetaTypeId<QVariant>()) { | |
((QVariant *)&data)->~QVariant(); | |
} else if (type == qMetaTypeId<QScriptValue>()) { | |
((QScriptValue *)&data)->~QScriptValue(); | |
} else if (type == qMetaTypeId<QList<QObject *> >()) { | |
((QList<QObject *> *)&data)->~QList<QObject *>(); | |
} | |
} | |
void *MetaCallArgument::dataPtr() | |
{ | |
if (type == -1) | |
return ((QVariant *)data)->data(); | |
else | |
return (void *)&data; | |
} | |
void MetaCallArgument::initAsType(int callType, QDeclarativeEngine *e) | |
{ | |
if (type != 0) { cleanup(); type = 0; } | |
if (callType == 0) return; | |
QScriptEngine *engine = QDeclarativeEnginePrivate::getScriptEngine(e); | |
if (callType == qMetaTypeId<QScriptValue>()) { | |
new (&data) QScriptValue(engine->undefinedValue()); | |
type = callType; | |
} else if (callType == QMetaType::Int || | |
callType == QMetaType::UInt || | |
callType == QMetaType::Bool || | |
callType == QMetaType::Double || | |
callType == QMetaType::Float) { | |
type = callType; | |
} else if (callType == QMetaType::QObjectStar) { | |
*((QObject **)&data) = 0; | |
type = callType; | |
} else if (callType == QMetaType::QString) { | |
new (&data) QString(); | |
type = callType; | |
} else if (callType == qMetaTypeId<QVariant>()) { | |
type = callType; | |
new (&data) QVariant(); | |
} else if (callType == qMetaTypeId<QList<QObject *> >()) { | |
type = callType; | |
new (&data) QList<QObject *>(); | |
} else { | |
type = -1; | |
new (&data) QVariant(callType, (void *)0); | |
} | |
} | |
void MetaCallArgument::fromScriptValue(int callType, QDeclarativeEngine *engine, const QScriptValue &value) | |
{ | |
if (type != 0) { cleanup(); type = 0; } | |
if (callType == qMetaTypeId<QScriptValue>()) { | |
new (&data) QScriptValue(value); | |
type = qMetaTypeId<QScriptValue>(); | |
} else if (callType == QMetaType::Int) { | |
*((int *)&data) = int(value.toInt32()); | |
type = callType; | |
} else if (callType == QMetaType::UInt) { | |
*((uint *)&data) = uint(value.toUInt32()); | |
type = callType; | |
} else if (callType == QMetaType::Bool) { | |
*((bool *)&data) = value.toBool(); | |
type = callType; | |
} else if (callType == QMetaType::Double) { | |
*((double *)&data) = double(value.toNumber()); | |
type = callType; | |
} else if (callType == QMetaType::Float) { | |
*((float *)&data) = float(value.toNumber()); | |
type = callType; | |
} else if (callType == QMetaType::QString) { | |
if (value.isNull() || value.isUndefined()) | |
new (&data) QString(); | |
else | |
new (&data) QString(value.toString()); | |
type = callType; | |
} else if (callType == QMetaType::QObjectStar) { | |
*((QObject **)&data) = value.toQObject(); | |
type = callType; | |
} else if (callType == qMetaTypeId<QVariant>()) { | |
new (&data) QVariant(QDeclarativeEnginePrivate::get(engine)->scriptValueToVariant(value)); | |
type = callType; | |
} else if (callType == qMetaTypeId<QList<QObject*> >()) { | |
QList<QObject *> *list = new (&data) QList<QObject *>(); | |
if (value.isArray()) { | |
int length = value.property(QLatin1String("length")).toInt32(); | |
for (int ii = 0; ii < length; ++ii) { | |
QScriptValue arrayItem = value.property(ii); | |
QObject *d = arrayItem.toQObject(); | |
list->append(d); | |
} | |
} else if (QObject *d = value.toQObject()) { | |
list->append(d); | |
} | |
type = callType; | |
} else { | |
new (&data) QVariant(); | |
type = -1; | |
QDeclarativeEnginePrivate *priv = QDeclarativeEnginePrivate::get(engine); | |
QVariant v = priv->scriptValueToVariant(value); | |
if (v.userType() == callType) { | |
*((QVariant *)&data) = v; | |
} else if (v.canConvert((QVariant::Type)callType)) { | |
*((QVariant *)&data) = v; | |
((QVariant *)&data)->convert((QVariant::Type)callType); | |
} else if (const QMetaObject *mo = priv->rawMetaObjectForType(callType)) { | |
QObject *obj = priv->toQObject(v); | |
if (obj) { | |
const QMetaObject *objMo = obj->metaObject(); | |
while (objMo && objMo != mo) objMo = objMo->superClass(); | |
if (!objMo) obj = 0; | |
} | |
*((QVariant *)&data) = QVariant(callType, &obj); | |
} else { | |
*((QVariant *)&data) = QVariant(callType, (void *)0); | |
} | |
} | |
} | |
QScriptDeclarativeClass::Value MetaCallArgument::toValue(QDeclarativeEngine *e) | |
{ | |
QScriptEngine *engine = QDeclarativeEnginePrivate::getScriptEngine(e); | |
if (type == qMetaTypeId<QScriptValue>()) { | |
return QScriptDeclarativeClass::Value(engine, *((QScriptValue *)&data)); | |
} else if (type == QMetaType::Int) { | |
return QScriptDeclarativeClass::Value(engine, *((int *)&data)); | |
} else if (type == QMetaType::UInt) { | |
return QScriptDeclarativeClass::Value(engine, *((uint *)&data)); | |
} else if (type == QMetaType::Bool) { | |
return QScriptDeclarativeClass::Value(engine, *((bool *)&data)); | |
} else if (type == QMetaType::Double) { | |
return QScriptDeclarativeClass::Value(engine, *((double *)&data)); | |
} else if (type == QMetaType::Float) { | |
return QScriptDeclarativeClass::Value(engine, *((float *)&data)); | |
} else if (type == QMetaType::QString) { | |
return QScriptDeclarativeClass::Value(engine, *((QString *)&data)); | |
} else if (type == QMetaType::QObjectStar) { | |
QObject *object = *((QObject **)&data); | |
if (object) | |
QDeclarativeData::get(object, true)->setImplicitDestructible(); | |
QDeclarativeEnginePrivate *priv = QDeclarativeEnginePrivate::get(e); | |
return QScriptDeclarativeClass::Value(engine, priv->objectClass->newQObject(object)); | |
} else if (type == qMetaTypeId<QList<QObject *> >()) { | |
QList<QObject *> &list = *(QList<QObject *>*)&data; | |
QScriptValue rv = engine->newArray(list.count()); | |
QDeclarativeEnginePrivate *priv = QDeclarativeEnginePrivate::get(e); | |
for (int ii = 0; ii < list.count(); ++ii) { | |
QObject *object = list.at(ii); | |
QDeclarativeData::get(object, true)->setImplicitDestructible(); | |
rv.setProperty(ii, priv->objectClass->newQObject(object)); | |
} | |
return QScriptDeclarativeClass::Value(engine, rv); | |
} else if (type == -1 || type == qMetaTypeId<QVariant>()) { | |
QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(e); | |
QScriptValue rv = ep->scriptValueFromVariant(*((QVariant *)&data)); | |
if (rv.isQObject()) { | |
QObject *object = rv.toQObject(); | |
if (object) | |
QDeclarativeData::get(object, true)->setImplicitDestructible(); | |
} | |
return QScriptDeclarativeClass::Value(engine, rv); | |
} else { | |
return QScriptDeclarativeClass::Value(); | |
} | |
} | |
int QDeclarativeObjectMethodScriptClass::enumType(const QMetaObject *meta, const QString &strname) | |
{ | |
QByteArray str = strname.toUtf8(); | |
QByteArray scope; | |
QByteArray name; | |
int scopeIdx = str.lastIndexOf("::"); | |
if (scopeIdx != -1) { | |
scope = str.left(scopeIdx); | |
name = str.mid(scopeIdx + 2); | |
} else { | |
name = str; | |
} | |
for (int i = meta->enumeratorCount() - 1; i >= 0; --i) { | |
QMetaEnum m = meta->enumerator(i); | |
if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope))) | |
return QVariant::Int; | |
} | |
return QVariant::Invalid; | |
} | |
QDeclarativeObjectMethodScriptClass::Value QDeclarativeObjectMethodScriptClass::call(Object *o, QScriptContext *ctxt) | |
{ | |
MethodData *method = static_cast<MethodData *>(o); | |
if (method->data.relatedIndex == -1) | |
return callPrecise(method->object, method->data, ctxt); | |
else | |
return callOverloaded(method, ctxt); | |
} | |
QDeclarativeObjectMethodScriptClass::Value | |
QDeclarativeObjectMethodScriptClass::callPrecise(QObject *object, const QDeclarativePropertyCache::Data &data, | |
QScriptContext *ctxt) | |
{ | |
if (data.flags & QDeclarativePropertyCache::Data::HasArguments) { | |
QMetaMethod m = object->metaObject()->method(data.coreIndex); | |
QList<QByteArray> argTypeNames = m.parameterTypes(); | |
QVarLengthArray<int, 9> argTypes(argTypeNames.count()); | |
// ### Cache | |
for (int ii = 0; ii < argTypeNames.count(); ++ii) { | |
argTypes[ii] = QMetaType::type(argTypeNames.at(ii)); | |
if (argTypes[ii] == QVariant::Invalid) | |
argTypes[ii] = enumType(object->metaObject(), QString::fromLatin1(argTypeNames.at(ii))); | |
if (argTypes[ii] == QVariant::Invalid) | |
return Value(ctxt, ctxt->throwError(QString::fromLatin1("Unknown method parameter type: %1").arg(QLatin1String(argTypeNames.at(ii))))); | |
} | |
if (argTypes.count() > ctxt->argumentCount()) | |
return Value(ctxt, ctxt->throwError(QLatin1String("Insufficient arguments"))); | |
return callMethod(object, data.coreIndex, data.propType, argTypes.count(), argTypes.data(), ctxt); | |
} else { | |
return callMethod(object, data.coreIndex, data.propType, 0, 0, ctxt); | |
} | |
} | |
QDeclarativeObjectMethodScriptClass::Value | |
QDeclarativeObjectMethodScriptClass::callMethod(QObject *object, int index, | |
int returnType, int argCount, int *argTypes, | |
QScriptContext *ctxt) | |
{ | |
if (argCount > 0) { | |
QVarLengthArray<MetaCallArgument, 9> args(argCount + 1); | |
args[0].initAsType(returnType, engine); | |
for (int ii = 0; ii < argCount; ++ii) | |
args[ii + 1].fromScriptValue(argTypes[ii], engine, ctxt->argument(ii)); | |
QVarLengthArray<void *, 9> argData(args.count()); | |
for (int ii = 0; ii < args.count(); ++ii) | |
argData[ii] = args[ii].dataPtr(); | |
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, index, argData.data()); | |
return args[0].toValue(engine); | |
} else if (returnType != 0) { | |
MetaCallArgument arg; | |
arg.initAsType(returnType, engine); | |
void *args[] = { arg.dataPtr() }; | |
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, index, args); | |
return arg.toValue(engine); | |
} else { | |
void *args[] = { 0 }; | |
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, index, args); | |
return Value(); | |
} | |
} | |
/*! | |
Resolve the overloaded method to call. The algorithm works conceptually like this: | |
1. Resolve the set of overloads it is *possible* to call. | |
Impossible overloads include those that have too many parameters or have parameters | |
of unknown type. | |
2. Filter the set of overloads to only contain those with the closest number of | |
parameters. | |
For example, if we are called with 3 parameters and there are 2 overloads that | |
take 2 parameters and one that takes 3, eliminate the 2 parameter overloads. | |
3. Find the best remaining overload based on its match score. | |
If two or more overloads have the same match score, call the last one. The match | |
score is constructed by adding the matchScore() result for each of the parameters. | |
*/ | |
QDeclarativeObjectMethodScriptClass::Value | |
QDeclarativeObjectMethodScriptClass::callOverloaded(MethodData *method, QScriptContext *ctxt) | |
{ | |
int argumentCount = ctxt->argumentCount(); | |
QDeclarativePropertyCache::Data *best = 0; | |
int bestParameterScore = INT_MAX; | |
int bestMatchScore = INT_MAX; | |
QDeclarativePropertyCache::Data dummy; | |
QDeclarativePropertyCache::Data *attempt = &method->data; | |
do { | |
QList<QByteArray> methodArgTypeNames; | |
if (attempt->flags & QDeclarativePropertyCache::Data::HasArguments) | |
methodArgTypeNames = method->object->metaObject()->method(attempt->coreIndex).parameterTypes(); | |
int methodArgumentCount = methodArgTypeNames.count(); | |
if (methodArgumentCount > argumentCount) | |
continue; // We don't have sufficient arguments to call this method | |
int methodParameterScore = argumentCount - methodArgumentCount; | |
if (methodParameterScore > bestParameterScore) | |
continue; // We already have a better option | |
int methodMatchScore = 0; | |
QVarLengthArray<int, 9> methodArgTypes(methodArgumentCount); | |
bool unknownArgument = false; | |
for (int ii = 0; ii < methodArgumentCount; ++ii) { | |
methodArgTypes[ii] = QMetaType::type(methodArgTypeNames.at(ii)); | |
if (methodArgTypes[ii] == QVariant::Invalid) | |
methodArgTypes[ii] = enumType(method->object->metaObject(), | |
QString::fromLatin1(methodArgTypeNames.at(ii))); | |
if (methodArgTypes[ii] == QVariant::Invalid) { | |
unknownArgument = true; | |
break; | |
} | |
methodMatchScore += matchScore(ctxt->argument(ii), methodArgTypes[ii], methodArgTypeNames.at(ii)); | |
} | |
if (unknownArgument) | |
continue; // We don't understand all the parameters | |
if (bestParameterScore > methodParameterScore || bestMatchScore > methodMatchScore) { | |
best = attempt; | |
bestParameterScore = methodParameterScore; | |
bestMatchScore = methodMatchScore; | |
} | |
if (bestParameterScore == 0 && bestMatchScore == 0) | |
break; // We can't get better than that | |
} while((attempt = relatedMethod(method->object, attempt, dummy)) != 0); | |
if (best) { | |
return callPrecise(method->object, *best, ctxt); | |
} else { | |
QString error = QLatin1String("Unable to determine callable overload. Candidates are:"); | |
QDeclarativePropertyCache::Data *candidate = &method->data; | |
while (candidate) { | |
error += QLatin1String("\n ") + QString::fromUtf8(method->object->metaObject()->method(candidate->coreIndex).signature()); | |
candidate = relatedMethod(method->object, candidate, dummy); | |
} | |
return Value(ctxt, ctxt->throwError(error)); | |
} | |
} | |
/*! | |
Returns the match score for converting \a actual to be of type \a conversionType. A | |
zero score means "perfect match" whereas a higher score is worse. | |
The conversion table is copied out of the QtScript callQtMethod() function. | |
*/ | |
int QDeclarativeObjectMethodScriptClass::matchScore(const QScriptValue &actual, int conversionType, | |
const QByteArray &conversionTypeName) | |
{ | |
if (actual.isNumber()) { | |
switch (conversionType) { | |
case QMetaType::Double: | |
return 0; | |
case QMetaType::Float: | |
return 1; | |
case QMetaType::LongLong: | |
case QMetaType::ULongLong: | |
return 2; | |
case QMetaType::Long: | |
case QMetaType::ULong: | |
return 3; | |
case QMetaType::Int: | |
case QMetaType::UInt: | |
return 4; | |
case QMetaType::Short: | |
case QMetaType::UShort: | |
return 5; | |
break; | |
case QMetaType::Char: | |
case QMetaType::UChar: | |
return 6; | |
default: | |
return 10; | |
} | |
} else if (actual.isString()) { | |
switch (conversionType) { | |
case QMetaType::QString: | |
return 0; | |
default: | |
return 10; | |
} | |
} else if (actual.isBoolean()) { | |
switch (conversionType) { | |
case QMetaType::Bool: | |
return 0; | |
default: | |
return 10; | |
} | |
} else if (actual.isDate()) { | |
switch (conversionType) { | |
case QMetaType::QDateTime: | |
return 0; | |
case QMetaType::QDate: | |
return 1; | |
case QMetaType::QTime: | |
return 2; | |
default: | |
return 10; | |
} | |
} else if (actual.isRegExp()) { | |
switch (conversionType) { | |
case QMetaType::QRegExp: | |
return 0; | |
default: | |
return 10; | |
} | |
} else if (actual.isVariant()) { | |
if (conversionType == qMetaTypeId<QVariant>()) | |
return 0; | |
else if (actual.toVariant().userType() == conversionType) | |
return 0; | |
else | |
return 10; | |
} else if (actual.isArray()) { | |
switch (conversionType) { | |
case QMetaType::QStringList: | |
case QMetaType::QVariantList: | |
return 5; | |
default: | |
return 10; | |
} | |
} else if (actual.isQObject()) { | |
switch (conversionType) { | |
case QMetaType::QObjectStar: | |
return 0; | |
default: | |
return 10; | |
} | |
} else if (actual.isNull()) { | |
switch (conversionType) { | |
case QMetaType::VoidStar: | |
case QMetaType::QObjectStar: | |
return 0; | |
default: | |
if (!conversionTypeName.endsWith('*')) | |
return 10; | |
else | |
return 0; | |
} | |
} else { | |
return 10; | |
} | |
} | |
static inline int QMetaObject_methods(const QMetaObject *metaObject) | |
{ | |
struct Private | |
{ | |
int revision; | |
int className; | |
int classInfoCount, classInfoData; | |
int methodCount, methodData; | |
}; | |
return reinterpret_cast<const Private *>(metaObject->d.data)->methodCount; | |
} | |
static QByteArray QMetaMethod_name(const QMetaMethod &m) | |
{ | |
QByteArray sig = m.signature(); | |
int paren = sig.indexOf('('); | |
if (paren == -1) | |
return sig; | |
else | |
return sig.left(paren); | |
} | |
/*! | |
Returns the next related method, if one, or 0. | |
*/ | |
QDeclarativePropertyCache::Data * | |
QDeclarativeObjectMethodScriptClass::relatedMethod(QObject *object, QDeclarativePropertyCache::Data *current, | |
QDeclarativePropertyCache::Data &dummy) | |
{ | |
QDeclarativePropertyCache *cache = QDeclarativeData::get(object)->propertyCache; | |
if (current->relatedIndex == -1) | |
return 0; | |
if (cache) { | |
return cache->method(current->relatedIndex); | |
} else { | |
const QMetaObject *mo = object->metaObject(); | |
int methodOffset = mo->methodCount() - QMetaObject_methods(mo); | |
while (methodOffset > current->relatedIndex) { | |
mo = mo->superClass(); | |
methodOffset -= QMetaObject_methods(mo); | |
} | |
QMetaMethod method = mo->method(current->relatedIndex); | |
dummy.load(method); | |
// Look for overloaded methods | |
QByteArray methodName = QMetaMethod_name(method); | |
for (int ii = current->relatedIndex - 1; ii >= methodOffset; --ii) { | |
if (methodName == QMetaMethod_name(mo->method(ii))) { | |
dummy.relatedIndex = ii; | |
return &dummy; | |
} | |
} | |
return &dummy; | |
} | |
} | |
QT_END_NAMESPACE | |