/**************************************************************************** ** ** 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 #include #include 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 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()) { 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 >()); 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; iproperty(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 object; QDeclarativePropertyCache::Data data; }; QDeclarativeObjectMethodScriptClass::QDeclarativeObjectMethodScriptClass(QDeclarativeEngine *bindEngine) : QScriptDeclarativeClass(QDeclarativeEnginePrivate::getScriptEngine(bindEngine)), engine(bindEngine) { qRegisterMetaType >("QList"); 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 *)&data)->~QVariant(); } else if (type == qMetaTypeId()) { ((QScriptValue *)&data)->~QScriptValue(); } else if (type == qMetaTypeId >()) { ((QList *)&data)->~QList(); } } 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()) { 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()) { type = callType; new (&data) QVariant(); } else if (callType == qMetaTypeId >()) { type = callType; new (&data) QList(); } 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()) { new (&data) QScriptValue(value); type = qMetaTypeId(); } 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()) { new (&data) QVariant(QDeclarativeEnginePrivate::get(engine)->scriptValueToVariant(value)); type = callType; } else if (callType == qMetaTypeId >()) { QList *list = new (&data) QList(); 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()) { 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 &list = *(QList*)&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()) { 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(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 argTypeNames = m.parameterTypes(); QVarLengthArray 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 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 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 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 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()) 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(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