Implementing Abstract Qt Interfaces

Discussion forum for C++ and script developers who are using the QCAD development platform or who are looking to contribute to QCAD (translations, documentation, etc).

Moderator: andrew

Forum rules

Always indicate your operating system and QCAD version.

Attach drawing files, scripts and screenshots.

Post one question per topic.

Post Reply
cjh
Active Member
Posts: 31
Joined: Tue Jul 12, 2016 4:51 pm

Implementing Abstract Qt Interfaces

Post by cjh » Wed Oct 19, 2016 8:44 pm

I'm having trouble implementing abstract Qt interfaces in javascript files.
Should I be doing this as a c++ plugin instead?

With this implementation of a QAbstractTableModel I get the error:
"Fatal: QAbstractTableModel::rowCount() is abstract!"

when I call tableView.setModel() with the BomModel.

Code: Select all

function BomModel(parent) {
    QAbstractTableModel.call(this, parent);
}

BomModel.prototype = new QAbstractTableModel();

BomModel.prototype.rowCount = function (qModelindex) {
    return 2;
}

BomModel.prototype.columnCount = function (qModelIndex) {
    return 3;
}

BomModel.prototype.data = function (index, role) {
    qDebug("data call");
    if (role === Qt.DisplayRole) {
        return ("Row%1, Column%2").arg(index.row()).arg(index.column());
    }
    return ;
}

User avatar
andrew
Site Admin
Posts: 9052
Joined: Fri Mar 30, 2007 6:07 am

Re: Implementing Abstract Qt Interfaces

Post by andrew » Thu Oct 20, 2016 8:06 am

The problem with base classes in JavaScript is that an instance is created as prototype object (new QAbstractTableModel()). This is not possible for abstract base classes. A C++ plugin could be used instead or to implement a non-abstract base class for your script class (with dummy implementations for the abstract functions).

For the abstract classes of the QCAD API, we provide adapter classes in src/scripting/ecmaapi/adapters which can be used as base classes for scripts instead of their abstract counterparts.

cjh
Active Member
Posts: 31
Joined: Tue Jul 12, 2016 4:51 pm

Re: Implementing Abstract Qt Interfaces

Post by cjh » Thu Oct 20, 2016 9:13 pm

Here's what I've come up with.

AbstractTableModelPlugin.h

Code: Select all

#ifndef ABSTRACTTABLEMODELPLUGIN_H
#define ABSTRACTTABLEMODELPLUGIN_H

#include <QObject>
#include <QDebug>
#include <QScriptEngine>
#include <QAbstractTableModel>
#include <string>

#include "RPluginInterface.h"

class AbstractTableModelAdapter : public QAbstractTableModel
{
Q_OBJECT
public:
    AbstractTableModelAdapter(QObject* parent = 0) : QAbstractTableModel(parent) {}
    ~AbstractTableModelAdapter() {}

    // The following functions are to be overridden
    // by javascript prototype.
    int rowCount(const QModelIndex &parent) const {
        Q_UNUSED(parent)
        return 10;
    }

    int columnCount(const QModelIndex &parent) const {
        Q_UNUSED(parent)
        return 10;
    }

    QVariant data(const QModelIndex &index, int role) const {
        Q_UNUSED(index)
        if (role == Qt::DisplayRole) {
            return QVariant("Row " + QString::number(index.row()) + " Col " + QString::number(index.column()));
        }
        return QVariant();
    }

//    QVariant headerData(int section, Qt::Orientation orientation, int role);
//    bool setData(QModelIndex &index, const QVariant &value, int role);


};
Q_DECLARE_METATYPE(AbstractTableModelAdapter*)

class AbstractTableModelPlugin : public QObject, public RPluginInterface
{
    Q_OBJECT
    Q_INTERFACES(RPluginInterface)
#if QT_VERSION >= 0x050000
    Q_PLUGIN_METADATA(IID "org.qcad.AbstractTableModelPlugin")
#endif

public:
    AbstractTableModelPlugin() : QObject() {}
    ~AbstractTableModelPlugin() {}

    virtual bool init();
    virtual void uninit(bool) {}
    virtual void postInit(InitStatus status);
    virtual void initScriptExtensions(QScriptEngine& engine);
    virtual RPluginInfo getPluginInfo();

static QScriptValue createAbstractTableModelAdapter(QScriptContext* context, QScriptEngine* engine);

};
Q_DECLARE_METATYPE(AbstractTableModelPlugin*)
#endif // ABSTRACTTABLEMODELPLUGIN_H
AbstractTableModelPlugin.cpp

Code: Select all

#include "AbstractTableModelPlugin.h"
#include "RVersion.h"
#include "REcmaHelper.h"
#include "RSettings.h"

bool AbstractTableModelPlugin::init() {
    qDebug() << "AbstractTableModelPlugin::init";
    return true;
}

void AbstractTableModelPlugin::postInit(InitStatus status) {
    if (status != RPluginInterface::AllDone) {
        return;
    }
}

void AbstractTableModelPlugin::initScriptExtensions(QScriptEngine &engine) {
    QScriptValue* proto = new QScriptValue(engine.newVariant(qVariantFromValue((AbstractTableModelAdapter*)0)));

    QScriptValue dpt = engine.defaultPrototype(qMetaTypeId<QAbstractTableModel*>());
    proto->setPrototype(dpt);

    engine.setDefaultPrototype(qMetaTypeId<AbstractTableModelAdapter*>(), *proto);

    QScriptValue ctor = engine.newFunction(AbstractTableModelPlugin::createAbstractTableModelAdapter, *proto, 0);
    engine.globalObject().setProperty("AbstractTableModelAdapter", ctor, QScriptValue::SkipInEnumeration);
}

