Creating a newable QtScript object from C++ class and extend its functions via prototype



  • 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(); //works

    myFruit.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



  • 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!


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.