blob: 5b5bd5cd368fc4283553b2397e049f66ecc16ca5 [file] [log] [blame]
/****************************************************************************
**
** 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