Hybriding Qt with PyQt

A hybrid Qt and PyQt application sounds attractive: within a Qt application, parts for duty calculation are written in C++ , and UI is written in PyQt, making the application scriptable and expandable. To create such a program, we need to know how to do two sets of things: one is to embed PyQt into a C++ application, the other is to send information from PyQt back to C++. Above is an example. First line is in C++, and second is in PyQt. When the dial or slider is changed, numbers related field will be updated.

So how does it work?

Let’s start with the central widget of the main window.

Center::Center()
{
    setObjectName("center");
    QVBoxLayout* box = new QVBoxLayout();
    LeftWidget *leftDial = new LeftWidget();
    box->addWidget(leftDial);
    ShakeHole *rightDial = new ShakeHole();
    box->addWidget(rightDial);
    setLayout(box);
}

It has two widgets: a LeftWidget and a ShakeHole. (Sorry for the confusing names.)

First, the ShakeHole:

class ShakeHole : public QWidget
{
public:
    ShakeHole();
    ~ShakeHole() {}   
};
ShakeHole::ShakeHole()
{

    int wnd = winId();
        cout<<"winid "<<wnd<<endl;

    cout<<"version "<<Py_GetVersion()<<endl;

    FILE *fin = fopen("/Users/jianzhang/aphid/hybrid_qtpyqt/foo.py","r+");
    PyRun_SimpleFile(fin,"foo");

        PyObject *mainDict = PyModule_GetDict(PyImport_Import(PyString_FromString("__main__")));

        PyObject *claus = PyDict_GetItemString(mainDict, "Ui_Form");

        PyObject* snakewig = Py_BuildValue("i", wnd);

        PyObject * pTuple = PyTuple_New (1);
        PyTuple_SetItem (pTuple, 0, snakewig);

        PyObject *clausInstance = PyObject_CallObject(claus, pTuple);

        cout<<"finished python ui!\n";

        PyErr_Print();

}

It is derived from QWidget, and hooks a PyQt widget within via Python C API.

Now LeftWidget:

class LeftWidget : public HWidget
{
public:
    LeftWidget();
    void setAttribute(const char *attributeName, int value);
private:
    QLineEdit *attributeEdit;
    QLineEdit *attribute1Edit;
};

LeftWidget::LeftWidget()
{
    setObjectName("SnakeHole");
    QHBoxLayout* box = new QHBoxLayout();
    box->addWidget(new QLabel("qt host"));
    attributeEdit = new QLineEdit();
    box->addWidget(attributeEdit);
    attribute1Edit = new QLineEdit();
    box->addWidget(attribute1Edit);
    setLayout(box);

}

void LeftWidget::setAttribute(const char *attributeName, int value)
{
    QString attr(attributeName);
    if(attr == "fieldNumber")
    {
        QString str = QString("%1")
             .arg(value);
        attributeEdit->setText(str);
    }
    else if(attr == "sliderNumber")
    {
        QString str = QString("%1")
             .arg(value);
        attribute1Edit->setText(str);
    }
}

It is a derived HWidget, and it has two QLineEdit, which will be updated by setAttribute(). OK, so what is HWidget?

class HWidget : public QWidget
{
public:
    HWidget();
    virtual ~HWidget();
    virtual void setAttribute(const char *attributeName, int value);
};
void HWidget::setAttribute(const char *attributeName, int value)
{
    qDebug()<<"beep";
}

It is a derived QWidget as well, and it has a virtual function setAttribute() to override.

Go to the PyQt side:

from PyQt4 import QtCore, QtGui
import hoatzin

class Ui_Form(QtGui.QWidget):
    def __init__(self, parentId):      
        Form = QtGui.QWidget.find(parentId)
        super(Ui_Form, self).__init__(Form)
        self.resize(400, 100)
        self.host = hoatzin.Hoatzin()
        self.boxLayout = QtGui.QHBoxLayout()
        self.boxLayout.setObjectName('ui_form_box')
        self.boxLayout.setObjectName("boxLayout")
        self.boxLayout.addWidget(QtGui.QLabel('pyqt widget'))
        self.dial = QtGui.QDial()
        self.boxLayout.addWidget(self.dial)

        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.boxLayout.addWidget(self.slider)

        self.setLayout(self.boxLayout)

        self.dial.valueChanged.connect(self.dialTest)
        self.slider.valueChanged.connect(self.sliderTest)

    def dialTest(self):
        w = self.host.set_attribute_int('SnakeHole', 'fieldNumber', self.dial.value() )
        if w != 1:
            print 'SnakeHole not found'

    def sliderTest(self):
        w = self.host.set_attribute_int('SnakeHole', 'sliderNumber', self.slider.value() )
        if w != 1:
            print 'SnakeHole not found'

OK, it is a QWidget and will be attached to a specific parent. It has a dial and a slider, each of them will trigger a function by the signal of valueChanged. Wait a second! What on earth is hoatzin?

It is a Python extension to send information back to the host C++ program:

class Hoatzin
{
public:
    Hoatzin();

    int set_attribute_int(const char *widgetName, const char *attributeName, int value);

private:

};
int Hoatzin::set_attribute_int(const char *widgetName, const char *attributeName, int value)
{
    foreach (QWidget *widget, QApplication::allWidgets())
    {
            if(widget->objectName() == QString(widgetName))
            {
                HWidget *h = (HWidget *)widget;
                h->setAttribute(attributeName, value);
                return 1;        
            }          
    }
    return 0;
}

In set_attribute_int() function, it will search the specific QWidget by objectName(). If found, cast to HWidget, and call its setAttribute(). That is why the target widget should have a specific object name. When it calls “SnakeHole” to setAttribute(), the derived LeftWidget will be called.

Now you know all the tricks necessary for this stunt. The rest is how to compile and test.

First you will need file in https://github.com/spinos/aphid/tree/master/hoatzin

Follow the readme to build the Python module of hoatzin.

Second you need files in https://github.com/spinos/aphid/tree/master/hybrid_qtpyqt

Qmake will create the project for you. It links the compiled library of hoatzin, so no need to compile the source of HWidget again.

Just a confusing example, may be helpful.

Advertisements

Leave a comment

Filed under C++, Python, Qt

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s