Creating a newable QtScript object from C++ class and extend its functions via prototype
-
wrote on 26 Nov 2012, 12:51 last edited by
Hi,
i'm trying to wrap my head around QtScript. Right now, i'm trying to get a new-able QtScript object representation of a C++ class hierarchy, which works fine so far, using scriptValueFromQMetaObject<>() and Q_SCRIPT_DECLARE_QMETAOBJECT().From that point on, I want to extend this object represention with some additional functions via custom prototypes, similiar to the "Default Prototypes Example":http://doc.qt.digia.com/qt/script-defaultprototypes.html shown at Qt Ref. Doc.
Last point doesn't work, i can compile but get the message "TypeError: Result of expression 'myFruit.eat' [undefined] is not a function" from my QtScriptEngine (according to the code snippets below).
To show my setting...
These is my simplified class hierarchy:
@class Fruit{}class ExoticFruit : public Fruit {}
class Cumquat : public ExoticFruit {
public slots:
void someNativeCppCumquatSlot();}
Q_META_DECLARE(Cumquat*)
Q_SCRIPT_DECLARE_QMETAOBJECT(Cumquat, QObject*)@Now I added a prototype class:
@
class EatableProto : public QObject
{
Q_OBJECT
public:
explicit EatableProto(QObject * parent = 0){};public slots:
void eat(){ qDebug() << "yamm"; };
}
@this is my simplified scripting setup:
@
//ctor of scripting class
class MyScripter::MyScripter()
{
m_engine = new QScriptEngine;//has no effect, as far as I can see
m_eatablePrototype = new EatableProto();
m_engine->setDefaultPrototype( qMetaTypeId<Cumquat*>(), m_engine->newObject(m_eatablePrototype) );// works fine
QScriptValue scriptCumquat = m_engine->scriptValueFromMetaObject<Cumquat>();
m_engine->globalObject().setProperty("SmallOrange",scriptCumquat);
}@On QtScripting view, I want to achive something like this:
@
//QtScript Code:
var myFruit = new SmallOrange();
myFruit.someNativeCppCumquatSlot(); //worksmyFruit.eat(); //protoype slot, fails with error 'is not a function'
@I'm working on the prototype approach because I want to have more ExoticFruits in near future.. lets say
@
class Lemon : public ExoticFruit {}
class PineApple : public ExoticFruit {}
...
@which are all eatable... but I want them to have eatable in scripting-world only.
Can someone see my mistake? Any suggestions?
JSchmidt -
wrote on 26 Nov 2012, 16:45 last edited by
Okay, i could fix it by my own. My mistake was the usage of the out-of-box macro Q_SCRIPT_DECLARE_QMETAOBJECT() (Qt 4.8.1)
There is nothing wrong with it, despite the fact, that behind this macro, there is a boiler plate constructor for script objects which does not set the default prototype! Maybe you could see this as a bug? I don't know.
But more important, to use the defaultPrototype feature in the way I used it above, either you have to define your own constructor for script objects or, a more elegant way, define your own Q_SCRIPT_DECLARE_QMETAOBJECT() macro. Below there is my suggestion with defaultPrototype-Support:
original Q_SCRIPT_DECLARE_QMETAOBJECT() macro:
@
#define Q_SCRIPT_DECLARE_QMETAOBJECT(T, _Arg1)
template<> inline QScriptValue qscriptQMetaObjectConstructor<T>(QScriptContext *ctx, QScriptEngine *eng, T )
{
_Arg1 arg1 = qscriptvalue_cast<_Arg1> (ctx->argument(0));
T t = new T(arg1);
if (ctx->isCalledAsConstructor())
return eng->newQObject(ctx->thisObject(), t, QScriptEngine::AutoOwnership);
QScriptValue o = eng->newQObject(t, QScriptEngine::AutoOwnership);
o.setPrototype(ctx->callee().property(QString::fromLatin1("prototype")));
return o;
}
@improved suggestion with defaultPrototype-support:
@
#define Q_SCRIPT_DECLARE_QMETAOBJECT_DEFAULT_PROTOTYPE(T, _Arg1)
template<> inline QScriptValue qscriptQMetaObjectConstructor<T>(QScriptContext *ctx, QScriptEngine eng, T )
{
_Arg1 arg1 = qscriptvalue_cast<_Arg1> (ctx->argument(0));
T t = new T(arg1);
if (ctx->isCalledAsConstructor()) {
QScriptValue proto = eng->defaultPrototype(qMetaTypeId<T>());
QScriptValue u = eng->newQObject(ctx->thisObject(), t, QScriptEngine::AutoOwnership);
u.setPrototype(proto);
return u;
}
QScriptValue o = eng->newQObject(t, QScriptEngine::AutoOwnership);
o.setPrototype(ctx->callee().property(QString::fromLatin1("prototype")));
return o;
}
@All needed voodoo is to ask the ScriptEngine for the defaultPrototype using the template parameter T (line 7) and setting it as prototype of the newly created script object (line 9)
Maybe it is useful for someone out there!
1/2