/* * Copyright (C) 2006 Trolltech ASA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "config.h" #include "qt_runtime.h" #include "qt_instance.h" #include "object.h" #include "array_instance.h" #include "date_object.h" #include "DateMath.h" #include "regexp_object.h" #include #include #include #include "PropertyNameArray.h" #include "qmetatype.h" #include "qmetaobject.h" #include "qobject.h" #include "qstringlist.h" #include "qdebug.h" #include "qvarlengtharray.h" #include "qdatetime.h" #include // QtScript has these Q_DECLARE_METATYPE(QObjectList); Q_DECLARE_METATYPE(QList); Q_DECLARE_METATYPE(QVariant); namespace KJS { namespace Bindings { // Debugging //#define QTWK_RUNTIME_CONVERSION_DEBUG //#define QTWK_RUNTIME_MATCH_DEBUG class QWKNoDebug { public: inline QWKNoDebug(){} inline ~QWKNoDebug(){} template inline QWKNoDebug &operator<<(const T &) { return *this; } }; #ifdef QTWK_RUNTIME_CONVERSION_DEBUG #define qConvDebug() qDebug() #else #define qConvDebug() QWKNoDebug() #endif #ifdef QTWK_RUNTIME_MATCH_DEBUG #define qMatchDebug() qDebug() #else #define qMatchDebug() QWKNoDebug() #endif typedef enum { Variant, Number, Boolean, String, Date, RegExp, Array, QObj, Object, Null } JSRealType; static JSRealType valueRealType(ExecState* exec, JSValue* val) { if (val->isNumber()) return Number; else if (val->isString()) return String; else if (val->isBoolean()) return Boolean; else if (val->isNull()) return Null; else if (val->isObject()) { JSObject *object = val->toObject(exec); if (object->inherits(&ArrayInstance::info)) return Array; else if (object->inherits(&DateInstance::info)) return Date; else if (object->inherits(&RegExpImp::info)) return RegExp; else if (object->inherits(&RuntimeObjectImp::info)) return QObj; return Object; } return String; // I don't know. } QVariant convertValueToQVariant(ExecState* exec, JSValue* value, QMetaType::Type hint, int *distance) { // check magic pointer values before dereferencing value if (value == jsNaN() || value == jsUndefined()) { if (distance) *distance = -1; return QVariant(); } JSLock lock; JSRealType type = valueRealType(exec, value); if (hint == QMetaType::Void) { switch(type) { case Number: hint = QMetaType::Double; break; case Boolean: hint = QMetaType::Bool; break; case String: default: hint = QMetaType::QString; break; case Date: hint = QMetaType::QDateTime; break; case RegExp: hint = QMetaType::QRegExp; break; case QObj: hint = QMetaType::QObjectStar; break; case Array: hint = QMetaType::QVariantList; break; } } if (value == jsNull() && hint != QMetaType::QObjectStar && hint != QMetaType::VoidStar) { if (distance) *distance = -1; return QVariant(); } QVariant ret; int dist = -1; switch (hint) { case QMetaType::Bool: ret = QVariant(value->toBoolean(exec)); if (type == Boolean) dist = 0; else dist = 10; break; case QMetaType::Int: case QMetaType::UInt: case QMetaType::Long: case QMetaType::ULong: case QMetaType::LongLong: case QMetaType::ULongLong: case QMetaType::Short: case QMetaType::UShort: case QMetaType::Float: case QMetaType::Double: ret = QVariant(value->toNumber(exec)); ret.convert((QVariant::Type)hint); if (type == Number) { switch (hint) { case QMetaType::Double: dist = 0; break; case QMetaType::Float: dist = 1; break; case QMetaType::LongLong: case QMetaType::ULongLong: dist = 2; break; case QMetaType::Long: case QMetaType::ULong: dist = 3; break; case QMetaType::Int: case QMetaType::UInt: dist = 4; break; case QMetaType::Short: case QMetaType::UShort: dist = 5; break; break; default: dist = 10; break; } } else { dist = 10; } break; case QMetaType::QChar: if (type == Number || type == Boolean) { ret = QVariant(QChar((ushort)value->toNumber(exec))); if (type == Boolean) dist = 3; else dist = 6; } else { UString str = value->toString(exec); ret = QVariant(QChar(str.size() ? *(const ushort*)str.rep()->data() : 0)); if (type == String) dist = 3; else dist = 10; } break; case QMetaType::QString: { UString ustring = value->toString(exec); ret = QVariant(QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size())); if (type == String) dist = 0; else dist = 10; break; } case QMetaType::QVariantMap: if (type == Object || type == Array) { // Enumerate the contents of the object JSObject* object = value->toObject(exec); PropertyNameArray properties; object->getPropertyNames(exec, properties); PropertyNameArray::const_iterator it = properties.begin(); QVariantMap result; int objdist = 0; while(it != properties.end()) { if (object->propertyIsEnumerable(exec, *it)) { JSValue* val = object->get(exec, *it); QVariant v = convertValueToQVariant(exec, val, QMetaType::Void, &objdist); if (objdist >= 0) { UString ustring = (*it).ustring(); QString id = QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()); result.insert(id, v); } } ++it; } dist = 1; ret = QVariant(result); } break; case QMetaType::QVariantList: if (type == Array) { JSObject* object = value->toObject(exec); ArrayInstance* array = static_cast(object); QVariantList result; int len = array->getLength(); int objdist = 0; for (int i = 0; i < len; ++i) { JSValue *val = array->getItem(i); result.append(convertValueToQVariant(exec, val, QMetaType::Void, &objdist)); if (objdist == -1) break; // Failed converting a list entry, so fail the array } if (objdist != -1) { dist = 5; ret = QVariant(result); } } else { // Make a single length array QVariantList result; int objdist; result.append(convertValueToQVariant(exec, value, QMetaType::Void, &objdist)); if (objdist != -1) { ret = QVariant(result); dist = 10; } } break; case QMetaType::QStringList: { if (type == Array) { JSObject* object = value->toObject(exec); ArrayInstance* array = static_cast(object); QStringList result; int len = array->getLength(); for (int i = 0; i < len; ++i) { JSValue* val = array->getItem(i); UString ustring = val->toString(exec); QString qstring = QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()); result.append(qstring); } dist = 5; ret = QVariant(result); } else { // Make a single length array UString ustring = value->toString(exec); QString qstring = QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()); QStringList result; result.append(qstring); ret = QVariant(result); dist = 10; } break; } case QMetaType::QByteArray: { UString ustring = value->toString(exec); ret = QVariant(QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()).toLatin1()); if (type == String) dist = 5; else dist = 10; break; } case QMetaType::QDateTime: case QMetaType::QDate: case QMetaType::QTime: if (type == Date) { JSObject* object = value->toObject(exec); DateInstance* date = static_cast(object); GregorianDateTime gdt; date->getUTCTime(gdt); if (hint == QMetaType::QDateTime) { ret = QDateTime(QDate(gdt.year + 1900, gdt.month + 1, gdt.monthDay), QTime(gdt.hour, gdt.minute, gdt.second), Qt::UTC); dist = 0; } else if (hint == QMetaType::QDate) { ret = QDate(gdt.year + 1900, gdt.month + 1, gdt.monthDay); dist = 1; } else { ret = QTime(gdt.hour + 1900, gdt.minute, gdt.second); dist = 2; } } else if (type == Number) { double b = value->toNumber(exec); GregorianDateTime gdt; msToGregorianDateTime(b, true, gdt); if (hint == QMetaType::QDateTime) { ret = QDateTime(QDate(gdt.year + 1900, gdt.month + 1, gdt.monthDay), QTime(gdt.hour, gdt.minute, gdt.second), Qt::UTC); dist = 6; } else if (hint == QMetaType::QDate) { ret = QDate(gdt.year + 1900, gdt.month + 1, gdt.monthDay); dist = 8; } else { ret = QTime(gdt.hour, gdt.minute, gdt.second); dist = 10; } } else if (type == String) { UString ustring = value->toString(exec); QString qstring = QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()); if (hint == QMetaType::QDateTime) { QDateTime dt = QDateTime::fromString(qstring, Qt::ISODate); if (!dt.isValid()) dt = QDateTime::fromString(qstring, Qt::TextDate); if (!dt.isValid()) dt = QDateTime::fromString(qstring, Qt::SystemLocaleDate); if (!dt.isValid()) dt = QDateTime::fromString(qstring, Qt::LocaleDate); if (dt.isValid()) { ret = dt; dist = 2; } } else if (hint == QMetaType::QDate) { QDate dt = QDate::fromString(qstring, Qt::ISODate); if (!dt.isValid()) dt = QDate::fromString(qstring, Qt::TextDate); if (!dt.isValid()) dt = QDate::fromString(qstring, Qt::SystemLocaleDate); if (!dt.isValid()) dt = QDate::fromString(qstring, Qt::LocaleDate); if (dt.isValid()) { ret = dt; dist = 3; } } else { QTime dt = QTime::fromString(qstring, Qt::ISODate); if (!dt.isValid()) dt = QTime::fromString(qstring, Qt::TextDate); if (!dt.isValid()) dt = QTime::fromString(qstring, Qt::SystemLocaleDate); if (!dt.isValid()) dt = QTime::fromString(qstring, Qt::LocaleDate); if (dt.isValid()) { ret = dt; dist = 3; } } } break; case QMetaType::QRegExp: if (type == RegExp) { /* JSObject *object = value->toObject(exec); RegExpImp *re = static_cast(object); */ // Attempt to convert.. a bit risky UString ustring = value->toString(exec); QString qstring = QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()); // this is of the form '/xxxxxx/i' int firstSlash = qstring.indexOf('/'); int lastSlash = qstring.lastIndexOf('/'); if (firstSlash >=0 && lastSlash > firstSlash) { QRegExp realRe; realRe.setPattern(qstring.mid(firstSlash + 1, lastSlash - firstSlash - 1)); if (qstring.mid(lastSlash + 1).contains('i')) realRe.setCaseSensitivity(Qt::CaseInsensitive); ret = qVariantFromValue(realRe); dist = 0; } else { qConvDebug() << "couldn't parse a JS regexp"; } } else if (type == String) { UString ustring = value->toString(exec); QString qstring = QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size()); QRegExp re(qstring); if (re.isValid()) { ret = qVariantFromValue(re); dist = 10; } } break; case QMetaType::QObjectStar: if (type == QObj) { JSObject* object = value->toObject(exec); QtInstance* qtinst = static_cast(Instance::getInstance(object, Instance::QtLanguage)); if (qtinst) { if (qtinst->getObject()) { qConvDebug() << "found instance, with object:" << (void*) qtinst->getObject(); ret = qVariantFromValue(qtinst->getObject()); qConvDebug() << ret; dist = 0; } else { qConvDebug() << "can't convert deleted qobject"; } } else { qConvDebug() << "wasn't a qtinstance"; } } else if (type == Null) { QObject* nullobj = 0; ret = qVariantFromValue(nullobj); dist = 0; } else { qConvDebug() << "previous type was not an object:" << type; } break; case QMetaType::VoidStar: if (type == QObj) { JSObject* object = value->toObject(exec); QtInstance* qtinst = static_cast(Instance::getInstance(object, Instance::QtLanguage)); if (qtinst) { if (qtinst->getObject()) { qConvDebug() << "found instance, with object:" << (void*) qtinst->getObject(); ret = qVariantFromValue((void *)qtinst->getObject()); qConvDebug() << ret; dist = 0; } else { qConvDebug() << "can't convert deleted qobject"; } } else { qConvDebug() << "wasn't a qtinstance"; } } else if (type == Null) { ret = qVariantFromValue((void*)0); dist = 0; } else if (type == Number) { // I don't think that converting a double to a pointer is a wise // move. Except maybe 0. qConvDebug() << "got number for void * - not converting, seems unsafe:" << value->toNumber(exec); } else { qConvDebug() << "void* - unhandled type" << type; } break; default: // Non const type ids if (hint == (QMetaType::Type) qMetaTypeId()) { if (type == Array) { JSObject* object = value->toObject(exec); ArrayInstance* array = static_cast(object); QObjectList result; int len = array->getLength(); for (int i = 0; i < len; ++i) { JSValue *val = array->getItem(i); int itemdist = -1; QVariant item = convertValueToQVariant(exec, val, QMetaType::QObjectStar, &itemdist); if (itemdist >= 0) result.append(item.value()); else break; } // If we didn't fail conversion if (result.count() == len) { dist = 5; ret = QVariant::fromValue(result); } else { qConvDebug() << "type conversion failed (wanted" << len << ", got " << result.count() << ")"; } } else { // Make a single length array QObjectList result; int itemdist = -1; QVariant item = convertValueToQVariant(exec, value, QMetaType::QObjectStar, &itemdist); if (itemdist >= 0) { result.append(item.value()); dist = 10; ret = QVariant::fromValue(result); } } break; } else if (hint == (QMetaType::Type) qMetaTypeId >()) { if (type == Array) { JSObject* object = value->toObject(exec); ArrayInstance* array = static_cast(object); QList result; int len = array->getLength(); for (int i = 0; i < len; ++i) { JSValue* val = array->getItem(i); int itemdist = -1; QVariant item = convertValueToQVariant(exec, val, QMetaType::Int, &itemdist); if (itemdist >= 0) result.append(item.value()); else break; } // If we didn't fail conversion if (result.count() == len) { dist = 5; ret = QVariant::fromValue(result); } else { qConvDebug() << "type conversion failed (wanted" << len << ", got " << result.count() << ")"; } } else { // Make a single length array QList result; int itemdist = -1; QVariant item = convertValueToQVariant(exec, value, QMetaType::Int, &itemdist); if (itemdist >= 0) { result.append(item.value()); dist = 10; ret = QVariant::fromValue(result); } } break; } else if (hint == (QMetaType::Type) qMetaTypeId()) { // Well.. we can do anything... just recurse with the autodetect flag ret = convertValueToQVariant(exec, value, QMetaType::Void, distance); dist = 10; break; } dist = 10; break; } if (!ret.isValid()) dist = -1; if (distance) *distance = dist; return ret; } JSValue* convertQVariantToValue(ExecState* exec, PassRefPtr root, const QVariant& variant) { // Variants with QObject * can be isNull but not a null pointer // An empty QString variant is also null QMetaType::Type type = (QMetaType::Type) variant.userType(); if (variant.isNull() && type != QMetaType::QObjectStar && type != QMetaType::VoidStar && type != QMetaType::QWidgetStar && type != QMetaType::QString) { return jsNull(); } JSLock lock; if (type == QMetaType::Bool) return jsBoolean(variant.toBool()); if (type == QMetaType::Int || type == QMetaType::UInt || type == QMetaType::Long || type == QMetaType::ULong || type == QMetaType::LongLong || type == QMetaType::ULongLong || type == QMetaType::Short || type == QMetaType::UShort || type == QMetaType::Float || type == QMetaType::Double) return jsNumber(variant.toDouble()); if (type == QMetaType::QRegExp) { QRegExp re = variant.value(); if (re.isValid()) { RegExpObjectImp* regExpObj = static_cast(exec->lexicalGlobalObject()->regExpConstructor()); List args; UString uflags; if (re.caseSensitivity() == Qt::CaseInsensitive) uflags = "i"; // ### Can't do g or m UString ustring((KJS::UChar*)re.pattern().utf16(), re.pattern().length()); args.append(jsString(ustring)); args.append(jsString(uflags)); return regExpObj->construct(exec, args); } } if (type == QMetaType::QDateTime || type == QMetaType::QDate || type == QMetaType::QTime) { DateObjectImp *dateObj = static_cast(exec->lexicalGlobalObject()->dateConstructor()); List args; QDate date = QDate::currentDate(); QTime time(0,0,0); // midnight if (type == QMetaType::QDate) date = variant.value(); else if (type == QMetaType::QTime) time = variant.value(); else { QDateTime dt = variant.value().toLocalTime(); date = dt.date(); time = dt.time(); } // Dates specified this way are in local time (we convert DateTimes above) args.append(jsNumber(date.year())); args.append(jsNumber(date.month() - 1)); args.append(jsNumber(date.day())); args.append(jsNumber(time.hour())); args.append(jsNumber(time.minute())); args.append(jsNumber(time.second())); args.append(jsNumber(time.msec())); return dateObj->construct(exec, args); } if (type == QMetaType::QByteArray) { QByteArray ba = variant.value(); UString ustring(ba.constData()); return jsString(ustring); } if (type == QMetaType::QObjectStar || type == QMetaType::QWidgetStar) { QObject* obj = variant.value(); return Instance::createRuntimeObject(Instance::QtLanguage, obj, root); } if (type == QMetaType::QVariantMap) { // create a new object, and stuff properties into it JSObject* ret = new JSObject(exec->lexicalGlobalObject()->objectPrototype()); QVariantMap map = variant.value(); QVariantMap::const_iterator i = map.constBegin(); while (i != map.constEnd()) { QString s = i.key(); JSValue* val = convertQVariantToValue(exec, root, i.value()); if (val) ret->put(exec, Identifier((const UChar *)s.constData(), s.length()), val); // ### error case? ++i; } return ret; } // List types if (type == QMetaType::QVariantList) { QVariantList vl = variant.toList(); return new RuntimeArray(exec, new QtArray(vl, QMetaType::Void, root)); } else if (type == QMetaType::QStringList) { QStringList sl = variant.value(); return new RuntimeArray(exec, new QtArray(sl, QMetaType::QString, root)); } else if (type == (QMetaType::Type) qMetaTypeId()) { QObjectList ol= variant.value(); return new RuntimeArray(exec, new QtArray(ol, QMetaType::QObjectStar, root)); } else if (type == (QMetaType::Type)qMetaTypeId >()) { QList il= variant.value >(); return new RuntimeArray(exec, new QtArray(il, QMetaType::Int, root)); } if (type == (QMetaType::Type)qMetaTypeId()) { QVariant real = variant.value(); qConvDebug() << "real variant is:" << real; return convertQVariantToValue(exec, root, real); } qConvDebug() << "fallback path for" << variant << variant.userType(); QString string = variant.toString(); UString ustring((KJS::UChar*)string.utf16(), string.length()); return jsString(ustring); } // =============== // Qt-like macros #define QW_D(Class) Class##Data* d = d_func() #define QW_DS(Class,Instance) Class##Data* d = Instance->d_func() QtRuntimeMethod::QtRuntimeMethod(QtRuntimeMethodData* dd, ExecState *exec, const Identifier &ident, PassRefPtr inst) : InternalFunctionImp (static_cast(exec->lexicalGlobalObject()->functionPrototype()), ident) , d_ptr(dd) { QW_D(QtRuntimeMethod); d->m_instance = inst; } QtRuntimeMethod::~QtRuntimeMethod() { delete d_ptr; } CodeType QtRuntimeMethod::codeType() const { return FunctionCode; } Completion QtRuntimeMethod::execute(ExecState*) { return Completion(Normal, jsUndefined()); } // =============== QtRuntimeMethodData::~QtRuntimeMethodData() { } QtRuntimeMetaMethodData::~QtRuntimeMetaMethodData() { } QtRuntimeConnectionMethodData::~QtRuntimeConnectionMethodData() { } // =============== // Type conversion metadata (from QtScript originally) class QtMethodMatchType { public: enum Kind { Invalid, Variant, MetaType, Unresolved, MetaEnum }; QtMethodMatchType() : m_kind(Invalid) { } Kind kind() const { return m_kind; } QMetaType::Type typeId() const; bool isValid() const { return (m_kind != Invalid); } bool isVariant() const { return (m_kind == Variant); } bool isMetaType() const { return (m_kind == MetaType); } bool isUnresolved() const { return (m_kind == Unresolved); } bool isMetaEnum() const { return (m_kind == MetaEnum); } QByteArray name() const; int enumeratorIndex() const { Q_ASSERT(isMetaEnum()); return m_typeId; } static QtMethodMatchType variant() { return QtMethodMatchType(Variant); } static QtMethodMatchType metaType(int typeId, const QByteArray &name) { return QtMethodMatchType(MetaType, typeId, name); } static QtMethodMatchType metaEnum(int enumIndex, const QByteArray &name) { return QtMethodMatchType(MetaEnum, enumIndex, name); } static QtMethodMatchType unresolved(const QByteArray &name) { return QtMethodMatchType(Unresolved, /*typeId=*/0, name); } private: QtMethodMatchType(Kind kind, int typeId = 0, const QByteArray &name = QByteArray()) : m_kind(kind), m_typeId(typeId), m_name(name) { } Kind m_kind; int m_typeId; QByteArray m_name; }; QMetaType::Type QtMethodMatchType::typeId() const { if (isVariant()) return (QMetaType::Type) QMetaType::type("QVariant"); return (QMetaType::Type) (isMetaEnum() ? QMetaType::Int : m_typeId); } QByteArray QtMethodMatchType::name() const { if (!m_name.isEmpty()) return m_name; else if (m_kind == Variant) return "QVariant"; return QByteArray(); } struct QtMethodMatchData { int matchDistance; int index; QVector types; QVarLengthArray args; QtMethodMatchData(int dist, int idx, QVector typs, const QVarLengthArray &as) : matchDistance(dist), index(idx), types(typs), args(as) { } QtMethodMatchData() : index(-1) { } bool isValid() const { return (index != -1); } int firstUnresolvedIndex() const { for (int i=0; i < types.count(); i++) { if (types.at(i).isUnresolved()) return i; } return -1; } }; static int indexOfMetaEnum(const QMetaObject *meta, const QByteArray &str) { QByteArray scope; QByteArray name; int scopeIdx = str.indexOf("::"); 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; } // Helper function for resolving methods // Largely based on code in QtScript for compatibility reasons static int findMethodIndex(ExecState* exec, const QMetaObject* meta, const QByteArray& signature, bool allowPrivate, const List& jsArgs, QVarLengthArray &vars, void** vvars, JSObject **pError) { QList matchingIndices; bool overloads = !signature.contains('('); int count = meta->methodCount(); for (int i = count - 1; i >= 0; --i) { const QMetaMethod m = meta->method(i); // Don't choose private methods if (m.access() == QMetaMethod::Private && !allowPrivate) continue; // try and find all matching named methods if (m.signature() == signature) matchingIndices.append(i); else if (overloads) { QByteArray rawsignature = m.signature(); rawsignature.truncate(rawsignature.indexOf('(')); if (rawsignature == signature) matchingIndices.append(i); } } int chosenIndex = -1; *pError = 0; QVector chosenTypes; QVarLengthArray args; QVector candidates; QVector unresolved; QVector tooFewArgs; QVector conversionFailed; foreach(int index, matchingIndices) { QMetaMethod method = meta->method(index); QVector types; bool unresolvedTypes = false; // resolve return type QByteArray returnTypeName = method.typeName(); int rtype = QMetaType::type(returnTypeName); if ((rtype == 0) && !returnTypeName.isEmpty()) { if (returnTypeName == "QVariant") { types.append(QtMethodMatchType::variant()); } else if (returnTypeName.endsWith('*')) { types.append(QtMethodMatchType::metaType(QMetaType::VoidStar, returnTypeName)); } else { int enumIndex = indexOfMetaEnum(meta, returnTypeName); if (enumIndex != -1) types.append(QtMethodMatchType::metaEnum(enumIndex, returnTypeName)); else { unresolvedTypes = true; types.append(QtMethodMatchType::unresolved(returnTypeName)); } } } else { if (returnTypeName == "QVariant") types.append(QtMethodMatchType::variant()); else types.append(QtMethodMatchType::metaType(rtype, returnTypeName)); } // resolve argument types QList 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(QtMethodMatchType::variant()); } else { int enumIndex = indexOfMetaEnum(meta, argTypeName); if (enumIndex != -1) types.append(QtMethodMatchType::metaEnum(enumIndex, argTypeName)); else { unresolvedTypes = true; types.append(QtMethodMatchType::unresolved(argTypeName)); } } } else { if (argTypeName == "QVariant") types.append(QtMethodMatchType::variant()); else types.append(QtMethodMatchType::metaType(atype, argTypeName)); } } if (jsArgs.size() < (types.count() - 1)) { qMatchDebug() << "Match:too few args for" << method.signature(); tooFewArgs.append(index); continue; } if (unresolvedTypes) { qMatchDebug() << "Match:unresolved arg types for" << method.signature(); // remember it so we can give an error message later, if necessary unresolved.append(QtMethodMatchData(/*matchDistance=*/INT_MAX, index, types, QVarLengthArray())); continue; } // Now convert arguments if (args.count() != types.count()) args.resize(types.count()); QtMethodMatchType retType = types[0]; args[0] = QVariant(retType.typeId(), (void *)0); // the return value bool converted = true; int matchDistance = 0; for (int i = 0; converted && i < types.count() - 1; ++i) { JSValue* arg = i < jsArgs.size() ? jsArgs[i] : jsUndefined(); int argdistance = -1; QVariant v = convertValueToQVariant(exec, arg, types.at(i+1).typeId(), &argdistance); if (argdistance >= 0) { matchDistance += argdistance; args[i+1] = v; } else { qMatchDebug() << "failed to convert argument " << i << "type" << types.at(i+1).typeId() << QMetaType::typeName(types.at(i+1).typeId()); converted = false; } } qMatchDebug() << "Match: " << method.signature() << (converted ? "converted":"failed to convert") << "distance " << matchDistance; if (converted) { if ((jsArgs.size() == types.count() - 1) && (matchDistance == 0)) { // perfect match, use this one chosenIndex = index; break; } else { QtMethodMatchData metaArgs(matchDistance, index, types, args); if (candidates.isEmpty()) { candidates.append(metaArgs); } else { QtMethodMatchData 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 { conversionFailed.append(index); } if (!overloads) break; } if (chosenIndex == -1 && candidates.count() == 0) { // No valid functions at all - format an error message if (!conversionFailed.isEmpty()) { QString message = QString::fromLatin1("incompatible type of argument(s) in call to %0(); candidates were\n") .arg(QLatin1String(signature)); for (int i = 0; i < conversionFailed.size(); ++i) { if (i > 0) message += QLatin1String("\n"); QMetaMethod mtd = meta->method(conversionFailed.at(i)); message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); } *pError = throwError(exec, TypeError, message.toLatin1().constData()); } else if (!unresolved.isEmpty()) { QtMethodMatchData argsInstance = unresolved.first(); int unresolvedIndex = argsInstance.firstUnresolvedIndex(); Q_ASSERT(unresolvedIndex != -1); QtMethodMatchType unresolvedType = argsInstance.types.at(unresolvedIndex); QString message = QString::fromLatin1("cannot call %0(): unknown type `%1'") .arg(QString::fromLatin1(signature)) .arg(QLatin1String(unresolvedType.name())); *pError = throwError(exec, TypeError, message.toLatin1().constData()); } else { QString message = QString::fromLatin1("too few arguments in call to %0(); candidates are\n") .arg(QLatin1String(signature)); for (int i = 0; i < tooFewArgs.size(); ++i) { if (i > 0) message += QLatin1String("\n"); QMetaMethod mtd = meta->method(tooFewArgs.at(i)); message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); } *pError = throwError(exec, SyntaxError, message.toLatin1().constData()); } } if (chosenIndex == -1 && candidates.count() > 0) { QtMethodMatchData 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(signature)); for (int i = 0; i < candidates.size(); ++i) { if (i > 0) message += QLatin1String("\n"); QMetaMethod mtd = meta->method(candidates.at(i).index); message += QString::fromLatin1(" %0").arg(QString::fromLatin1(mtd.signature())); } *pError = throwError(exec, TypeError, message.toLatin1().constData()); } else { chosenIndex = metaArgs.index; args = metaArgs.args; } } if (chosenIndex != -1) { /* Copy the stuff over */ int i; vars.resize(args.count()); for (i=0; i < args.count(); i++) { vars[i] = args[i]; vvars[i] = vars[i].data(); } } return chosenIndex; } // Signals are not fuzzy matched as much as methods static int findSignalIndex(const QMetaObject* meta, int initialIndex, QByteArray signature) { int index = initialIndex; QMetaMethod method = meta->method(index); bool overloads = !signature.contains('('); if (overloads && (method.attributes() & QMetaMethod::Cloned)) { // find the most general method do { method = meta->method(--index); } while (method.attributes() & QMetaMethod::Cloned); } return index; } QtRuntimeMetaMethod::QtRuntimeMetaMethod(ExecState* exec, const Identifier& ident, PassRefPtr inst, int index, const QByteArray& signature, bool allowPrivate) : QtRuntimeMethod (new QtRuntimeMetaMethodData(), exec, ident, inst) { QW_D(QtRuntimeMetaMethod); d->m_signature = signature; d->m_index = index; d->m_connect = 0; d->m_disconnect = 0; d->m_allowPrivate = allowPrivate; } void QtRuntimeMetaMethod::mark() { QtRuntimeMethod::mark(); QW_D(QtRuntimeMetaMethod); if (d->m_connect) d->m_connect->mark(); if (d->m_disconnect) d->m_disconnect->mark(); } JSValue* QtRuntimeMetaMethod::callAsFunction(ExecState* exec, JSObject*, const List& args) { QW_D(QtRuntimeMetaMethod); // We're limited to 10 args if (args.size() > 10) return jsUndefined(); // We have to pick a method that matches.. JSLock lock; QObject *obj = d->m_instance->getObject(); if (obj) { QVarLengthArray vargs; void *qargs[11]; int methodIndex; JSObject* errorObj = 0; if ((methodIndex = findMethodIndex(exec, obj->metaObject(), d->m_signature, d->m_allowPrivate, args, vargs, (void **)qargs, &errorObj)) != -1) { if (obj->qt_metacall(QMetaObject::InvokeMetaMethod, methodIndex, qargs) >= 0) return jsUndefined(); if (vargs[0].isValid()) return convertQVariantToValue(exec, d->m_instance->rootObject(), vargs[0]); } if (errorObj) return errorObj; } else { return throwError(exec, GeneralError, "cannot call function of deleted QObject"); } // void functions return undefined return jsUndefined(); } bool QtRuntimeMetaMethod::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) { if (propertyName == "connect") { slot.setCustom(this, connectGetter); return true; } else if (propertyName == "disconnect") { slot.setCustom(this, disconnectGetter); return true; } else if (propertyName == exec->propertyNames().length) { slot.setCustom(this, lengthGetter); return true; } return QtRuntimeMethod::getOwnPropertySlot(exec, propertyName, slot); } JSValue *QtRuntimeMetaMethod::lengthGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot&) { // QtScript always returns 0 return jsNumber(0); } JSValue *QtRuntimeMetaMethod::connectGetter(ExecState* exec, JSObject*, const Identifier& ident, const PropertySlot& slot) { QtRuntimeMetaMethod* thisObj = static_cast(slot.slotBase()); QW_DS(QtRuntimeMetaMethod, thisObj); if (!d->m_connect) d->m_connect = new QtRuntimeConnectionMethod(exec, ident, true, d->m_instance, d->m_index, d->m_signature); return d->m_connect; } JSValue* QtRuntimeMetaMethod::disconnectGetter(ExecState* exec, JSObject*, const Identifier& ident, const PropertySlot& slot) { QtRuntimeMetaMethod* thisObj = static_cast(slot.slotBase()); QW_DS(QtRuntimeMetaMethod, thisObj); if (!d->m_disconnect) d->m_disconnect = new QtRuntimeConnectionMethod(exec, ident, false, d->m_instance, d->m_index, d->m_signature); return d->m_disconnect; } // =============== QMultiMap QtRuntimeConnectionMethod::connections; QtRuntimeConnectionMethod::QtRuntimeConnectionMethod(ExecState* exec, const Identifier& ident, bool isConnect, PassRefPtr inst, int index, const QByteArray& signature) : QtRuntimeMethod (new QtRuntimeConnectionMethodData(), exec, ident, inst) { QW_D(QtRuntimeConnectionMethod); d->m_signature = signature; d->m_index = index; d->m_isConnect = isConnect; } JSValue *QtRuntimeConnectionMethod::callAsFunction(ExecState* exec, JSObject*, const List& args) { QW_D(QtRuntimeConnectionMethod); JSLock lock; QObject* sender = d->m_instance->getObject(); if (sender) { JSObject* thisObject = exec->lexicalGlobalObject(); JSObject* funcObject = 0; // QtScript checks signalness first, arguments second int signalIndex = -1; // Make sure the initial index is a signal QMetaMethod m = sender->metaObject()->method(d->m_index); if (m.methodType() == QMetaMethod::Signal) signalIndex = findSignalIndex(sender->metaObject(), d->m_index, d->m_signature); if (signalIndex != -1) { if (args.size() == 1) { funcObject = args[0]->toObject(exec); if (!funcObject->implementsCall()) { if (d->m_isConnect) return throwError(exec, TypeError, "QtMetaMethod.connect: target is not a function"); else return throwError(exec, TypeError, "QtMetaMethod.disconnect: target is not a function"); } } else if (args.size() >= 2) { if (args[0]->type() == ObjectType) { thisObject = args[0]->toObject(exec); // Get the actual function to call JSObject *asObj = args[1]->toObject(exec); if (asObj->implementsCall()) { // Function version funcObject = asObj; } else { // Convert it to a string UString funcName = args[1]->toString(exec); Identifier funcIdent(funcName); // ### DropAllLocks // This is resolved at this point in QtScript JSValue* val = thisObject->get(exec, funcIdent); JSObject* asFuncObj = val->toObject(exec); if (asFuncObj->implementsCall()) { funcObject = asFuncObj; } else { if (d->m_isConnect) return throwError(exec, TypeError, "QtMetaMethod.connect: target is not a function"); else return throwError(exec, TypeError, "QtMetaMethod.disconnect: target is not a function"); } } } else { if (d->m_isConnect) return throwError(exec, TypeError, "QtMetaMethod.connect: thisObject is not an object"); else return throwError(exec, TypeError, "QtMetaMethod.disconnect: thisObject is not an object"); } } else { if (d->m_isConnect) return throwError(exec, GeneralError, "QtMetaMethod.connect: no arguments given"); else return throwError(exec, GeneralError, "QtMetaMethod.disconnect: no arguments given"); } if (d->m_isConnect) { // to connect, we need: // target object [from ctor] // target signal index etc. [from ctor] // receiver function [from arguments] // receiver this object [from arguments] QtConnectionObject* conn = new QtConnectionObject(d->m_instance, signalIndex, thisObject, funcObject); bool ok = QMetaObject::connect(sender, signalIndex, conn, conn->metaObject()->methodOffset()); if (!ok) { delete conn; QString msg = QString("QtMetaMethod.connect: failed to connect to %1::%2()") .arg(sender->metaObject()->className()) .arg(QLatin1String(d->m_signature)); return throwError(exec, GeneralError, msg.toLatin1().constData()); } else { // Store connection connections.insert(sender, conn); } } else { // Now to find our previous connection object. Hmm. QList conns = connections.values(sender); bool ret = false; foreach(QtConnectionObject* conn, conns) { // Is this the right connection? if (conn->match(sender, signalIndex, thisObject, funcObject)) { // Yep, disconnect it QMetaObject::disconnect(sender, signalIndex, conn, conn->metaObject()->methodOffset()); delete conn; // this will also remove it from the map ret = true; break; } } if (!ret) { QString msg = QString("QtMetaMethod.disconnect: failed to disconnect from %1::%2()") .arg(sender->metaObject()->className()) .arg(QLatin1String(d->m_signature)); return throwError(exec, GeneralError, msg.toLatin1().constData()); } } } else { QString msg = QString("QtMetaMethod.%1: %2::%3() is not a signal") .arg(d->m_isConnect ? "connect": "disconnect") .arg(sender->metaObject()->className()) .arg(QLatin1String(d->m_signature)); return throwError(exec, TypeError, msg.toLatin1().constData()); } } else { return throwError(exec, GeneralError, "cannot call function of deleted QObject"); } return jsUndefined(); } bool QtRuntimeConnectionMethod::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) { if (propertyName == exec->propertyNames().length) { slot.setCustom(this, lengthGetter); return true; } return QtRuntimeMethod::getOwnPropertySlot(exec, propertyName, slot); } JSValue *QtRuntimeConnectionMethod::lengthGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot&) { // we have one formal argument, and one optional return jsNumber(1); } // =============== QtConnectionObject::QtConnectionObject(PassRefPtr instance, int signalIndex, JSObject* thisObject, JSObject* funcObject) : m_instance(instance) , m_signalIndex(signalIndex) , m_originalObject(m_instance->getObject()) , m_thisObject(thisObject) , m_funcObject(funcObject) { setParent(m_originalObject); ASSERT(JSLock::currentThreadIsHoldingLock()); // so our ProtectedPtrs are safe } QtConnectionObject::~QtConnectionObject() { // Remove us from the map of active connections QtRuntimeConnectionMethod::connections.remove(m_originalObject, this); } static const uint qt_meta_data_QtConnectionObject[] = { // content: 1, // revision 0, // classname 0, 0, // classinfo 1, 10, // methods 0, 0, // properties 0, 0, // enums/sets // slots: signature, parameters, type, tag, flags 28, 27, 27, 27, 0x0a, 0 // eod }; static const char qt_meta_stringdata_QtConnectionObject[] = { "KJS::Bindings::QtConnectionObject\0\0execute()\0" }; const QMetaObject QtConnectionObject::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_QtConnectionObject, qt_meta_data_QtConnectionObject, 0 } }; const QMetaObject *QtConnectionObject::metaObject() const { return &staticMetaObject; } void *QtConnectionObject::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_QtConnectionObject)) return static_cast(const_cast(this)); return QObject::qt_metacast(_clname); } int QtConnectionObject::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) { switch (_id) { case 0: execute(_a); break; } _id -= 1; } return _id; } void QtConnectionObject::execute(void **argv) { QObject* obj = m_instance->getObject(); if (obj) { const QMetaObject* meta = obj->metaObject(); const QMetaMethod method = meta->method(m_signalIndex); QList parameterTypes = method.parameterTypes(); int argc = parameterTypes.count(); JSLock lock; // ### Should the Interpreter/ExecState come from somewhere else? RefPtr ro = m_instance->rootObject(); if (ro) { JSGlobalObject* globalobj = ro->globalObject(); if (globalobj) { ExecState* exec = globalobj->globalExec(); if (exec) { // Build the argument list (up to the formal argument length of the slot) List l; // ### DropAllLocks? int funcArgC = m_funcObject->get(exec, exec->propertyNames().length)->toInt32(exec); int argTotal = qMax(funcArgC, argc); for(int i=0; i < argTotal; i++) { if (i < argc) { int argType = QMetaType::type(parameterTypes.at(i)); l.append(convertQVariantToValue(exec, ro, QVariant(argType, argv[i+1]))); } else { l.append(jsUndefined()); } } // Stuff in the __qt_sender property, if we can if (m_funcObject->inherits(&FunctionImp::info)) { FunctionImp* fimp = static_cast(m_funcObject.get()); JSObject* qt_sender = Instance::createRuntimeObject(Instance::QtLanguage, sender(), ro); JSObject* wrapper = new JSObject(); wrapper->put(exec, "__qt_sender__", qt_sender); ScopeChain oldsc = fimp->scope(); ScopeChain sc = oldsc; sc.push(wrapper); fimp->setScope(sc); fimp->call(exec, m_thisObject, l); fimp->setScope(oldsc); } else m_funcObject->call(exec, m_thisObject, l); } } } } else { // A strange place to be - a deleted object emitted a signal here. qWarning() << "sender deleted, cannot deliver signal"; } } bool QtConnectionObject::match(QObject* sender, int signalIndex, JSObject* thisObject, JSObject *funcObject) { if (m_originalObject == sender && m_signalIndex == signalIndex && thisObject == (JSObject*)m_thisObject && funcObject == (JSObject*)m_funcObject) return true; return false; } // =============== template QtArray::QtArray(QList list, QMetaType::Type type, PassRefPtr rootObject) : Array(rootObject) , m_list(list) , m_type(type) { m_length = m_list.count(); } template QtArray::~QtArray () { } template RootObject* QtArray::rootObject() const { return _rootObject && _rootObject->isValid() ? _rootObject.get() : 0; } template void QtArray::setValueAt(ExecState *exec, unsigned int index, JSValue *aValue) const { // QtScript sets the value, but doesn't forward it to the original source // (e.g. if you do 'object.intList[5] = 6', the object is not updated, but the // copy of the list is). int dist = -1; QVariant val = convertValueToQVariant(exec, aValue, m_type, &dist); if (dist >= 0) { m_list[index] = val.value(); } } template JSValue* QtArray::valueAt(ExecState *exec, unsigned int index) const { if (index < m_length) { T val = m_list.at(index); return convertQVariantToValue(exec, rootObject(), QVariant::fromValue(val)); } return jsUndefined(); } // =============== } }