Showing the changes to libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp (1 of 63 files). Show all
Unified · Split
| @@ -0,0 +1,622 @@ | |||
| 1 | // | ||
| 2 | // ScriptObjectQtProxy.cpp | ||
| 3 | // libraries/script-engine/src/qtscript | ||
| 4 | // | ||
| 5 | // Created by Heather Anderson on 12/5/21. | ||
| 6 | // Copyright 2021 Vircadia contributors. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #include "ScriptObjectQtProxy.h" | ||
| 13 | |||
| 14 | #include <QtCore/QList> | ||
| 15 | #include <QtCore/QSharedPointer> | ||
| 16 | |||
| 17 | #include <QtScript/QScriptContext> | ||
| 18 | |||
| 19 | #include "ScriptContextQtWrapper.h" | ||
| 20 | #include "ScriptValueQtWrapper.h" | ||
| 21 | |||
| 22 | Q_DECLARE_METATYPE(QScriptContext*) | ||
| 23 | Q_DECLARE_METATYPE(ScriptValue) | ||
| 24 | Q_DECLARE_METATYPE(QScriptValue) | ||
| 25 | |||
| 26 | Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectQtProxy>) | ||
| 27 | Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantQtProxy>) | ||
| 28 | |||
| 29 | // Used strictly to replace the "this" object value for property access. May expand to a full context element | ||
| 30 | // if we find it necessary to, but hopefully not needed | ||
| 31 | class ScriptPropertyContextQtWrapper final : public ScriptContext { | ||
| 32 | public: // construction | ||
| 33 | inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) : | ||
| 34 | _parent(parentContext), _object(object) {} | ||
| 35 | |||
| 36 | public: // ScriptContext implementation | ||
| 37 | virtual int argumentCount() const override { return _parent->argumentCount(); } | ||
| 38 | virtual ScriptValue argument(int index) const override { return _parent->argument(index); } | ||
| 39 | virtual QStringList backtrace() const override { return _parent->backtrace(); } | ||
| 40 | virtual ScriptValue callee() const override { return _parent->callee(); } | ||
| 41 | virtual ScriptEnginePointer engine() const override { return _parent->engine(); } | ||
| 42 | virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } | ||
| 43 | virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); } | ||
| 44 | virtual ScriptValue thisObject() const override { return _object; } | ||
| 45 | virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); } | ||
| 46 | virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } | ||
| 47 | |||
| 48 | private: // storage | ||
| 49 | ScriptContext* _parent; | ||
| 50 | const ScriptValue& _object; | ||
| 51 | }; | ||
| 52 | |||
| 53 | QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object, | ||
| 54 | ScriptEngine::ValueOwnership ownership, | ||
| 55 | const ScriptEngine::QObjectWrapOptions& options) { | ||
| 56 | bool ownsObject; | ||
| 57 | switch (ownership) { | ||
| 58 | case ScriptEngine::QtOwnership: | ||
| 59 | ownsObject = false; | ||
| 60 | break; | ||
| 61 | case ScriptEngine::ScriptOwnership: | ||
| 62 | ownsObject = true; | ||
| 63 | break; | ||
| 64 | case ScriptEngine::AutoOwnership: | ||
| 65 | ownsObject = !object->parent(); | ||
| 66 | break; | ||
| 67 | } | ||
| 68 | QScriptEngine* qengine = static_cast<QScriptEngine*>(engine); | ||
| 69 | auto proxy = QSharedPointer<ScriptObjectQtProxy>::create(engine, object, ownsObject, options); | ||
| 70 | return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); | ||
| 71 | } | ||
| 72 | |||
| 73 | ScriptObjectQtProxy* ScriptObjectQtProxy::unwrapProxy(const QScriptValue& val) { | ||
| 74 | QScriptClass* scriptClass = val.scriptClass(); | ||
| 75 | return scriptClass ? dynamic_cast<ScriptObjectQtProxy*>(scriptClass) : nullptr; | ||
| 76 | } | ||
| 77 | |||
| 78 | QObject* ScriptObjectQtProxy::unwrap(const QScriptValue& val) { | ||
| 79 | if (val.isQObject()) { | ||
| 80 | return val.toQObject(); | ||
| 81 | } | ||
| 82 | ScriptObjectQtProxy* proxy = unwrapProxy(val); | ||
| 83 | return proxy ? proxy->toQtValue() : nullptr; | ||
| 84 | } | ||
| 85 | |||
| 86 | ScriptObjectQtProxy::~ScriptObjectQtProxy() { | ||
| 87 | if (_ownsObject) { | ||
| 88 | QObject* qobject = _object; | ||
| 89 | if(qobject) qobject->deleteLater(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | void ScriptObjectQtProxy::investigate() { | ||
| 94 | QObject* qobject = _object; | ||
| 95 | Q_ASSERT(qobject); | ||
| 96 | if (!qobject) return; | ||
| 97 | |||
| 98 | const QMetaObject* metaObject = qobject->metaObject(); | ||
| 99 | _name = QString::fromLatin1(metaObject->className()); | ||
| 100 | |||
| 101 | // discover properties | ||
| 102 | int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; | ||
| 103 | int num = metaObject->propertyCount(); | ||
| 104 | for (int idx = startIdx; idx < num; ++idx) { | ||
| 105 | QMetaProperty prop = metaObject->property(idx); | ||
| 106 | if (!prop.isScriptable()) continue; | ||
| 107 | |||
| 108 | // always exclude child objects (at least until we decide otherwise) | ||
| 109 | int metaTypeId = prop.userType(); | ||
| 110 | if (metaTypeId != QMetaType::UnknownType) { | ||
| 111 | QMetaType metaType(metaTypeId); | ||
| 112 | if (metaType.flags() & QMetaType::PointerToQObject) { | ||
| 113 | continue; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | PropertyDef& propDef = _props.insert(idx, PropertyDef()).value(); | ||
| 118 | propDef.name = _engine->toStringHandle(QString::fromLatin1(prop.name())); | ||
| 119 | propDef.flags = QScriptValue::Undeletable | QScriptValue::PropertyGetter | QScriptValue::PropertySetter | | ||
| 120 | QScriptValue::QObjectMember; | ||
| 121 | if (prop.isConstant()) propDef.flags |= QScriptValue::ReadOnly; | ||
| 122 | } | ||
| 123 | |||
| 124 | // discover methods | ||
| 125 | startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassMethods ? metaObject->methodCount() : 0; | ||
| 126 | num = metaObject->methodCount(); | ||
| 127 | QHash<QScriptString, int> methodNames; | ||
| 128 | for (int idx = startIdx; idx < num; ++idx) { | ||
| 129 | QMetaMethod method = metaObject->method(idx); | ||
| 130 | |||
| 131 | // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible | ||
| 132 | /* if (method.access() != QMetaMethod::Public) continue;*/ | ||
| 133 | |||
| 134 | bool isSignal = false; | ||
| 135 | QByteArray szName = method.name(); | ||
| 136 | |||
| 137 | switch (method.methodType()) { | ||
| 138 | case QMetaMethod::Constructor: | ||
| 139 | continue; | ||
| 140 | case QMetaMethod::Signal: | ||
| 141 | isSignal = true; | ||
| 142 | break; | ||
| 143 | case QMetaMethod::Slot: | ||
| 144 | if (_wrapOptions & ScriptEngine::ExcludeSlots) { | ||
| 145 | continue; | ||
| 146 | } | ||
| 147 | if (szName == "deleteLater") { | ||
| 148 | continue; | ||
| 149 | } | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | QScriptString name = _engine->toStringHandle(QString::fromLatin1(szName)); | ||
| 154 | auto nameLookup = methodNames.find(name); | ||
| 155 | if (isSignal) { | ||
| 156 | if (nameLookup == methodNames.end()) { | ||
| 157 | SignalDef& signalDef = _signals.insert(idx, SignalDef()).value(); | ||
| 158 | signalDef.name = name; | ||
| 159 | signalDef.signal = method; | ||
| 160 | methodNames.insert(name, idx); | ||
| 161 | } else { | ||
| 162 | int originalMethodId = nameLookup.value(); | ||
| 163 | SignalDefMap::iterator signalLookup = _signals.find(originalMethodId); | ||
| 164 | Q_ASSERT(signalLookup != _signals.end()); | ||
| 165 | SignalDef& signalDef = signalLookup.value(); | ||
| 166 | Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount()); | ||
| 167 | if (signalDef.signal.parameterCount() < method.parameterCount()) { | ||
| 168 | signalDef.signal = method; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } else { | ||
| 172 | if (nameLookup == methodNames.end()) { | ||
| 173 | MethodDef& methodDef = _methods.insert(idx, MethodDef()).value(); | ||
| 174 | methodDef.name = name; | ||
| 175 | methodDef.methods.append(method); | ||
| 176 | methodNames.insert(name, idx); | ||
| 177 | } else { | ||
| 178 | int originalMethodId = nameLookup.value(); | ||
| 179 | MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); | ||
| 180 | Q_ASSERT(methodLookup != _methods.end()); | ||
| 181 | MethodDef& methodDef = methodLookup.value(); | ||
| 182 | methodDef.methods.append(method); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) { | ||
| 189 | // check for properties | ||
| 190 | for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { | ||
| 191 | const PropertyDef& propDef = trans.value(); | ||
| 192 | if (propDef.name != name) continue; | ||
| 193 | *id = trans.key() | PROPERTY_TYPE; | ||
| 194 | return flags & (HandlesReadAccess | HandlesWriteAccess); | ||
| 195 | } | ||
| 196 | |||
| 197 | // check for methods | ||
| 198 | for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { | ||
| 199 | if (trans.value().name != name) continue; | ||
| 200 | *id = trans.key() | METHOD_TYPE; | ||
| 201 | return flags & (HandlesReadAccess | HandlesWriteAccess); | ||
| 202 | } | ||
| 203 | |||
| 204 | // check for signals | ||
| 205 | for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { | ||
| 206 | if (trans.value().name != name) continue; | ||
| 207 | *id = trans.key() | SIGNAL_TYPE; | ||
| 208 | return flags & (HandlesReadAccess | HandlesWriteAccess); | ||
| 209 | } | ||
| 210 | |||
| 211 | return QueryFlags(); | ||
| 212 | } | ||
| 213 | |||
| 214 | QScriptValue::PropertyFlags ScriptObjectQtProxy::propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) { | ||
| 215 | QObject* qobject = _object; | ||
| 216 | if (!qobject) { | ||
| 217 | return QScriptValue::PropertyFlags(); | ||
| 218 | } | ||
| 219 | |||
| 220 | switch (id & TYPE_MASK) { | ||
| 221 | case PROPERTY_TYPE: { | ||
| 222 | PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); | ||
| 223 | if (lookup == _props.cend()) return QScriptValue::PropertyFlags(); | ||
| 224 | const PropertyDef& propDef = lookup.value(); | ||
| 225 | return propDef.flags; | ||
| 226 | } | ||
| 227 | case METHOD_TYPE: { | ||
| 228 | MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK); | ||
| 229 | if (lookup == _methods.cend()) return QScriptValue::PropertyFlags(); | ||
| 230 | return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember; | ||
| 231 | } | ||
| 232 | case SIGNAL_TYPE: { | ||
| 233 | SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); | ||
| 234 | if (lookup == _signals.cend()) return QScriptValue::PropertyFlags(); | ||
| 235 | return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember; | ||
| 236 | } | ||
| 237 | } | ||
| 238 | return QScriptValue::PropertyFlags(); | ||
| 239 | } | ||
| 240 | |||
| 241 | QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QScriptString& name, uint id) { | ||
| 242 | QObject* qobject = _object; | ||
| 243 | if (!qobject) { | ||
| 244 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 245 | currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); | ||
| 246 | return QScriptValue(); | ||
| 247 | } | ||
| 248 | |||
| 249 | const QMetaObject* metaObject = qobject->metaObject(); | ||
| 250 | |||
| 251 | switch (id & TYPE_MASK) { | ||
| 252 | case PROPERTY_TYPE: { | ||
| 253 | int propId = id & ~TYPE_MASK; | ||
| 254 | PropertyDefMap::const_iterator lookup = _props.find(propId); | ||
| 255 | if (lookup == _props.cend()) return QScriptValue(); | ||
| 256 | const PropertyDef& propDef = lookup.value(); | ||
| 257 | |||
| 258 | QMetaProperty prop = metaObject->property(propId); | ||
| 259 | ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object)); | ||
| 260 | ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); | ||
| 261 | ScriptContextGuard guard(&ourContext); | ||
| 262 | |||
| 263 | QVariant varValue = prop.read(qobject); | ||
| 264 | return _engine->castVariantToValue(varValue); | ||
| 265 | } | ||
| 266 | case METHOD_TYPE: { | ||
| 267 | int methodId = id & ~TYPE_MASK; | ||
| 268 | MethodDefMap::const_iterator lookup = _methods.find(methodId); | ||
| 269 | if (lookup == _methods.cend()) return QScriptValue(); | ||
| 270 | return static_cast<QScriptEngine*>(_engine)->newObject( | ||
| 271 | new ScriptMethodQtProxy(_engine, qobject, object, name, lookup.value().methods)); | ||
| 272 | } | ||
| 273 | case SIGNAL_TYPE: { | ||
| 274 | int signalId = id & ~TYPE_MASK; | ||
| 275 | SignalDefMap::const_iterator defLookup = _signals.find(signalId); | ||
| 276 | if (defLookup == _signals.cend()) return QScriptValue(); | ||
| 277 | |||
| 278 | InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); | ||
| 279 | if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { | ||
| 280 | instLookup = _signalInstances.insert(signalId, | ||
| 281 | new ScriptSignalQtProxy(_engine, qobject, object, name, defLookup.value().signal)); | ||
| 282 | Q_ASSERT(instLookup != _signalInstances.cend()); | ||
| 283 | } | ||
| 284 | ScriptSignalQtProxy* proxy = instLookup.value(); | ||
| 285 | |||
| 286 | QScriptEngine::QObjectWrapOptions options = QScriptEngine::ExcludeSuperClassContents | | ||
| 287 | QScriptEngine::ExcludeDeleteLater | | ||
| 288 | QScriptEngine::PreferExistingWrapperObject; | ||
| 289 | return static_cast<QScriptEngine*>(_engine)->newQObject(proxy, QScriptEngine::ScriptOwnership, options); | ||
| 290 | } | ||
| 291 | } | ||
| 292 | return QScriptValue(); | ||
| 293 | } | ||
| 294 | |||
| 295 | void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) { | ||
| 296 | if (!(id & PROPERTY_TYPE)) return; | ||
| 297 | QObject* qobject = _object; | ||
| 298 | if (!qobject) { | ||
| 299 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 300 | currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); | ||
| 301 | return; | ||
| 302 | } | ||
| 303 | |||
| 304 | int propId = id & ~TYPE_MASK; | ||
| 305 | PropertyDefMap::const_iterator lookup = _props.find(propId); | ||
| 306 | if (lookup == _props.cend()) return; | ||
| 307 | const PropertyDef& propDef = lookup.value(); | ||
| 308 | if (propDef.flags & QScriptValue::ReadOnly) return; | ||
| 309 | |||
| 310 | const QMetaObject* metaObject = qobject->metaObject(); | ||
| 311 | QMetaProperty prop = metaObject->property(propId); | ||
| 312 | |||
| 313 | ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object)); | ||
| 314 | ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); | ||
| 315 | ScriptContextGuard guard(&ourContext); | ||
| 316 | |||
| 317 | int propTypeId = prop.userType(); | ||
| 318 | QVariant varValue; | ||
| 319 | if(!_engine->castValueToVariant(value, varValue, propTypeId)) { | ||
| 320 | QByteArray propTypeName = QMetaType(propTypeId).name(); | ||
| 321 | QByteArray valTypeName = value.toVariant().typeName(); | ||
| 322 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 323 | currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName)); | ||
| 324 | return; | ||
| 325 | } | ||
| 326 | prop.write(qobject, varValue); | ||
| 327 | } | ||
| 328 | |||
| 329 | ScriptVariantQtProxy::ScriptVariantQtProxy(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue scriptProto, ScriptObjectQtProxy* proto) : | ||
| 330 | QScriptClass(engine), _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { | ||
| 331 | _name = QString::fromLatin1(variant.typeName()); | ||
| 332 | } | ||
| 333 | |||
| 334 | QScriptValue ScriptVariantQtProxy::newVariant(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue proto) { | ||
| 335 | QScriptEngine* qengine = static_cast<QScriptEngine*>(engine); | ||
| 336 | ScriptObjectQtProxy* protoProxy = ScriptObjectQtProxy::unwrapProxy(proto); | ||
| 337 | if (!protoProxy) { | ||
| 338 | Q_ASSERT(protoProxy); | ||
| 339 | return qengine->newVariant(variant); | ||
| 340 | } | ||
| 341 | auto proxy = QSharedPointer<ScriptVariantQtProxy>::create(engine, variant, proto, protoProxy); | ||
| 342 | return static_cast<QScriptEngine*>(engine)->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); | ||
| 343 | } | ||
| 344 | |||
| 345 | ScriptVariantQtProxy* ScriptVariantQtProxy::unwrapProxy(const QScriptValue& val) { | ||
| 346 | QScriptClass* scriptClass = val.scriptClass(); | ||
| 347 | return scriptClass ? dynamic_cast<ScriptVariantQtProxy*>(scriptClass) : nullptr; | ||
| 348 | } | ||
| 349 | |||
| 350 | QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) { | ||
| 351 | ScriptVariantQtProxy* proxy = unwrapProxy(val); | ||
| 352 | return proxy ? proxy->toQtValue() : QVariant(); | ||
| 353 | } | ||
| 354 | |||
| 355 | bool ScriptMethodQtProxy::supportsExtension(Extension extension) const { | ||
| 356 | switch (extension) { | ||
| 357 | case Callable: | ||
| 358 | return true; | ||
| 359 | default: | ||
| 360 | return false; | ||
| 361 | } | ||
| 362 | } | ||
| 363 | |||
| 364 | QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& argument) { | ||
| 365 | if (extension != Callable) return QVariant(); | ||
| 366 | QScriptContext* context = qvariant_cast<QScriptContext*>(argument); | ||
| 367 | |||
| 368 | QObject* qobject = _object; | ||
| 369 | if (!qobject) { | ||
| 370 | context->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); | ||
| 371 | return QVariant(); | ||
| 372 | } | ||
| 373 | |||
| 374 | int scriptNumArgs = context->argumentCount(); | ||
| 375 | Q_ASSERT(scriptNumArgs < 10); | ||
| 376 | int numArgs = std::min(scriptNumArgs, 10); | ||
| 377 | |||
| 378 | const int scriptValueTypeId = qMetaTypeId<ScriptValue>(); | ||
| 379 | |||
| 380 | for (auto iter = _metas.cbegin(); iter != _metas.end(); ++iter) { | ||
| 381 | const QMetaMethod& meta = *iter; | ||
| 382 | int methodNumArgs = meta.parameterCount(); | ||
| 383 | if (methodNumArgs != numArgs) { | ||
| 384 | continue; | ||
| 385 | } | ||
| 386 | |||
| 387 | QList<ScriptValue> qScriptArgList; | ||
| 388 | QList<QVariant> qVarArgList; | ||
| 389 | QGenericArgument qGenArgs[10]; | ||
| 390 | int arg; | ||
| 391 | bool conversionFailed = false; | ||
| 392 | for (arg = 0; arg < numArgs && !conversionFailed; ++arg) { | ||
| 393 | int methodArgTypeId = meta.parameterType(arg); | ||
| 394 | QScriptValue argVal = context->argument(arg); | ||
| 395 | if (methodArgTypeId == scriptValueTypeId) { | ||
| 396 | qScriptArgList.append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal))); | ||
| 397 | qGenArgs[arg] = Q_ARG(ScriptValue, qScriptArgList.back()); | ||
| 398 | } else if (methodArgTypeId == QMetaType::QVariant) { | ||
| 399 | qVarArgList.append(argVal.toVariant()); | ||
| 400 | qGenArgs[arg] = Q_ARG(QVariant, qVarArgList.back()); | ||
| 401 | } else { | ||
| 402 | QVariant varArgVal; | ||
| 403 | if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { | ||
| 404 | conversionFailed = true; | ||
| 405 | break; | ||
| 406 | /*QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); | ||
| 407 | QByteArray argTypeName = argVal.toVariant().typeName(); | ||
| 408 | context->throwError(QScriptContext::TypeError, | ||
| 409 | QString("Cannot convert %1 to %2").arg(argTypeName, methodTypeName)); | ||
| 410 | return QVariant();*/ | ||
| 411 | } | ||
| 412 | qVarArgList.append(varArgVal); | ||
| 413 | const QVariant& converted = qVarArgList.back(); | ||
| 414 | |||
| 415 | // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | ||
| 416 | // A const_cast is needed because calling data() would detach the QVariant. | ||
| 417 | qGenArgs[arg] = | ||
| 418 | QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); | ||
| 419 | } | ||
| 420 | } | ||
| 421 | if (conversionFailed) { | ||
| 422 | continue; | ||
| 423 | } | ||
| 424 | |||
| 425 | ScriptContextQtWrapper ourContext(_engine, context); | ||
| 426 | ScriptContextGuard guard(&ourContext); | ||
| 427 | |||
| 428 | int returnTypeId = meta.returnType(); | ||
| 429 | if (returnTypeId == QMetaType::Void) { | ||
| 430 | bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], | ||
| 431 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 432 | if (!success) { | ||
| 433 | context->throwError("Native call failed"); | ||
| 434 | } | ||
| 435 | return QVariant(); | ||
| 436 | } else if (returnTypeId == scriptValueTypeId) { | ||
| 437 | ScriptValue result; | ||
| 438 | bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], | ||
| 439 | qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], | ||
| 440 | qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 441 | if (!success) { | ||
| 442 | context->throwError("Native call failed"); | ||
| 443 | return QVariant(); | ||
| 444 | } | ||
| 445 | QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result); | ||
| 446 | return QVariant::fromValue(qResult); | ||
| 447 | } else { | ||
| 448 | // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | ||
| 449 | const char* typeName = meta.typeName(); | ||
| 450 | QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); | ||
| 451 | QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); | ||
| 452 | |||
| 453 | bool success = | ||
| 454 | meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], | ||
| 455 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 456 | if (!success) { | ||
| 457 | context->throwError("Native call failed"); | ||
| 458 | return QVariant(); | ||
| 459 | } | ||
| 460 | QScriptValue qResult = _engine->castVariantToValue(qRetVal); | ||
| 461 | return QVariant::fromValue(qResult); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | context->throwError("Native call failed: could not locate an overload with the requested arguments"); | ||
| 465 | return QVariant(); | ||
| 466 | } | ||
| 467 | |||
| 468 | // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it | ||
| 469 | int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { | ||
| 470 | id = ScriptSignalQtProxyBase::qt_metacall(call, id, arguments); | ||
| 471 | if (id != 0 || call != QMetaObject::InvokeMetaMethod) { | ||
| 472 | return id; | ||
| 473 | } | ||
| 474 | |||
| 475 | QScriptValueList args; | ||
| 476 | int numArgs = _meta.parameterCount(); | ||
| 477 | for (int arg = 0; arg < numArgs; ++arg) { | ||
| 478 | int methodArgTypeId = _meta.parameterType(arg); | ||
| 479 | QVariant argValue(methodArgTypeId, arguments[arg+1]); | ||
| 480 | args.append(_engine->castVariantToValue(argValue)); | ||
| 481 | } | ||
| 482 | |||
| 483 | for (ConnectionList::iterator iter = _connections.begin(); iter != _connections.end(); ++iter) { | ||
| 484 | Connection& conn = *iter; | ||
| 485 | conn.callback.call(conn.thisValue, args); | ||
| 486 | } | ||
| 487 | |||
| 488 | return -1; | ||
| 489 | } | ||
| 490 | |||
| 491 | int ScriptSignalQtProxy::discoverMetaCallIdx() { | ||
| 492 | const QMetaObject* ourMeta = metaObject(); | ||
| 493 | return ourMeta->methodCount(); | ||
| 494 | } | ||
| 495 | |||
| 496 | ScriptSignalQtProxy::ConnectionList::iterator ScriptSignalQtProxy::findConnection(QScriptValue thisObject, | ||
| 497 | QScriptValue callback) { | ||
| 498 | for (ConnectionList::iterator iter = _connections.begin(); iter != _connections.end(); ++iter) { | ||
| 499 | Connection& conn = *iter; | ||
| 500 | if (conn.callback.strictlyEquals(callback) && conn.thisValue.strictlyEquals(thisObject)) { | ||
| 501 | return iter; | ||
| 502 | } | ||
| 503 | } | ||
| 504 | return _connections.end(); | ||
| 505 | } | ||
| 506 | |||
| 507 | |||
| 508 | void ScriptSignalQtProxy::connect(QScriptValue arg0, QScriptValue arg1) { | ||
| 509 | QObject* qobject = _object; | ||
| 510 | if (!qobject) { | ||
| 511 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 512 | currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); | ||
| 513 | return; | ||
| 514 | } | ||
| 515 | |||
| 516 | // untangle the arguments | ||
| 517 | QScriptValue callback; | ||
| 518 | QScriptValue callbackThis; | ||
| 519 | if (arg1.isFunction()) { | ||
| 520 | callbackThis = arg0; | ||
| 521 | callback = arg1; | ||
| 522 | } else { | ||
| 523 | callback = arg0; | ||
| 524 | } | ||
| 525 | if (!callback.isFunction()) { | ||
| 526 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 527 | currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'connect'"); | ||
| 528 | return; | ||
| 529 | } | ||
| 530 | |||
| 531 | // are we already connected? | ||
| 532 | ConnectionList::iterator lookup = findConnection(callbackThis, callback); | ||
| 533 | if (lookup != _connections.end()) { | ||
| 534 | return; // already exists | ||
| 535 | } | ||
| 536 | |||
| 537 | // add a reference to ourselves to the destination callback | ||
| 538 | QScriptValue destData = callback.data(); | ||
| 539 | Q_ASSERT(!destData.isValid() || destData.isArray()); | ||
| 540 | if (!destData.isArray()) { | ||
| 541 | destData = static_cast<QScriptEngine*>(_engine)->newArray(); | ||
| 542 | } | ||
| 543 | { | ||
| 544 | QScriptValueList args; | ||
| 545 | args << thisObject(); | ||
| 546 | destData.property("push").call(destData, args); | ||
| 547 | } | ||
| 548 | callback.setData(destData); | ||
| 549 | |||
| 550 | // add this to our internal list of connections | ||
| 551 | Connection newConn; | ||
| 552 | newConn.callback = callback; | ||
| 553 | newConn.thisValue = callbackThis; | ||
| 554 | _connections.append(newConn); | ||
| 555 | |||
| 556 | // inform Qt that we're connecting to this signal | ||
| 557 | if (!_isConnected) { | ||
| 558 | auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); | ||
| 559 | Q_ASSERT(result); | ||
| 560 | _isConnected = true; | ||
| 561 | } | ||
| 562 | } | ||
| 563 | |||
| 564 | void ScriptSignalQtProxy::disconnect(QScriptValue arg0, QScriptValue arg1) { | ||
| 565 | QObject* qobject = _object; | ||
| 566 | if (!qobject) { | ||
| 567 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 568 | currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); | ||
| 569 | return; | ||
| 570 | } | ||
| 571 | |||
| 572 | // untangle the arguments | ||
| 573 | QScriptValue callback; | ||
| 574 | QScriptValue callbackThis; | ||
| 575 | if (arg1.isFunction()) { | ||
| 576 | callbackThis = arg0; | ||
| 577 | callback = arg1; | ||
| 578 | } else { | ||
| 579 | callback = arg0; | ||
| 580 | } | ||
| 581 | if (!callback.isFunction()) { | ||
| 582 | QScriptContext* currentContext = static_cast<QScriptEngine*>(_engine)->currentContext(); | ||
| 583 | currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'disconnect'"); | ||
| 584 | return; | ||
| 585 | } | ||
| 586 | |||
| 587 | // locate this connection in our list of connections | ||
| 588 | ConnectionList::iterator lookup = findConnection(callbackThis, callback); | ||
| 589 | if (lookup == _connections.end()) { | ||
| 590 | return; // not here | ||
| 591 | } | ||
| 592 | |||
| 593 | // remove it from our internal list of connections | ||
| 594 | _connections.erase(lookup); | ||
| 595 | |||
| 596 | // remove a reference to ourselves from the destination callback | ||
| 597 | QScriptValue destData = callback.data(); | ||
| 598 | Q_ASSERT(destData.isArray()); | ||
| 599 | if (destData.isArray()) { | ||
| 600 | QScriptValue qThis = thisObject(); | ||
| 601 | int len = destData.property("length").toInteger(); | ||
| 602 | bool foundIt = false; | ||
| 603 | for (int idx = 0; idx < len && !foundIt; ++idx) { | ||
| 604 | QScriptValue entry = destData.property(idx); | ||
| 605 | if (entry.strictlyEquals(qThis)) { | ||
| 606 | foundIt = true; | ||
| 607 | QScriptValueList args; | ||
| 608 | args << idx << 1; | ||
| 609 | destData.property("splice").call(destData, args); | ||
| 610 | } | ||
| 611 | } | ||
| 612 | Q_ASSERT(foundIt); | ||
| 613 | } | ||
| 614 | |||
| 615 | // inform Qt that we're no longer connected to this signal | ||
| 616 | if (_connections.empty()) { | ||
| 617 | Q_ASSERT(_isConnected); | ||
| 618 | bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); | ||
| 619 | Q_ASSERT(result); | ||
| 620 | _isConnected = false; | ||
| 621 | } | ||
| 622 | } | ||