RPluginInfo AbstractTableModelPlugin::getPluginInfo() {
    RPluginInfo ret;
    ret.set("Version", R_QCAD_VERSION_STRING);
    ret.set("ID", "AbstractTableModelPlugin");
    ret.set("Name", "AbstractTableModelPlugin");
    ret.set("License", "GPLv3");
    ret.set("URL", "http://qcad.org");
    return ret;
}

QScriptValue AbstractTableModelPlugin::createAbstractTableModelAdapter(QScriptContext* context, QScriptEngine* engine) {
    if (context->thisObject().strictlyEquals(engine->globalObject())) {
        return REcmaHelper::throwError(QString::fromLatin1("AbstractTableModelAdapter(): Did you forget to construct with 'new'?"), context);
    }

    // constructor without arguments:
    if (context->argumentCount() == 0) {
        qDebug() << "AbstractTableModelPlugin::create... arg count == 0";
        AbstractTableModelAdapter* cppResult = new AbstractTableModelAdapter();
        return engine->newQObject(context->thisObject(), cppResult);
    }
    else if (context->argumentCount() == 1) {
        qDebug() << "AbstractTableModelPlugin::create... arg count == 1";
                QObject* a0 = context->argument(0).toQObject();
                AbstractTableModelAdapter* cppResult = new AbstractTableModelAdapter(a0);
                return engine->newQObject(context->thisObject(), cppResult);
    } else {
        return REcmaHelper::throwError(QString::fromLatin1("AbstractTableModelAdapter(): no matching constructor found."), context);
    }

}
BomModel.js

Code: Select all

function BomModel(parent) {
    AbstractTableModelAdapter.call(this, parent);
}

BomModel.prototype = new AbstractTableModelAdapter();

BomModel.prototype.rowCount = function (qModelindex) {
    EAction.handleUserMessage("BomModel.rowCount()");
    return 2;
}

BomModel.prototype.columnCount = function (qModelIndex) {
    EAction.handleUserMessage("BomModel.columnCount()");
    return 3;
}

BomModel.prototype.data = function (index, role) {
    EAction.handleUserMessage("BomModel.data()");
    if (role === Qt.DisplayRole) {
        return ("Row%1, Column%2").arg(index.row()).arg(index.column());
    }
    return;
}
Is this along the lines of what you were thinking?
I haven't gotten my JS prototype functions to override the AbstractTableModelAdapter functions yet.

User avatar
andrew
Site Admin
Posts: 9052
Joined: Fri Mar 30, 2007 6:07 am

Re: Implementing Abstract Qt Interfaces

Post by andrew » Fri Oct 21, 2016 8:13 am

Yes, exactly. The adapter class can also come in handy if you want to turn events into signals which can then be connected in JS. I'm not sure if that makes any sense in this case though.

cjh
Active Member
Posts: 31
Joined: Tue Jul 12, 2016 4:51 pm

Re: Implementing Abstract Qt Interfaces

Post by cjh » Fri Oct 21, 2016 2:22 pm

Right - I see how the signals are working in the other adapter classes but I don't see any uses for them yet.

Would you expect the BomModel.prototype.rowCount() to override the definition in AbstractTableModelAdapter?

It currently isn't - I get a 10x10 table model as defined in AbstractTableModelAdapter.

User avatar
andrew
Site Admin
Posts: 9052
Joined: Fri Mar 30, 2007 6:07 am

Re: Implementing Abstract Qt Interfaces

Post by andrew » Fri Oct 21, 2016 2:35 pm

cjh wrote:Would you expect the BomModel.prototype.rowCount() to override the definition in AbstractTableModelAdapter?

It currently isn't - I get a 10x10 table model as defined in AbstractTableModelAdapter.
That depends how your script bindings look like. To allow proper inheritance, you'd have to implement a script shell which is instantiated instead of the actual C++ class. The shell then deals with inheritance related issues (call implementation from JS class if available or C++ class otherwise). The shell also avoids recursion when calling the C++ implementation from JS.

You can check out REcmaActionAdapter and REcmaShellActionAdapter as an example.

cjh
Active Member
Posts: 31
Joined: Tue Jul 12, 2016 4:51 pm

Re: Implementing Abstract Qt Interfaces

Post by cjh » Mon Oct 24, 2016 8:00 pm

Ahh I see! The generated ecma code is pretty powerful.
It took me a little while to follow through the generated code but I understand it now.

I'm not sure how much time I want to spend on learning all of the nuances of QtScript because its development has ceased.
Do you have any plans of moving toward QJSEngine (the QtScript replacement) with future versions of QCAD?

Connor

User avatar
andrew
Site Admin
Posts: 9052
Joined: Fri Mar 30, 2007 6:07 am

Re: Implementing Abstract Qt Interfaces

Post by andrew » Tue Oct 25, 2016 10:15 am

cjh wrote:Do you have any plans of moving toward QJSEngine (the QtScript replacement) with future versions of QCAD?
Not anytime soon. There are no script bindings for the Qt API for QJSEngine at this point.

Post Reply

Return to “QCAD Programming, Script Programming and Contributing”