diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:34:13 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:34:13 (GMT) |
commit | 67ad0519fd165acee4a4d2a94fa502e9e4847bd0 (patch) | |
tree | 1dbf50b3dff8d5ca7e9344733968c72704eb15ff /src/script/qscriptextqobject.cpp | |
download | Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.zip Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.gz Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.bz2 |
Long live Qt!
Diffstat (limited to 'src/script/qscriptextqobject.cpp')
-rw-r--r-- | src/script/qscriptextqobject.cpp | 2209 |
1 files changed, 2209 insertions, 0 deletions
diff --git a/src/script/qscriptextqobject.cpp b/src/script/qscriptextqobject.cpp new file mode 100644 index 0000000..d18c3da --- /dev/null +++ b/src/script/qscriptextqobject.cpp @@ -0,0 +1,2209 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtScript module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qglobal.h> + +#ifndef QT_NO_SCRIPT + +#include "qscriptengine_p.h" +#include "qscriptvalueimpl_p.h" +#include "qscriptcontext_p.h" +#include "qscriptmember_p.h" +#include "qscriptobject_p.h" +#include "qscriptable.h" +#include "qscriptable_p.h" +#include "qscriptextqobject_p.h" + +#include <QtCore/QtDebug> +#include <QtCore/QMetaMethod> +#include <QtCore/QRegExp> +#include <QtCore/QVarLengthArray> +#include <QtCore/QPointer> + +QT_BEGIN_NAMESPACE + +// we use bits 15..12 of property flags +enum { + PROPERTY_ID = 0 << 12, + DYNAPROPERTY_ID = 1 << 12, + METHOD_ID = 2 << 12, + CHILD_ID = 3 << 12, + ID_MASK = 7 << 12, + MAYBE_OVERLOADED = 8 << 12 +}; + +static const bool GeneratePropertyFunctions = true; + +int QScriptMetaType::typeId() const +{ + if (isVariant()) + return QMetaType::type("QVariant"); + return isMetaEnum() ? 2/*int*/ : m_typeId; +} + +QByteArray QScriptMetaType::name() const +{ + if (!m_name.isEmpty()) + return m_name; + else if (m_kind == Variant) + return "QVariant"; + return QMetaType::typeName(typeId()); +} + +namespace QScript { + +class QObjectNotifyCaller : public QObject +{ +public: + void callConnectNotify(const char *signal) + { connectNotify(signal); } + void callDisconnectNotify(const char *signal) + { disconnectNotify(signal); } +}; + +class QtPropertyFunction: public QScriptFunction +{ +public: + QtPropertyFunction(const QMetaObject *meta, int index) + : m_meta(meta), m_index(index) + { } + + ~QtPropertyFunction() { } + + virtual void execute(QScriptContextPrivate *context); + + virtual Type type() const { return QScriptFunction::QtProperty; } + + virtual QString functionName() const; + +private: + const QMetaObject *m_meta; + int m_index; +}; + +class QObjectPrototype : public QObject +{ + Q_OBJECT +public: + QObjectPrototype(QObject *parent = 0) + : QObject(parent) { } + ~QObjectPrototype() { } +}; + +static inline QByteArray methodName(const QMetaMethod &method) +{ + QByteArray signature = method.signature(); + return signature.left(signature.indexOf('(')); +} + +static inline QVariant variantFromValue(QScriptEnginePrivate *eng, + int targetType, const QScriptValueImpl &value) +{ + QVariant v(targetType, (void *)0); + Q_ASSERT(eng); + if (QScriptEnginePrivate::convert(value, targetType, v.data(), eng)) + return v; + if (uint(targetType) == QVariant::LastType) + return value.toVariant(); + if (value.isVariant()) { + v = value.toVariant(); + if (v.canConvert(QVariant::Type(targetType))) { + v.convert(QVariant::Type(targetType)); + return v; + } + QByteArray typeName = v.typeName(); + if (typeName.endsWith('*') + && (QMetaType::type(typeName.left(typeName.size()-1)) == targetType)) { + return QVariant(targetType, *reinterpret_cast<void* *>(v.data())); + } + } + + return QVariant(); +} + +void ExtQObject::Instance::finalize(QScriptEnginePrivate *eng) +{ + switch (ownership) { + case QScriptEngine::QtOwnership: + break; + case QScriptEngine::ScriptOwnership: + if (value) + eng->disposeQObject(value); + break; + case QScriptEngine::AutoOwnership: + if (value && !value->parent()) + eng->disposeQObject(value); + break; + } +} + +ExtQObject::Instance *ExtQObject::Instance::get(const QScriptValueImpl &object, QScriptClassInfo *klass) +{ + if (! klass || klass == object.classInfo()) + return static_cast<Instance*> (object.objectData()); + + return 0; +} + + +static inline QScriptable *scriptableFromQObject(QObject *qobj) +{ + void *ptr = qobj->qt_metacast("QScriptable"); + return reinterpret_cast<QScriptable*>(ptr); +} + +static bool isObjectProperty(const QScriptValueImpl &object, const char *name) +{ + QScriptEnginePrivate *eng = object.engine(); + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(name)); + QScript::Member member; + QScriptValueImpl base; + return object.resolve(nameId, &member, &base, QScriptValue::ResolveLocal, QScript::Read) + && member.testFlags(QScript::Member::ObjectProperty); +} + +static bool hasMethodAccess(const QMetaMethod &method, int index, const QScriptEngine::QObjectWrapOptions &opt) +{ + return (method.access() != QMetaMethod::Private) + && ((index != 2) || !(opt & QScriptEngine::ExcludeDeleteLater)); +} + +static bool isEnumerableMetaProperty(const QMetaProperty &prop, + const QMetaObject *mo, int index) +{ + return prop.isScriptable() && prop.isValid() + // the following lookup is to ensure that we have the + // "most derived" occurrence of the property with this name + && (mo->indexOfProperty(prop.name()) == index); +} + +static uint flagsForMetaProperty(const QMetaProperty &prop) +{ + return (QScriptValue::Undeletable + | (!prop.isWritable() + ? QScriptValue::ReadOnly + : QScriptValue::PropertyFlag(0)) + | (GeneratePropertyFunctions + ? (QScriptValue::PropertyGetter + | QScriptValue::PropertySetter) + : QScriptValue::PropertyFlag(0)) + | QScriptValue::QObjectMember + | PROPERTY_ID); +} + + +static int indexOfMetaEnum(const QMetaObject *meta, const QByteArray &str) +{ + 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 i; + } + return -1; +} + +static QMetaMethod metaMethod(const QMetaObject *meta, + QMetaMethod::MethodType type, + int index) +{ + if (type != QMetaMethod::Constructor) + return meta->method(index); + else + return meta->constructor(index); +} + +static void callQtMethod(QScriptContextPrivate *context, QMetaMethod::MethodType callType, + QObject *thisQObject, const QMetaObject *meta, int initialIndex, + bool maybeOverloaded) +{ + QScriptValueImpl result; + QScriptEnginePrivate *engine = context->engine(); + + int limit; +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + int lastFoundIndex = initialIndex; + QScriptMetaObject *metaCache = engine->cachedMetaObject(meta); + if (callType != QMetaMethod::Constructor) + limit = metaCache->methodLowerBound(initialIndex); + else + limit = 0; +#else + limit = 0; +#endif + + QByteArray funName; + QScriptMetaMethod chosenMethod; + int chosenIndex = -1; + QVarLengthArray<QVariant, 9> args; + QVector<QScriptMetaArguments> candidates; + QVector<QScriptMetaArguments> unresolved; + QVector<int> tooFewArgs; + QVector<int> conversionFailed; + int index; + for (index = initialIndex; index >= limit; --index) { + QScriptMetaMethod mtd; +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + if (callType != QMetaMethod::Constructor) + mtd = metaCache->findMethod(index); + if (!mtd.isValid()) +#endif + { + QMetaMethod method = metaMethod(meta, callType, index); + + QVector<QScriptMetaType> types; + // resolve return type + QByteArray returnTypeName = method.typeName(); + int rtype = QMetaType::type(returnTypeName); + if ((rtype == 0) && !returnTypeName.isEmpty()) { + if (returnTypeName == "QVariant") { + types.append(QScriptMetaType::variant()); + } else { + int enumIndex = indexOfMetaEnum(meta, returnTypeName); + if (enumIndex != -1) + types.append(QScriptMetaType::metaEnum(enumIndex, returnTypeName)); + else + types.append(QScriptMetaType::unresolved(returnTypeName)); + } + } else { + if (callType == QMetaMethod::Constructor) + types.append(QScriptMetaType::metaType(QMetaType::QObjectStar, "QObject*")); + else if (returnTypeName == "QVariant") + types.append(QScriptMetaType::variant()); + else + types.append(QScriptMetaType::metaType(rtype, returnTypeName)); + } + // resolve argument types + QList<QByteArray> parameterTypeNames = method.parameterTypes(); + for (int i = 0; i < parameterTypeNames.count(); ++i) { + QByteArray argTypeName = parameterTypeNames.at(i); + int atype = QMetaType::type(argTypeName); + if (atype == 0) { + if (argTypeName == "QVariant") { + types.append(QScriptMetaType::variant()); + } else { + int enumIndex = indexOfMetaEnum(meta, argTypeName); + if (enumIndex != -1) + types.append(QScriptMetaType::metaEnum(enumIndex, argTypeName)); + else + types.append(QScriptMetaType::unresolved(argTypeName)); + } + } else { + if (argTypeName == "QVariant") + types.append(QScriptMetaType::variant()); + else + types.append(QScriptMetaType::metaType(atype, argTypeName)); + } + } + + mtd = QScriptMetaMethod(methodName(method), types); + +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + if (mtd.fullyResolved() && (callType != QMetaMethod::Constructor)) + metaCache->registerMethod(index, mtd); +#endif + } + + if (index == initialIndex) + funName = mtd.name(); + else { + if (mtd.name() != funName) + continue; +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + lastFoundIndex = index; +#endif + } + + if (context->argumentCount() < mtd.argumentCount()) { + tooFewArgs.append(index); + continue; + } + + if (!mtd.fullyResolved()) { + // remember it so we can give an error message later, if necessary + unresolved.append(QScriptMetaArguments(/*matchDistance=*/INT_MAX, index, + mtd, QVarLengthArray<QVariant, 9>())); + if (mtd.hasUnresolvedReturnType()) + continue; + } + + if (args.count() != mtd.count()) + args.resize(mtd.count()); + + QScriptMetaType retType = mtd.returnType(); + args[0] = QVariant(retType.typeId(), (void *)0); // the result + + // try to convert arguments + bool converted = true; + int matchDistance = 0; + for (int i = 0; converted && i < mtd.argumentCount(); ++i) { + QScriptValueImpl actual = context->argument(i); + QScriptMetaType argType = mtd.argumentType(i); + int tid = -1; + QVariant v; + if (argType.isUnresolved()) { + v = QVariant(QMetaType::QObjectStar, (void *)0); + converted = engine->convertToNativeQObject( + actual, argType.name(), reinterpret_cast<void* *>(v.data())); + } else if (argType.isVariant()) { + if (actual.isVariant()) { + v = actual.variantValue(); + } else { + v = actual.toVariant(); + converted = v.isValid() || actual.isUndefined() || actual.isNull(); + } + } else { + tid = argType.typeId(); + v = QVariant(tid, (void *)0); + converted = QScriptEnginePrivate::convert(actual, tid, v.data(), engine); + if (engine->hasUncaughtException()) + return; + } + + if (!converted) { + if (actual.isVariant()) { + if (tid == -1) + tid = argType.typeId(); + QVariant &vv = actual.variantValue(); + if (vv.canConvert(QVariant::Type(tid))) { + v = vv; + converted = v.convert(QVariant::Type(tid)); + if (converted && (vv.userType() != tid)) + matchDistance += 10; + } else { + QByteArray vvTypeName = vv.typeName(); + if (vvTypeName.endsWith('*') + && (vvTypeName.left(vvTypeName.size()-1) == argType.name())) { + v = QVariant(tid, *reinterpret_cast<void* *>(vv.data())); + converted = true; + matchDistance += 10; + } + } + } else if (actual.isNumber()) { + // see if it's an enum value + QMetaEnum m; + if (argType.isMetaEnum()) { + m = meta->enumerator(argType.enumeratorIndex()); + } else { + int mi = indexOfMetaEnum(meta, argType.name()); + if (mi != -1) + m = meta->enumerator(mi); + } + if (m.isValid()) { + int ival = actual.toInt32(); + if (m.valueToKey(ival) != 0) { + qVariantSetValue(v, ival); + converted = true; + matchDistance += 10; + } + } + } + } else { + // determine how well the conversion matched + if (actual.isNumber()) { + switch (tid) { + case QMetaType::Double: + // perfect + break; + case QMetaType::Float: + matchDistance += 1; + break; + case QMetaType::LongLong: + case QMetaType::ULongLong: + matchDistance += 2; + break; + case QMetaType::Long: + case QMetaType::ULong: + matchDistance += 3; + break; + case QMetaType::Int: + case QMetaType::UInt: + matchDistance += 4; + break; + case QMetaType::Short: + case QMetaType::UShort: + matchDistance += 5; + break; + case QMetaType::Char: + case QMetaType::UChar: + matchDistance += 6; + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isString()) { + switch (tid) { + case QMetaType::QString: + // perfect + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isBoolean()) { + switch (tid) { + case QMetaType::Bool: + // perfect + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isDate()) { + switch (tid) { + case QMetaType::QDateTime: + // perfect + break; + case QMetaType::QDate: + matchDistance += 1; + break; + case QMetaType::QTime: + matchDistance += 2; + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isRegExp()) { + switch (tid) { + case QMetaType::QRegExp: + // perfect + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isVariant()) { + if (argType.isVariant() + || (actual.variantValue().userType() == tid)) { + // perfect + } else { + matchDistance += 10; + } + } else if (actual.isArray()) { + switch (tid) { + case QMetaType::QStringList: + case QMetaType::QVariantList: + matchDistance += 5; + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isQObject()) { + switch (tid) { + case QMetaType::QObjectStar: + case QMetaType::QWidgetStar: + // perfect + break; + default: + matchDistance += 10; + break; + } + } else if (actual.isNull()) { + switch (tid) { + case QMetaType::VoidStar: + case QMetaType::QObjectStar: + case QMetaType::QWidgetStar: + // perfect + break; + default: + if (!argType.name().endsWith('*')) + matchDistance += 10; + break; + } + } else { + matchDistance += 10; + } + } + + if (converted) + args[i+1] = v; + } + + if (converted) { + if ((context->argumentCount() == mtd.argumentCount()) + && (matchDistance == 0)) { + // perfect match, use this one + chosenMethod = mtd; + chosenIndex = index; + break; + } else { + bool redundant = false; + if ((callType != QMetaMethod::Constructor) + && (index < meta->methodOffset())) { + // it is possible that a virtual method is redeclared in a subclass, + // in which case we want to ignore the superclass declaration + for (int i = 0; i < candidates.size(); ++i) { + const QScriptMetaArguments &other = candidates.at(i); + if (mtd.types() == other.method.types()) { + redundant = true; + break; + } + } + } + if (!redundant) { + QScriptMetaArguments metaArgs(matchDistance, index, mtd, args); + if (candidates.isEmpty()) { + candidates.append(metaArgs); + } else { + const QScriptMetaArguments &otherArgs = candidates.at(0); + if ((args.count() > otherArgs.args.count()) + || ((args.count() == otherArgs.args.count()) + && (matchDistance <= otherArgs.matchDistance))) { + candidates.prepend(metaArgs); + } else { + candidates.append(metaArgs); + } + } + } + } + } else if (mtd.fullyResolved()) { + conversionFailed.append(index); + } + + if (!maybeOverloaded) + break; + } + +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + if ((index == -1) && (lastFoundIndex != limit) && maybeOverloaded + && (callType != QMetaMethod::Constructor)) { + metaCache->setMethodLowerBound(initialIndex, lastFoundIndex); + } +#endif + + if ((chosenIndex == -1) && candidates.isEmpty()) { + context->calleeMetaIndex = initialIndex; +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + engine->notifyFunctionEntry(context); +#endif + if (!conversionFailed.isEmpty()) { + QString message = QString::fromLatin1("incompatible type of argument(s) in call to %0(); candidates were\n") + .arg(QLatin1String(funName)); + for (int i = 0; i < conversionFailed.size(); ++i) { + if (i > 0) + message += QLatin1String("\n"); + QMetaMethod mtd = metaMethod(meta, callType, conversionFailed.at(i)); + message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); + } + result = context->throwError(QScriptContext::TypeError, message); + } else if (!unresolved.isEmpty()) { + QScriptMetaArguments argsInstance = unresolved.first(); + int unresolvedIndex = argsInstance.method.firstUnresolvedIndex(); + Q_ASSERT(unresolvedIndex != -1); + QScriptMetaType unresolvedType = argsInstance.method.type(unresolvedIndex); + QString unresolvedTypeName = QString::fromLatin1(unresolvedType.name()); + QString message = QString::fromLatin1("cannot call %0(): ") + .arg(QString::fromLatin1(funName)); + if (unresolvedIndex > 0) { + message.append(QString::fromLatin1("argument %0 has unknown type `%1'"). + arg(unresolvedIndex).arg(unresolvedTypeName)); + } else { + message.append(QString::fromLatin1("unknown return type `%0'") + .arg(unresolvedTypeName)); + } + message.append(QString::fromLatin1(" (register the type with qScriptRegisterMetaType())")); + result = context->throwError(QScriptContext::TypeError, message); + } else { + QString message = QString::fromLatin1("too few arguments in call to %0(); candidates are\n") + .arg(QLatin1String(funName)); + for (int i = 0; i < tooFewArgs.size(); ++i) { + if (i > 0) + message += QLatin1String("\n"); + QMetaMethod mtd = metaMethod(meta, callType, tooFewArgs.at(i)); + message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); + } + result = context->throwError(QScriptContext::SyntaxError, message); + } + } else { + if (chosenIndex == -1) { + QScriptMetaArguments metaArgs = candidates.at(0); + if ((candidates.size() > 1) + && (metaArgs.args.count() == candidates.at(1).args.count()) + && (metaArgs.matchDistance == candidates.at(1).matchDistance)) { + // ambiguous call + QString message = QString::fromLatin1("ambiguous call of overloaded function %0(); candidates were\n") + .arg(QLatin1String(funName)); + for (int i = 0; i < candidates.size(); ++i) { + if (i > 0) + message += QLatin1String("\n"); + QMetaMethod mtd = metaMethod(meta, callType, candidates.at(i).index); + message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); + } + result = context->throwError(QScriptContext::TypeError, message); + } else { + chosenMethod = metaArgs.method; + chosenIndex = metaArgs.index; + args = metaArgs.args; + } + } + + if (chosenIndex != -1) { + // call it + context->calleeMetaIndex = chosenIndex; + + QVarLengthArray<void*, 9> array(args.count()); + void **params = array.data(); + for (int i = 0; i < args.count(); ++i) { + const QVariant &v = args[i]; + switch (chosenMethod.type(i).kind()) { + case QScriptMetaType::Variant: + params[i] = const_cast<QVariant*>(&v); + break; + case QScriptMetaType::MetaType: + case QScriptMetaType::MetaEnum: + case QScriptMetaType::Unresolved: + params[i] = const_cast<void*>(v.constData()); + break; + default: + Q_ASSERT(0); + } + } + + QScriptable *scriptable = 0; + if (thisQObject) + scriptable = scriptableFromQObject(thisQObject); + QScriptEngine *oldEngine = 0; + if (scriptable) { + oldEngine = QScriptablePrivate::get(scriptable)->engine; + QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(engine); + } + +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + engine->notifyFunctionEntry(context); +#endif + + if (callType == QMetaMethod::Constructor) { + Q_ASSERT(meta != 0); + meta->static_metacall(QMetaObject::CreateInstance, chosenIndex, params); + } else { + Q_ASSERT(thisQObject != 0); + thisQObject->qt_metacall(QMetaObject::InvokeMetaMethod, chosenIndex, params); + } + + if (scriptable) + QScriptablePrivate::get(scriptable)->engine = oldEngine; + + if (context->state() == QScriptContext::ExceptionState) { + result = context->returnValue(); // propagate + } else { + QScriptMetaType retType = chosenMethod.returnType(); + if (retType.isVariant()) { + result = engine->valueFromVariant(*(QVariant *)params[0]); + } else if (retType.typeId() != 0) { + result = engine->create(retType.typeId(), params[0]); + if (!result.isValid()) + engine->newVariant(&result, QVariant(retType.typeId(), params[0])); + } else { + result = engine->undefinedValue(); + } + } + } + } + + context->m_result = result; +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + engine->notifyFunctionExit(context); +#endif +} + + +class ExtQObjectDataIterator: public QScriptClassDataIterator +{ +public: + ExtQObjectDataIterator(const QScriptValueImpl &object); + virtual ~ExtQObjectDataIterator(); + + virtual bool hasNext() const; + virtual void next(QScript::Member *member); + + virtual bool hasPrevious() const; + virtual void previous(QScript::Member *member); + + virtual void toFront(); + virtual void toBack(); + +private: + enum State { + MetaProperties, + DynamicProperties, + MetaMethods + }; + + QScriptValueImpl m_object; + int m_index; + State m_state; +}; + +ExtQObjectDataIterator::ExtQObjectDataIterator(const QScriptValueImpl &object) +{ + m_object = object; + toFront(); +} + +ExtQObjectDataIterator::~ExtQObjectDataIterator() +{ +} + +bool ExtQObjectDataIterator::hasNext() const +{ + ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); + if (!inst->value) + return false; + const QMetaObject *meta = inst->value->metaObject(); + int i = m_index; + + switch (m_state) { + case MetaProperties: { + for ( ; i < meta->propertyCount(); ++i) { + QMetaProperty prop = meta->property(i); + if (isEnumerableMetaProperty(prop, meta, i) + && !isObjectProperty(m_object, prop.name())) { + return true; + } + } + i = 0; + // fall-through + } + + case DynamicProperties: { + QList<QByteArray> dpNames = inst->value->dynamicPropertyNames(); + for ( ; i < dpNames.count(); ++i) { + if (!isObjectProperty(m_object, dpNames.at(i))) { + return true; + } + } + if (inst->options & QScriptEngine::SkipMethodsInEnumeration) + return false; + i = (inst->options & QScriptEngine::ExcludeSuperClassMethods) + ? meta->methodOffset() : 0; + // fall-through + } + + case MetaMethods: { + for ( ; i < meta->methodCount(); ++i) { + QMetaMethod method = meta->method(i); + if (hasMethodAccess(method, i, inst->options) + && !isObjectProperty(m_object, method.signature())) { + return true; + } + } + } + + } // switch + + return false; +} + +void ExtQObjectDataIterator::next(QScript::Member *member) +{ + QScriptEnginePrivate *eng = m_object.engine(); + ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); + if (!inst->value) + return; + const QMetaObject *meta = inst->value->metaObject(); + int i = m_index; + + switch (m_state) { + case MetaProperties: { + for ( ; i < meta->propertyCount(); ++i) { + QMetaProperty prop = meta->property(i); + if (isEnumerableMetaProperty(prop, meta, i) + && !isObjectProperty(m_object, prop.name())) { + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(prop.name())); + member->native(nameId, i, flagsForMetaProperty(prop)); + m_index = i + 1; + return; + } + } + m_state = DynamicProperties; + m_index = 0; + i = m_index; + // fall-through + } + + case DynamicProperties: { + QList<QByteArray> dpNames = inst->value->dynamicPropertyNames(); + for ( ; i < dpNames.count(); ++i) { + if (!isObjectProperty(m_object, dpNames.at(i))) { + QByteArray name = dpNames.at(i); + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(name)); + member->native(nameId, i, + QScriptValue::QObjectMember + | DYNAPROPERTY_ID); + m_index = i + 1; + return; + } + } + m_state = MetaMethods; + m_index = (inst->options & QScriptEngine::ExcludeSuperClassMethods) + ? meta->methodOffset() : 0; + i = m_index; + // fall-through + } + + case MetaMethods: { + for ( ; i < meta->methodCount(); ++i) { + QMetaMethod method = meta->method(i); + if (hasMethodAccess(method, i, inst->options) + && !isObjectProperty(m_object, method.signature())) { + QMetaMethod method = meta->method(i); + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(method.signature())); + member->native(nameId, i, + QScriptValue::QObjectMember + | METHOD_ID); + m_index = i + 1; + return; + } + } + } + + } // switch + + member->invalidate(); +} + +bool ExtQObjectDataIterator::hasPrevious() const +{ + ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); + if (!inst->value) + return false; + const QMetaObject *meta = inst->value->metaObject(); + int i = m_index - 1; + + switch (m_state) { + case MetaMethods: { + int limit = (inst->options & QScriptEngine::ExcludeSuperClassMethods) + ? meta->methodOffset() : 0; + for ( ; i >= limit; --i) { + QMetaMethod method = meta->method(i); + if (hasMethodAccess(method, i, inst->options) + && !isObjectProperty(m_object, method.signature())) { + return true; + } + } + i = inst->value->dynamicPropertyNames().count() - 1; + // fall-through + } + + case DynamicProperties: { + QList<QByteArray> dpNames = inst->value->dynamicPropertyNames(); + for ( ; i >= 0; --i) { + if (!isObjectProperty(m_object, dpNames.at(i))) { + return true; + } + } + i = meta->propertyCount() - 1; + // fall-through + } + + case MetaProperties: { + int limit = (inst->options & QScriptEngine::ExcludeSuperClassProperties) + ? meta->propertyOffset() : 0; + for ( ; i >= limit; --i) { + QMetaProperty prop = meta->property(i); + if (isEnumerableMetaProperty(prop, meta, i) + && !isObjectProperty(m_object, prop.name())) { + return true; + } + } + } + + } // switch + + return false; +} + +void ExtQObjectDataIterator::previous(QScript::Member *member) +{ + QScriptEnginePrivate *eng = m_object.engine(); + ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); + if (!inst->value) + return; + const QMetaObject *meta = inst->value->metaObject(); + int i = m_index - 1; + + switch (m_state) { + case MetaMethods: { + int limit = (inst->options & QScriptEngine::ExcludeSuperClassMethods) + ? meta->methodOffset() : 0; + for ( ; i >= limit; --i) { + QMetaMethod method = meta->method(i); + if (hasMethodAccess(method, i, inst->options) + && !isObjectProperty(m_object, method.signature())) { + QMetaMethod method = meta->method(i); + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(method.signature())); + member->native(nameId, i, + QScriptValue::QObjectMember + | METHOD_ID); + m_index = i; + return; + } + } + m_state = DynamicProperties; + m_index = inst->value->dynamicPropertyNames().count() - 1; + i = m_index; + // fall-through + } + + case DynamicProperties: { + QList<QByteArray> dpNames = inst->value->dynamicPropertyNames(); + for ( ; i >= 0; --i) { + if (!isObjectProperty(m_object, dpNames.at(i))) { + QByteArray name = dpNames.at(i); + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(name)); + member->native(nameId, i, + QScriptValue::QObjectMember + | DYNAPROPERTY_ID); + m_index = i; + return; + } + } + m_state = MetaProperties; + m_index = meta->propertyCount() - 1; + i = m_index; + // fall-through + } + + case MetaProperties: { + int limit = (inst->options & QScriptEngine::ExcludeSuperClassProperties) + ? meta->propertyOffset() : 0; + for ( ; i >= limit; --i) { + QMetaProperty prop = meta->property(i); + if (isEnumerableMetaProperty(prop, meta, i) + && !isObjectProperty(m_object, prop.name())) { + QScriptNameIdImpl *nameId = eng->nameId(QLatin1String(prop.name())); + member->native(nameId, i, flagsForMetaProperty(prop)); + m_index = i; + return; + } + } + } + + } // switch + + member->invalidate(); +} + +void ExtQObjectDataIterator::toFront() +{ + ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); + if (!inst->value) + return; + m_state = MetaProperties; + const QMetaObject *meta = inst->value->metaObject(); + m_index = (inst->options & QScriptEngine::ExcludeSuperClassProperties) + ? meta->propertyOffset() : 0; +} + +void ExtQObjectDataIterator::toBack() +{ + ExtQObject::Instance *inst = ExtQObject::Instance::get(m_object); + if (!inst->value) + return; + if (inst->options & QScriptEngine::SkipMethodsInEnumeration) { + m_state = DynamicProperties; + m_index = inst->value->dynamicPropertyNames().count(); + } else { + m_state = MetaMethods; + const QMetaObject *meta = inst->value->metaObject(); + m_index = meta->methodCount(); + } +} + +class ExtQObjectData: public QScriptClassData +{ +public: + ExtQObjectData(QScriptClassInfo *classInfo) + : m_classInfo(classInfo) + { + } + + virtual bool resolve(const QScriptValueImpl &object, QScriptNameIdImpl *nameId, + QScript::Member *member, QScriptValueImpl *, + QScript::AccessMode access) + { + ExtQObject::Instance *inst = ExtQObject::Instance::get(object, m_classInfo); + QObject *qobject = inst->value; + if (! qobject) { + // the object was deleted. We return true so we can + // throw an error in get()/put() + member->native(nameId, /*id=*/-1, /*flags=*/0); + return true; + } + + const QScriptEngine::QObjectWrapOptions &opt = inst->options; + const QMetaObject *meta = qobject->metaObject(); + + QScriptEnginePrivate *eng = object.engine(); + +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + QScriptMetaObject *metaCache = eng->cachedMetaObject(meta); + if (metaCache->findMember(nameId, member)) { + bool ignore = false; + switch (member->flags() & ID_MASK) { + case PROPERTY_ID: + ignore = (opt & QScriptEngine::ExcludeSuperClassProperties) + && (member->id() < meta->propertyOffset()); + break; + case METHOD_ID: + ignore = ((opt & QScriptEngine::ExcludeSuperClassMethods) + && (member->id() < meta->methodOffset())) + || ((opt & QScriptEngine::ExcludeDeleteLater) + && (member->id() == 2)); + break; + // we don't cache dynamic properties nor children, + // so no need to handle DYNAPROPERTY_ID and CHILD_ID + default: + break; + } + if (!ignore) + return true; + } +#endif + + QString memberName = eng->toString(nameId); + QByteArray name = memberName.toLatin1(); + + int index = -1; + + if (name.contains('(')) { + QByteArray normalized = QMetaObject::normalizedSignature(name); + if (-1 != (index = meta->indexOfMethod(normalized))) { + QMetaMethod method = meta->method(index); + if (hasMethodAccess(method, index, opt)) { + member->native(nameId, index, + QScriptValue::QObjectMember + | METHOD_ID); +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + metaCache->registerMember(nameId, *member); +#endif + if (!(opt & QScriptEngine::ExcludeSuperClassMethods) + || (index >= meta->methodOffset())) { + return true; + } + } + } + } + + index = meta->indexOfProperty(name); + if (index != -1) { + QMetaProperty prop = meta->property(index); + if (prop.isScriptable()) { + member->native(nameId, index, flagsForMetaProperty(prop)); +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + metaCache->registerMember(nameId, *member); +#endif + if (!(opt & QScriptEngine::ExcludeSuperClassProperties) + || (index >= meta->propertyOffset())) { + return true; + } + } + } + + index = qobject->dynamicPropertyNames().indexOf(name); + if (index != -1) { + member->native(nameId, index, + QScriptValue::QObjectMember + | DYNAPROPERTY_ID); + // not cached because it can be removed + return true; + } + + const int offset = (opt & QScriptEngine::ExcludeSuperClassMethods) + ? meta->methodOffset() : 0; + for (index = meta->methodCount() - 1; index >= offset; --index) { + QMetaMethod method = meta->method(index); + if (hasMethodAccess(method, index, opt) + && (methodName(method) == name)) { + member->native(nameId, index, + QScriptValue::QObjectMember + | METHOD_ID + | MAYBE_OVERLOADED); +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + metaCache->registerMember(nameId, *member); +#endif + return true; + } + } + + if (!(opt & QScriptEngine::ExcludeChildObjects)) { + QList<QObject*> children = qobject->children(); + for (index = 0; index < children.count(); ++index) { + QObject *child = children.at(index); + if (child->objectName() == memberName) { + member->native(nameId, index, + QScriptValue::ReadOnly + | QScriptValue::Undeletable + | QScriptValue::SkipInEnumeration + | CHILD_ID); + // not cached because it can be removed or change name + return true; + } + } + } + + if ((access & QScript::Write) && (opt & QScriptEngine::AutoCreateDynamicProperties)) { + member->native(nameId, -1, DYNAPROPERTY_ID); + return true; + } + + return false; + } + + virtual bool get(const QScriptValueImpl &obj, const QScript::Member &member, QScriptValueImpl *result) + { + if (! member.isNativeProperty()) + return false; + + QScriptEnginePrivate *eng = obj.engine(); + + ExtQObject::Instance *inst = ExtQObject::Instance::get(obj, m_classInfo); + QObject *qobject = inst->value; + if (!qobject) { + QScriptContextPrivate *ctx = eng->currentContext(); + *result = ctx->throwError( + QString::fromLatin1("cannot access member `%0' of deleted QObject") + .arg(member.nameId()->s)); + return true; + } + + switch (member.flags() & ID_MASK) { + case PROPERTY_ID: { + const QMetaObject *meta = qobject->metaObject(); + const int propertyIndex = member.id(); + QMetaProperty prop = meta->property(propertyIndex); + Q_ASSERT(prop.isScriptable()); + if (GeneratePropertyFunctions) { + QScriptValueImpl accessor; +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + QScriptMetaObject *metaCache = eng->cachedMetaObject(meta); + accessor = metaCache->findPropertyAccessor(propertyIndex); + if (!accessor.isValid()) { +#endif + accessor = eng->createFunction(new QtPropertyFunction(meta, propertyIndex)); +#ifndef Q_SCRIPT_NO_QMETAOBJECT_CACHE + metaCache->registerPropertyAccessor(propertyIndex, accessor); + } +#endif + *result = accessor; + } else { + QVariant v = prop.read(qobject); + *result = eng->valueFromVariant(v); + } + } break; + + case DYNAPROPERTY_ID: { + if (member.id() != -1) { + QVariant v = qobject->property(member.nameId()->s.toLatin1()); + *result = eng->valueFromVariant(v); + } else { + *result = eng->undefinedValue(); + } + } break; + + case METHOD_ID: { + QScript::Member m; + bool maybeOverloaded = (member.flags() & MAYBE_OVERLOADED) != 0; + *result = eng->createFunction(new QtFunction(obj, member.id(), + maybeOverloaded)); + // make it persist (otherwise Function.prototype.disconnect() would fail) + uint flags = QScriptValue::QObjectMember; + if (inst->options & QScriptEngine::SkipMethodsInEnumeration) + flags |= QScriptValue::SkipInEnumeration; + QScriptObject *instance = obj.objectValue(); + if (!instance->findMember(member.nameId(), &m)) + instance->createMember(member.nameId(), &m, flags); + instance->put(m, *result); + } break; + + case CHILD_ID: { + QObject *child = qobject->children().at(member.id()); + result->invalidate(); + QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; + eng->newQObject(result, child, QScriptEngine::QtOwnership, opt); + } break; + + } // switch + + return true; + } + + virtual bool put(QScriptValueImpl *object, const QScript::Member &member, const QScriptValueImpl &value) + { + if (! member.isNativeProperty() || ! member.isWritable()) + return false; + + ExtQObject::Instance *inst = ExtQObject::Instance::get(*object, m_classInfo); + QObject *qobject = inst->value; + if (!qobject) { + QScriptEnginePrivate *eng = object->engine(); + QScriptContextPrivate *ctx = eng->currentContext(); + ctx->throwError(QString::fromLatin1("cannot access member `%0' of deleted QObject") + .arg(member.nameId()->s)); + return true; + } + + switch (member.flags() & ID_MASK) { + case CHILD_ID: + return false; + + case METHOD_ID: { + QScript::Member m; + QScriptObject *instance = object->objectValue(); + if (!instance->findMember(member.nameId(), &m)) { + instance->createMember(member.nameId(), &m, + /*flags=*/0); + } + instance->put(m, value); + return true; + } + + case PROPERTY_ID: + if (GeneratePropertyFunctions) { + // we shouldn't get here, QScriptValueImpl::setProperty() messed up + Q_ASSERT_X(0, "put", "Q_PROPERTY access cannot be overridden"); + return false; + } else { + const QMetaObject *meta = qobject->metaObject(); + QMetaProperty prop = meta->property(member.id()); + Q_ASSERT(prop.isScriptable()); + QVariant v = variantFromValue(object->engine(), prop.userType(), value); + bool ok = prop.write(qobject, v); + return ok; + } + + case DYNAPROPERTY_ID: { + QVariant v = value.toVariant(); + return ! qobject->setProperty(member.nameId()->s.toLatin1(), v); + } + + } // switch + return false; + } + + virtual bool removeMember(const QScriptValueImpl &object, + const QScript::Member &member) + { + QObject *qobject = object.toQObject(); + if (!qobject || !member.isNativeProperty() || !member.isDeletable()) + return false; + + if ((member.flags() & ID_MASK) == DYNAPROPERTY_ID) { + qobject->setProperty(member.nameId()->s.toLatin1(), QVariant()); + return true; + } + + return false; + } + + virtual void mark(const QScriptValueImpl &, int) + { + } + + virtual QScriptClassDataIterator *newIterator(const QScriptValueImpl &object) + { + return new ExtQObjectDataIterator(object); + } + +private: + QScriptClassInfo *m_classInfo; +}; + +struct QObjectConnection +{ + int slotIndex; + QScriptValueImpl receiver; + QScriptValueImpl slot; + QScriptValueImpl senderWrapper; + + QObjectConnection(int i, const QScriptValueImpl &r, const QScriptValueImpl &s, + const QScriptValueImpl &sw) + : slotIndex(i), receiver(r), slot(s), senderWrapper(sw) {} + QObjectConnection() : slotIndex(-1) {} + + bool hasTarget(const QScriptValueImpl &r, const QScriptValueImpl &s) const + { + if (r.isObject() != receiver.isObject()) + return false; + if ((r.isObject() && receiver.isObject()) + && (r.objectValue() != receiver.objectValue())) { + return false; + } + return (s.objectValue() == slot.objectValue()); + } + + void mark(int generation) + { + if (senderWrapper.isValid() && !senderWrapper.isMarked(generation)) { + // see if the sender should be marked or not + ExtQObject::Instance *inst = ExtQObject::Instance::get(senderWrapper); + if ((inst->ownership == QScriptEngine::ScriptOwnership) + || ((inst->ownership == QScriptEngine::AutoOwnership) + && inst->value && !inst->value->parent())) { + senderWrapper.invalidate(); + } else { + senderWrapper.mark(generation); + } + } + if (receiver.isValid()) + receiver.mark(generation); + if (slot.isValid()) + slot.mark(generation); + } +}; + +class QObjectConnectionManager: public QObject +{ +public: + QObjectConnectionManager(); + ~QObjectConnectionManager(); + + bool addSignalHandler(QObject *sender, int signalIndex, + const QScriptValueImpl &receiver, + const QScriptValueImpl &slot, + const QScriptValueImpl &senderWrapper = QScriptValueImpl()); + bool removeSignalHandler( + QObject *sender, int signalIndex, + const QScriptValueImpl &receiver, + const QScriptValueImpl &slot); + + static const QMetaObject staticMetaObject; + virtual const QMetaObject *metaObject() const; + virtual void *qt_metacast(const char *); + virtual int qt_metacall(QMetaObject::Call, int, void **argv); + + void execute(int slotIndex, void **argv); + + void mark(int generation); + +private: + int m_slotCounter; + QVector<QVector<QObjectConnection> > connections; +}; + +} // ::QScript + + + +QScript::ExtQObject::ExtQObject(QScriptEnginePrivate *eng): + Ecma::Core(eng, QLatin1String("QObject"), QScriptClassInfo::QObjectType) +{ + newQObject(&publicPrototype, new QScript::QObjectPrototype(), + QScriptEngine::AutoOwnership, + QScriptEngine::ExcludeSuperClassMethods + | QScriptEngine::ExcludeSuperClassProperties + | QScriptEngine::ExcludeChildObjects); + + eng->newConstructor(&ctor, this, publicPrototype); + addPrototypeFunction(QLatin1String("toString"), method_toString, 0); + addPrototypeFunction(QLatin1String("findChild"), method_findChild, 1); + addPrototypeFunction(QLatin1String("findChildren"), method_findChildren, 1); + + classInfo()->setData(new QScript::ExtQObjectData(classInfo())); +} + +QScript::ExtQObject::~ExtQObject() +{ +} + +void QScript::ExtQObject::execute(QScriptContextPrivate *context) +{ + QScriptValueImpl tmp; + newQObject(&tmp, 0); + context->setReturnValue(tmp); +} + +void QScript::ExtQObject::newQObject(QScriptValueImpl *result, QObject *value, + QScriptEngine::ValueOwnership ownership, + const QScriptEngine::QObjectWrapOptions &options) +{ + Instance *instance; + if (!result->isValid()) { + engine()->newObject(result, publicPrototype, classInfo()); + instance = new Instance(); + result->setObjectData(instance); + } else { + Q_ASSERT(result->isObject()); + if (result->classInfo() != classInfo()) { + result->destroyObjectData(); + result->setClassInfo(classInfo()); + instance = new Instance(); + result->setObjectData(instance); + } else { + instance = Instance::get(*result); + } + } + instance->value = value; + instance->ownership = ownership; + instance->options = options; +} + +QScriptValueImpl QScript::ExtQObject::method_findChild(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) +{ + if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { + QObject *obj = instance->value; + QString name = context->argument(0).toString(); + QObject *child = qFindChild<QObject*>(obj, name); + QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; + QScriptValueImpl result; + eng->newQObject(&result, child, QScriptEngine::QtOwnership, opt); + return result; + } + return eng->undefinedValue(); +} + +QScriptValueImpl QScript::ExtQObject::method_findChildren(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) +{ + if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { + QObject *obj = instance->value; + QList<QObject*> found; + QScriptValueImpl arg = context->argument(0); +#ifndef QT_NO_REGEXP + if (arg.isRegExp()) { + QRegExp re = arg.toRegExp(); + found = qFindChildren<QObject*>(obj, re); + } else +#endif + { + QString name = arg.isUndefined() ? QString() : arg.toString(); + found = qFindChildren<QObject*>(obj, name); + } + QScriptValueImpl result = eng->newArray(found.size()); + QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; + for (int i = 0; i < found.size(); ++i) { + QScriptValueImpl value; + eng->newQObject(&value, found.at(i), QScriptEngine::QtOwnership, opt); + result.setProperty(i, value); + } + return result; + } + return eng->undefinedValue(); +} + +QScriptValueImpl QScript::ExtQObject::method_toString(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) +{ + if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { + QObject *obj = instance->value; + const QMetaObject *meta = obj ? obj->metaObject() : &QObject::staticMetaObject; + QString name = obj ? obj->objectName() : QString::fromUtf8("unnamed"); + + QString str = QString::fromUtf8("%0(name = \"%1\")") + .arg(QLatin1String(meta->className())).arg(name); + return QScriptValueImpl(eng, str); + } + return eng->undefinedValue(); +} + + + +static const uint qt_meta_data_QObjectConnectionManager[] = { + + // content: + 1, // revision + 0, // classname + 0, 0, // classinfo + 1, 10, // methods + 0, 0, // properties + 0, 0, // enums/sets + + // slots: signature, parameters, type, tag, flags + 35, 34, 34, 34, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_QObjectConnectionManager[] = { + "QScript::QObjectConnectionManager\0\0execute()\0" +}; + +const QMetaObject QScript::QObjectConnectionManager::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_QObjectConnectionManager, + qt_meta_data_QObjectConnectionManager, 0 } +}; + +const QMetaObject *QScript::QObjectConnectionManager::metaObject() const +{ + return &staticMetaObject; +} + +void *QScript::QObjectConnectionManager::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_QObjectConnectionManager)) + return static_cast<void*>(const_cast<QObjectConnectionManager*>(this)); + return QObject::qt_metacast(_clname); +} + +int QScript::QObjectConnectionManager::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + execute(_id, _a); + _id -= m_slotCounter; + } + return _id; +} + +void QScript::QObjectConnectionManager::execute(int slotIndex, void **argv) +{ + QScriptValueImpl receiver; + QScriptValueImpl slot; + QScriptValueImpl senderWrapper; + int signalIndex = -1; + for (int i = 0; i < connections.size(); ++i) { + const QVector<QObjectConnection> &cs = connections.at(i); + for (int j = 0; j < cs.size(); ++j) { + const QObjectConnection &c = cs.at(j); + if (c.slotIndex == slotIndex) { + receiver = c.receiver; + slot = c.slot; + senderWrapper = c.senderWrapper; + signalIndex = i; + break; + } + } + } + Q_ASSERT(slot.isValid()); + + QScriptEnginePrivate *eng = slot.engine(); + + if (eng->isCollecting()) { + // we can't do a script function call during GC, + // so we're forced to ignore this signal + return; + } + + QScriptFunction *fun = eng->convertToNativeFunction(slot); + if (fun == 0) { + // the signal handler has been GC'ed. This can only happen when + // a QObject is owned by the engine, the engine is destroyed, and + // there is a script function connected to the destroyed() signal + Q_ASSERT(signalIndex <= 1); // destroyed(QObject*) + return; + } + + const QMetaObject *meta = sender()->metaObject(); + const QMetaMethod method = meta->method(signalIndex); + + QList<QByteArray> parameterTypes = method.parameterTypes(); + int argc = parameterTypes.count(); + + QScriptValueImpl activation; + eng->newActivation(&activation); + QScriptObject *activation_data = activation.objectValue(); + activation_data->m_scope = slot.scope(); + + int formalCount = fun->formals.count(); + int mx = qMax(formalCount, argc); + activation_data->m_members.resize(mx + 1); + activation_data->m_values.resize(mx + 1); + for (int i = 0; i < mx; ++i) { + QScriptNameIdImpl *nameId; + if (i < formalCount) + nameId = fun->formals.at(i); + else + nameId = 0; + activation_data->m_members[i].object(nameId, i, + QScriptValue::Undeletable + | QScriptValue::SkipInEnumeration); + if (i < argc) { + int argType = QMetaType::type(parameterTypes.at(i)); + activation_data->m_values[i] = eng->create(argType, argv[i + 1]); + } else { + activation_data->m_values[i] = eng->undefinedValue(); + } + } + + QScriptValueImpl senderObject; + if (senderWrapper.isQObject()) { + senderObject = senderWrapper; + } else { + QScriptEngine::QObjectWrapOptions opt = QScriptEngine::PreferExistingWrapperObject; + eng->newQObject(&senderObject, sender(), QScriptEngine::QtOwnership, opt); + } + activation_data->m_members[mx].object(eng->idTable()->id___qt_sender__, mx, + QScriptValue::SkipInEnumeration); + activation_data->m_values[mx] = senderObject; + + QScriptValueImpl thisObject; + if (receiver.isObject()) + thisObject = receiver; + else + thisObject = eng->globalObject(); + + QScriptContextPrivate *context_data = eng->pushContext(); + context_data->m_activation = activation; + context_data->m_callee = slot; + context_data->m_thisObject = thisObject; + context_data->argc = argc; + context_data->args = const_cast<QScriptValueImpl*> (activation_data->m_values.constData()); + + fun->execute(context_data); + + eng->popContext(); + if (eng->hasUncaughtException()) + eng->emitSignalHandlerException(); +} + +QScript::QObjectConnectionManager::QObjectConnectionManager() + : m_slotCounter(0) +{ +} + +QScript::QObjectConnectionManager::~QObjectConnectionManager() +{ +} + +void QScript::QObjectConnectionManager::mark(int generation) +{ + for (int i = 0; i < connections.size(); ++i) { + QVector<QObjectConnection> &cs = connections[i]; + for (int j = 0; j < cs.size(); ++j) + cs[j].mark(generation); + } +} + +bool QScript::QObjectConnectionManager::addSignalHandler( + QObject *sender, int signalIndex, const QScriptValueImpl &receiver, + const QScriptValueImpl &function, const QScriptValueImpl &senderWrapper) +{ + if (connections.size() <= signalIndex) + connections.resize(signalIndex+1); + QVector<QObjectConnection> &cs = connections[signalIndex]; + int absSlotIndex = m_slotCounter + metaObject()->methodOffset(); + bool ok = QMetaObject::connect(sender, signalIndex, this, absSlotIndex); + if (ok) { + cs.append(QScript::QObjectConnection(m_slotCounter++, receiver, function, senderWrapper)); + QMetaMethod signal = sender->metaObject()->method(signalIndex); + QByteArray signalString; + signalString.append('2'); // signal code + signalString.append(signal.signature()); + static_cast<QScript::QObjectNotifyCaller*>(sender)->callConnectNotify(signalString); + } + return ok; +} + +bool QScript::QObjectConnectionManager::removeSignalHandler( + QObject *sender, int signalIndex, + const QScriptValueImpl &receiver, + const QScriptValueImpl &slot) +{ + if (connections.size() <= signalIndex) + return false; + QVector<QObjectConnection> &cs = connections[signalIndex]; + for (int i = 0; i < cs.size(); ++i) { + const QObjectConnection &c = cs.at(i); + if (c.hasTarget(receiver, slot)) { + int absSlotIndex = c.slotIndex + metaObject()->methodOffset(); + bool ok = QMetaObject::disconnect(sender, signalIndex, this, absSlotIndex); + if (ok) { + cs.remove(i); + QMetaMethod signal = sender->metaObject()->method(signalIndex); + QByteArray signalString; + signalString.append('2'); // signal code + signalString.append(signal.signature()); + static_cast<QScript::QObjectNotifyCaller*>(sender)->callDisconnectNotify(signalString); + } + return ok; + } + } + return false; +} + + + +QString QScript::QtPropertyFunction::functionName() const +{ + QMetaProperty prop = m_meta->property(m_index); + return QLatin1String(prop.name()); +} + +void QScript::QtPropertyFunction::execute(QScriptContextPrivate *context) +{ + context->calleeMetaIndex = m_index; + + QScriptEnginePrivate *eng_p = context->engine(); +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + eng_p->notifyFunctionEntry(context); +#endif + QScriptValueImpl result = eng_p->undefinedValue(); + + QScriptValueImpl object = context->thisObject(); + QObject *qobject = object.toQObject(); + while ((!qobject || (qobject->metaObject() != m_meta)) + && object.prototype().isObject()) { + object = object.prototype(); + qobject = object.toQObject(); + } + Q_ASSERT(qobject); + + QMetaProperty prop = m_meta->property(m_index); + Q_ASSERT(prop.isScriptable()); + if (context->argumentCount() == 0) { + // get + if (prop.isValid()) { + QScriptable *scriptable = scriptableFromQObject(qobject); + QScriptEngine *oldEngine = 0; + if (scriptable) { + oldEngine = QScriptablePrivate::get(scriptable)->engine; + QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(eng_p); + } + + QVariant v = prop.read(qobject); + + if (scriptable) + QScriptablePrivate::get(scriptable)->engine = oldEngine; + + result = eng_p->valueFromVariant(v); + } + } else { + // set + QVariant v = variantFromValue(eng_p, prop.userType(), context->argument(0)); + + QScriptable *scriptable = scriptableFromQObject(qobject); + QScriptEngine *oldEngine = 0; + if (scriptable) { + oldEngine = QScriptablePrivate::get(scriptable)->engine; + QScriptablePrivate::get(scriptable)->engine = QScriptEnginePrivate::get(eng_p); + } + + prop.write(qobject, v); + + if (scriptable) + QScriptablePrivate::get(scriptable)->engine = oldEngine; + + result = context->argument(0); + } + if (!eng_p->hasUncaughtException()) + context->m_result = result; +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + eng_p->notifyFunctionExit(context); +#endif +} + +QString QScript::QtFunction::functionName() const +{ + const QMetaObject *meta = metaObject(); + if (!meta) + return QString(); + QMetaMethod method = meta->method(m_initialIndex); + return QLatin1String(methodName(method)); +} + +void QScript::QtFunction::mark(QScriptEnginePrivate *engine, int generation) +{ + if (m_object.isValid()) + engine->markObject(m_object, generation); + QScriptFunction::mark(engine, generation); +} + +void QScript::QtFunction::execute(QScriptContextPrivate *context) +{ + QScriptEnginePrivate *eng_p = context->engine(); + QObject *qobj = qobject(); + if (!qobj) { + context->calleeMetaIndex = m_initialIndex; +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + eng_p->notifyFunctionEntry(context); +#endif + context->throwError(QLatin1String("cannot call function of deleted QObject")); +#ifndef Q_SCRIPT_NO_EVENT_NOTIFY + eng_p->notifyFunctionExit(context); +#endif + return; + } + + QScriptValueImpl result = eng_p->undefinedValue(); + + const QMetaObject *meta = qobj->metaObject(); + + QObject *thisQObject = context->thisObject().toQObject(); + if (!thisQObject) // ### TypeError + thisQObject = qobj; + + if (!meta->cast(thisQObject)) { +#if 0 + // ### find common superclass, see if initialIndex is + // in that class (or a superclass of that class), + // then it's still safe to execute it + funName = methodName(meta->method(m_initialIndex)); + context->throwError( + QString::fromUtf8("cannot execute %0: %1 does not inherit %2") + .arg(QLatin1String(funName)) + .arg(QLatin1String(thisQObject->metaObject()->className())) + .arg(QLatin1String(meta->className()))); + return; +#endif + // invoking a function in the prototype + thisQObject = qobj; + } + + callQtMethod(context, QMetaMethod::Method, thisQObject, + meta, m_initialIndex, m_maybeOverloaded); +} + +int QScript::QtFunction::mostGeneralMethod(QMetaMethod *out) const +{ + const QMetaObject *meta = metaObject(); + if (!meta) + return -1; + int index = m_initialIndex; + QMetaMethod method = meta->method(index); + if (maybeOverloaded() && (method.attributes() & QMetaMethod::Cloned)) { + // find the most general method + do { + method = meta->method(--index); + } while (method.attributes() & QMetaMethod::Cloned); + } + if (out) + *out = method; + return index; +} + +QList<int> QScript::QtFunction::overloadedIndexes() const +{ + if (!maybeOverloaded()) + return QList<int>(); + QList<int> result; + QString name = functionName(); + const QMetaObject *meta = metaObject(); + for (int index = mostGeneralMethod() - 1; index >= 0; --index) { + QString otherName = QString::fromLatin1(methodName(meta->method(index))); + if (otherName == name) + result.append(index); + } + return result; +} + +///////////////////////////////////////////////////////// + +namespace QScript +{ + +ExtQMetaObject::Instance *ExtQMetaObject::Instance::get(const QScriptValueImpl &object, + QScriptClassInfo *klass) +{ + if (! klass || klass == object.classInfo()) + return static_cast<Instance*> (object.objectData()); + + return 0; +} + +void ExtQMetaObject::Instance::execute(QScriptContextPrivate *context) +{ + if (ctor.isFunction()) { + QScriptValueImplList args; + for (int i = 0; i < context->argumentCount(); ++i) + args << context->argument(i); + QScriptEnginePrivate *eng = context->engine(); + context->m_result = eng->call(ctor, context->thisObject(), args, + context->isCalledAsConstructor()); + } else { + if (value->constructorCount() > 0) { + callQtMethod(context, QMetaMethod::Constructor, /*thisQObject=*/0, + value, value->constructorCount()-1, /*maybeOverloaded=*/true); + if (context->state() == QScriptContext::NormalState) { + ExtQObject::Instance *inst = ExtQObject::Instance::get(context->m_result); + Q_ASSERT(inst != 0); + inst->ownership = QScriptEngine::AutoOwnership; + context->m_result.setPrototype(prototype); + } + } else { + context->m_result = context->throwError( + QScriptContext::TypeError, + QString::fromUtf8("no constructor for %0") + .arg(QLatin1String(value->className()))); + } + } +} + +struct StaticQtMetaObject : public QObject +{ + static const QMetaObject *get() + { return &static_cast<StaticQtMetaObject*> (0)->staticQtMetaObject; } +}; + +class ExtQMetaObjectData: public QScriptClassData +{ +public: + ExtQMetaObjectData(QScriptEnginePrivate *, QScriptClassInfo *classInfo); + + virtual bool resolve(const QScriptValueImpl &object, QScriptNameIdImpl *nameId, + QScript::Member *member, QScriptValueImpl *base, + QScript::AccessMode access); + virtual bool get(const QScriptValueImpl &object, const QScript::Member &member, + QScriptValueImpl *result); + virtual bool put(QScriptValueImpl *object, const QScript::Member &member, + const QScriptValueImpl &value); + virtual void mark(const QScriptValueImpl &object, int generation); + +private: + QScriptClassInfo *m_classInfo; +}; + +ExtQMetaObjectData::ExtQMetaObjectData(QScriptEnginePrivate *, + QScriptClassInfo *classInfo) + : m_classInfo(classInfo) +{ +} + +bool ExtQMetaObjectData::resolve(const QScriptValueImpl &object, + QScriptNameIdImpl *nameId, + QScript::Member *member, + QScriptValueImpl *base, + QScript::AccessMode /*access*/) +{ + const QMetaObject *meta = object.toQMetaObject(); + if (!meta) + return false; + + QScriptEnginePrivate *eng_p = object.engine(); + if (eng_p->idTable()->id_prototype == nameId) { + // prototype property is a proxy to constructor's prototype property + member->native(nameId, /*id=*/0, QScriptValue::Undeletable); + return true; + } + + QByteArray name = eng_p->toString(nameId).toLatin1(); + + for (int i = 0; i < meta->enumeratorCount(); ++i) { + QMetaEnum e = meta->enumerator(i); + + for (int j = 0; j < e.keyCount(); ++j) { + const char *key = e.key(j); + + if (! qstrcmp (key, name.constData())) { + member->native(nameId, e.value(j), QScriptValue::ReadOnly); + *base = object; + return true; + } + } + } + + return false; +} + +bool ExtQMetaObjectData::get(const QScriptValueImpl &object, + const QScript::Member &member, + QScriptValueImpl *result) +{ + if (! member.isNativeProperty()) + return false; + + QScriptEnginePrivate *eng_p = object.engine(); + if (eng_p->idTable()->id_prototype == member.nameId()) { + ExtQMetaObject::Instance *inst = ExtQMetaObject::Instance::get(object, m_classInfo); + if (inst->ctor.isFunction()) + *result = inst->ctor.property(eng_p->idTable()->id_prototype); + else + *result = inst->prototype; + } else { + *result = QScriptValueImpl(member.id()); + } + return true; +} + +bool ExtQMetaObjectData::put(QScriptValueImpl *object, const Member &member, + const QScriptValueImpl &value) +{ + if (! member.isNativeProperty()) + return false; + + QScriptEnginePrivate *eng_p = object->engine(); + if (eng_p->idTable()->id_prototype == member.nameId()) { + ExtQMetaObject::Instance *inst = ExtQMetaObject::Instance::get(*object, m_classInfo); + if (inst->ctor.isFunction()) + inst->ctor.setProperty(eng_p->idTable()->id_prototype, value); + else + inst->prototype = value; + } + + return true; +} + +void ExtQMetaObjectData::mark(const QScriptValueImpl &object, int generation) +{ + ExtQMetaObject::Instance *inst = ExtQMetaObject::Instance::get(object, m_classInfo); + if (inst->ctor.isObject() || inst->ctor.isString()) + inst->ctor.mark(generation); +} + +} // namespace QScript + +QScript::ExtQMetaObject::ExtQMetaObject(QScriptEnginePrivate *eng) + : Ecma::Core(eng, QLatin1String("QMetaObject"), QScriptClassInfo::QMetaObjectType) +{ + newQMetaObject(&publicPrototype, QScript::StaticQtMetaObject::get()); + + eng->newConstructor(&ctor, this, publicPrototype); + addPrototypeFunction(QLatin1String("className"), method_className, 0); + + classInfo()->setData(new QScript::ExtQMetaObjectData(eng, classInfo())); +} + +QScript::ExtQMetaObject::~ExtQMetaObject() +{ +} + +void QScript::ExtQMetaObject::execute(QScriptContextPrivate *context) +{ + QScriptValueImpl tmp; + newQMetaObject(&tmp, 0); + context->setReturnValue(tmp); +} + +void QScript::ExtQMetaObject::newQMetaObject(QScriptValueImpl *result, const QMetaObject *value, + const QScriptValueImpl &ctor) +{ + Instance *instance = new Instance(); + instance->value = value; + if (ctor.isFunction()) { + instance->ctor = ctor; + } else { + instance->prototype = engine()->newObject(); + instance->prototype.setPrototype(engine()->qobjectConstructor->publicPrototype); + } + + engine()->newObject(result, publicPrototype, classInfo()); + result->setObjectData(instance); +} + +QScriptValueImpl QScript::ExtQMetaObject::method_className(QScriptContextPrivate *context, QScriptEnginePrivate *eng, QScriptClassInfo *classInfo) +{ + if (Instance *instance = Instance::get(context->thisObject(), classInfo)) { + return QScriptValueImpl(eng, QString::fromLatin1(instance->value->className())); + } + return eng->undefinedValue(); +} + +QScriptQObjectData::QScriptQObjectData() + : m_connectionManager(0) +{ +} + +QScriptQObjectData::~QScriptQObjectData() +{ + if (m_connectionManager) { + delete m_connectionManager; + m_connectionManager = 0; + } +} + +bool QScriptQObjectData::addSignalHandler(QObject *sender, + int signalIndex, + const QScriptValueImpl &receiver, + const QScriptValueImpl &slot, + const QScriptValueImpl &senderWrapper) +{ + if (!m_connectionManager) + m_connectionManager = new QScript::QObjectConnectionManager(); + return m_connectionManager->addSignalHandler( + sender, signalIndex, receiver, slot, senderWrapper); +} + +bool QScriptQObjectData::removeSignalHandler(QObject *sender, + int signalIndex, + const QScriptValueImpl &receiver, + const QScriptValueImpl &slot) +{ + if (!m_connectionManager) + return false; + return m_connectionManager->removeSignalHandler( + sender, signalIndex, receiver, slot); +} + +bool QScriptQObjectData::findWrapper(QScriptEngine::ValueOwnership ownership, + const QScriptEngine::QObjectWrapOptions &options, + QScriptValueImpl *out) +{ + for (int i = 0; i < wrappers.size(); ++i) { + const QScriptQObjectWrapperInfo &info = wrappers.at(i); + if ((info.ownership == ownership) && (info.options == options)) { + *out = info.object; + return true; + } + } + return false; +} + +void QScriptQObjectData::registerWrapper(const QScriptValueImpl &wrapper, + QScriptEngine::ValueOwnership ownership, + const QScriptEngine::QObjectWrapOptions &options) +{ + wrappers.append(QScriptQObjectWrapperInfo(wrapper, ownership, options)); +} + +void QScriptQObjectData::mark(int generation) +{ + if (m_connectionManager) + m_connectionManager->mark(generation); + + { + QList<QScriptQObjectWrapperInfo>::iterator it; + for (it = wrappers.begin(); it != wrappers.end(); ) { + const QScriptQObjectWrapperInfo &info = *it; + if (info.object.isMarked(generation)) { + ++it; + } else { + it = wrappers.erase(it); + } + } + } +} + +QT_END_NAMESPACE + +#include "qscriptextqobject.moc" + +#endif // QT_NO_SCRIPT |