| /**************************************************************************** |
| ** |
| ** 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 |
| |