SuperHybrids part 2, now Qt + PySide

I would like to return to a topic that I opened few months ago – about creation of “hybrid” applications consisting of one part of c++ code and another part is python based, plus both part access each other with Qt-like Api, so they understand QString, etc and can exchange qt signals between those two parts. Even more, c++ part of application can generate python qt-like code to execute during runtime, he-he… In my previous post I explained how to implement this based on PyQt libraries. Also it can be interpreted as embedding PySide into Qt applications.

I got a lot of questions about this ideas, but one of frequent questions was about doing same with PySide. Well, PySide is LGPL so this makes the framework applicable for much more projects. So, let’s have a try of doing same but with PySide now.

First, I tried binary packages, but looks like they do not have generatorrunner executable included and other stuff for making bindings for Qt-like libraries – let’s wish for maintainers to include them. So, we need to build it manually. Needed CMake, installed Qt Frameworks, Python.

So I followed instructions listed on PySide web site and got it after hour-two. Let’s better show step-by-step for sure (I have done it on Mac OS 10.6, Qt 4.7 binaries from Nokia, Python 2.6):

# git clone git://gitorious.org/pyside/apiextractor.git
# cd apiextractor
# mkdir build && cd build
# cmake .. && make && make install && cd ../..
#
# git clone git://gitorious.org/pyside/generatorrunner.git
# cd generatorrunner
# mkdir build && cd build
# cmake .. && make && make install && cd ../..
#
# git clone git://gitorious.org/pyside/shiboken.git
# cd shiboken
# mkdir build && cd build
# cmake .. && make && make install && cd ../..
#
# git clone git://gitorious.org/pyside/pyside.git
# cd pyside
# mkdir build && cd build
# cmake .. && make && make install && cd ../..

Now we have additionaly generatorrunner and typesystems needed for making wrappings to c++ mudules.

Let’s return to our hybrid application – create next folder structure:

HybridApp/
 |-data/
 |    |-global.h
 |    |-typesystem.xml
 |-hybrid/
 |    |-MainWindow.h
 |    |-MainWindow.cpp
 |    |-hybrid.pro
 |-hybridpy/
 |    |-hybridpy.pro
 |-build.sh
 |-Main.py
 

Let’s explain:

  • Folder hybrid contains c++ part of application which is built into shared lib.
  • Folder hybridpy contains wrapping for c++ part – it builds Python module which will be imported into python part of application.
  • Folder data contains definition of typesystem used in c++ part. This is Xml file which describes types of used objects and their especialities when converting into python objects. More details about typesystems – on pyside page with following links.

Now we go into hybrid folder and create c++ part of hybrid. First let’s do it as c++ only application with own main() methon.

hybrid/hybrid.pro

TEMPLATE = app
CONFIG += qt
QT += core gui

UI_DIR = build
RCC_DIR = build
MOC_DIR = build
OBJECTS_DIR = build

HEADERS += MainWindow.h
SOURCES += MainWindow.cpp Main.cpp

hybrid/Main.cpp:

#include <QtGui>
#include "MainWindow.h"
int main(int argc, char ** argv) {
    QApplication app(argc, argv);
    MainWindow window;
    window.resize(1000,700);
    window.show();
    return app.exec();
}

hybrid/MainWindow.h:

#ifndef MainWindow_H
#define MainWindow_H

#include <QMainWindow>
class QPushButton;
class QGraphicsView;
class QGraphicsScene;
class QPlainTextEdit;

class MainWindow : public QMainWindow { Q_OBJECT
public:
    MainWindow(QWidget * parent = 0L);
    virtual ~MainWindow();

signals:
    void runPythonCode(QString);

private slots:
    void runPythonCode();

public:
    QGraphicsView * viewer;
    QGraphicsScene * scene;
    QPlainTextEdit * editor;
    QPushButton * pb_commit;
};
#endif // MainWindow_H

hybrid/MainWindow.cpp:

#include <QtGui>
#include "MainWindow.h"

MainWindow::MainWindow(QWidget * parent):QMainWindow(parent) {
    QSplitter * splitter = new QSplitter;
    setCentralWidget(splitter);

    QWidget * editorContent = new QWidget;
    splitter->addWidget(editorContent);

    QVBoxLayout * layout = new QVBoxLayout;
    editorContent->setLayout(layout);

    editor = new QPlainTextEdit;
    layout->addWidget(editor);

    pb_commit = new QPushButton(tr("Commit"));
    connect(pb_commit, SIGNAL(clicked()), 
            this, SLOT(runPythonCode()));
    layout->addWidget(pb_commit);

    scene = new QGraphicsScene(this);
    viewer = new QGraphicsView;
    viewer->setScene(scene);
    splitter->addWidget(viewer);

    splitter->setSizes(QList<int>() << 400 << 600);
}

MainWindow::~MainWindow() {;}

void MainWindow::runPythonCode() {
    emit runPythonCode(editor->toPlainText());
}

As result we have a window with editor (left) and canvas (right):
MainWindow

Now it is time to to turn it into PySide-based application. We are going to:

  • Change c++ part to dynamic library
  • Create wrappings for MainWindow class
  • Make Main.py instead of C main() routine

For first point we just change TEMPLATE to lib, exclude Main.cpp, and build the shared lib. Also set TARGET to have shared libraries in root folder of HybridApp

hybrid/hybrid.pro:

TEMPLATE = lib
TARGET = ../Hybrid
CONFIG += qt
QT += core gui

UI_DIR = build
RCC_DIR = build
MOC_DIR = build
OBJECTS_DIR = build

HEADERS += MainWindow.h
SOURCES += MainWindow.cpp

As result you should get libHybrid.dylib in root of our project (HybrydApp)

Now it is time to generate wrappings for c++ part to use in python. The generation is made by build.sh script in root of project. (Actually it builds everything – both c++ and wrapping parts)

build.sh

#!/bin/sh

cd hybrid
qmake
make
cd ..

cd hybridpy

QTGUI_INC=/Library/Frameworks/QtGui.framework/Versions/4/Headers
QTCORE_INC=/Library/Frameworks/QtCore.framework/Versions/4/Headers
QTTYPESYSTEM=/usr/local/share/PySide/typesystems

generatorrunner --generatorSet=shiboken \
    ../data/global.h \
    --include-paths=../hybrid:$QTCORE_INC:$QTGUI_INC:/usr/include \
    --typesystem-paths=../data:$QTTYPESYSTEM \
    --output-directory=. \
    ../data/typesystem.xml

qmake
make
cd ..

rm -rf PyHybrid.so
ln -s libPyHybrid.dylib PyHybrid.so

You see that this is just one call of generatorrunner plus you provide paths for Qt includes, qt typesystem and your typesystem. Then in folder hybridpy we with qmake build the python module.

Listing of our typesystem.xml and global.h:

data/typesystem.xml

<?xml version="1.0"?>
<typesystem package="PyHybrid">
    <load-typesystem name="typesystem_core.xml" generate="no"/>
    <load-typesystem name="typesystem_gui.xml" generate="no"/>
    <object-type name="MainWindow"/>
</typesystem>

data/global.h (fyi: more details about global.h – here)

#undef QT_NO_STL
#undef QT_NO_STL_WCHAR

#ifndef NULL
#define NULL    0
#endif

#include <MainWindow.h>

And pro file for building python module:

hybridpy/hybridpy.pro

TEMPLATE = lib
QT += core gui

INCLUDEPATH += hybrid
INCLUDEPATH += ../hybrid

INCLUDEPATH += /usr/include/python2.6
INCLUDEPATH += /usr/local/include/shiboken
INCLUDEPATH += /usr/local/include/PySide
INCLUDEPATH += /usr/local/include/PySide/QtCore
INCLUDEPATH += /usr/local/include/PySide/QtGui

LIBS += -ldl -lpython2.6
LIBS += -lpyside
LIBS += -lshiboken
LIBS += -L.. -lHybrid

TARGET = ../PyHybrid

SOURCES += \
    pyhybrid/pyhybrid_module_wrapper.cpp \
    pyhybrid/mainwindow_wrapper.cpp \

You can mention that most of used paths of python headers and qt headers can be extracted from utils like pkg-config etc – that’s true, but I showed exact paths intentionally just for demoing what is really included. Also, of course you can do it all with cmake, but I made a choice for qmake pro files to have better (plain) presentation of used files and logic.

Looks complete – if you just run build.sh you will get PyHybrid.so – python module.
Everything is ready, time to connect both parts in Main.py script:

Main.py

import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PyHybrid import *

class RunScript(QObject):
    def __init__(self, mainWindow):
        QObject.__init__(self)
        self.mainWindow = mainWindow

    def runScript(self, script):
        mainWindow = self.mainWindow
        exec(str(script))

a = QApplication(sys.argv)
w = MainWindow()
r = RunScript(w)
w.setWindowTitle('PyHybrid')
w.resize(1000,800)
w.show()
a.connect(w, SIGNAL('runPythonCode(QString)'), r.runScript)
a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()') )
a.exec_()

If you run it you will get same window as in c++ application except that python code entered in left panel with editor can be executed just here and the python code has control of our c++ part.

Let’s try small code snippets like:

mainWindow.statusBar().show()

Oh! status bar is appeared in our application. Well, more complex maybe – let’s change background of canvas:

mainWindow.scene.setBackgroundBrush(QColor('#e0e0ff'))

Can we create new objects? Of course yes:

li1 = QGraphicsLineItem(10,10, 500,500)
li1.setPen(QPen(QBrush(QColor("#ff0000")), 3.0, Qt.DashLine))
mainWindow.scene.addItem(li1)

Example with a line

Can we create new classes just in real time and instanciate them???
Sure yes!!!. Let’s create subclass of QGraphicsItem and make custom painting:

mainWindow.viewer.setRenderHint(QPainter.Antialiasing)
class MyItem(QGraphicsItem):
	def boundingRect(self):
		return QRectF(-100,-100,200,200)

	def paint(self, painter, option, widget):
		g = QLinearGradient(-100,-100, 100,100)
		g.setColorAt(0, QColor('#00ff00'))
		g.setColorAt(1, QColor('#ffffff'))
		painter.setBrush(g)
		p = QPen(QBrush(QColor("#ff0000")), 4, Qt.DashLine)
		painter.setPen(p)
		painter.drawRoundedRect(-100,-100,200,200, 30,30)

my1 = MyItem()
mainWindow.scene.addItem(my1)
my1.setPos(200,200)

Example with custom class

Well, so we can do everything with our small application – we can program it in real time. We can even generate code by program itself to execute it and more. Anyway, this also can be used for performance balancing when you can quickly prototype your code in python, and then transfer such code into C++ to make it running fast.

Good example – developing canvas-based games, when it can be really helpful and convenient to work with python code to make the logic, drawing etc and then migrate some code into C++.

Also it is very good for mobile devices – you program with python-pyside until you encounter performance issues and in this point you can easily separate critical part into c++ and keep Qt-like interface between these two parts.

Regarding our small application above – you can ask What is the difference to just programming with your editor?You are coding your application while it is running. This is very new approach to programming with Qt. There is one especiality – you can loose your programming when exit :), So, it makes sense to modify the application to save all your code snippets to files.

It’s up to you.

I would like to thanks to PySide team. They are doing really great work and potential of PySide looks very exciting to be used in many projects.

This entry was posted in Blog, Embedding, Hybrids, PySide, Qt, Research. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • http://design-reich.com Dan

    Great. Awesome. That’s what I needed to start with PySide.
    I need exactly this solution for some test application.

  • http://fmilo.blogspot.com/ Fabrizio

    You may want to suggest to download the latest tag version using git .
    git tag -l # list tags
    git checkout

  • Ernesto

    Hi budy, awsome job u did. I tried this demo but using Ubuntu, things were quite successful except that when I execute Main.py it returns this error to me:

    $ python Main.py
    Traceback (most recent call last):
    File “Main.py”, line 17, in
    w.setWindowTitle(‘PyHybrid’)
    AttributeError: ‘PyHybrid.MainWindow’ object has no attribute ‘show’
    Segmentation fault

    Any idea about that??

    Thanks in advance

  • yshurik

    Because it does not see show() method, I assume you have not bound correct with Qt typesystem. Have a look and check QTTYPESYSTEM in build.sh – make sure you have these files because they exactly describe methods and properties of Qt classes

  • Ernesto

    I’ve fixed this problem. The point was that generatorrunner was not asumming to know QtGui inside /usr/include path in –include-paths so I tried to fixit putting /usr/include/qt4 and din’t work. So it worked successfully putting /usr/include/qt4/QtGui explicitly. I don’t know why its happening because generator runner should find qt include subdirs automatically. Nevertheless I’m still having this kind of warnings.

    Any idea is welcome.

    $ sh build.sh
    make: Nothing to be done for `first’.
    Generating class model… [OK]
    Generating enum model… [OK]
    Generating namespace model… [OK]
    Resolving typedefs… [OK]
    Fixing class inheritance… [OK]
    Detecting inconsistencies in class model… [OK]
    Detecting inconsistencies in typesystem… [WARNING]
    type ‘QAbstractPageSetupDialog’ is specified in typesystem, but not defined. This could potentially lead to compilation errors.
    type ‘QSortFilterProxyModel’ is specified in typesystem, but not defined. This could potentially lead to compilation errors.
    type ‘QSpacerItem’ is specified in typesystem, but not defined. This could potentially lead to compilation errors.

  • yshurik

    2 Ernesto: why not ask PySide people? their build system could change meanwhile…

  • Ernesto

    Ok, thanks I´ll ask aorund pyside comunity

  • Bruno Tezine

    Hi, I’m trying to run the build.sh but it fails with the message below: How can I create the Qt typesystem? Is this why I’m getting these errors?
    Thanks/Bruno.

    $ ./build.sh
    Fatal error: line=3, column=62, message=Failed to parse: ‘typesystem_core.xml’
    Cannot parse file: ../data/typesystem.xmlWARNING: Failure to find: pyhybrid/pyhybrid_module_wrapper.cpp
    WARNING: Failure to find: pyhybrid/mainwindow_wrapper.cpp
    Makefile:121: *** multiple target patterns. Stop.

  • Den Sh

    Thanks for a great article!
    If anyone is still interested i’ve managed to make
    it work with latest python2.7 and pyside from
    macports: fetch it here.

  • strattonbrazil

    Is it possible to use pyside for calling python from C++ so one doesn’t have to start the application from python?

  • Anton Gusev

    $./build.shn…ngeneratorrunner: Error loading generator-set plugin: shiboken_generator module not found.n…nnHow to access for shiboken module? Debian wheezy linux. PySide and others installed, “HelloWorld” example run successfully.

  • Anton Gusev
  • Anton Gusev

    So generatorrunner can’t see shiboken and I edit build.sh as:nn#generatorrunner –generatorSet=shiboken
    # ../data/global.h
    # –include-paths=../cps::/usr/include:/usr/local/include
    # –typesystem-paths=../data:
    # –output-directory=.
    # ../data/typesystem.xmlnnshiboken
    –include-paths=../hybrid:::/usr/include
    –typesystem-paths=../data:
    –output-directory=.
    ../data/global.h
    ../data/typesystem.xmlnnNow all libs build fine, but when I try run: nn Main.pynTraceback (most recent call last):n File “Main.py”, line 4, in n from PyHybrid import *nImportError: libHybrid.so.1: cannot open shared object file: No such file or directorynn$ ls libHybrid.so.1nlibHybrid.so.1nn

  • Anton Gusev

    ok, do this:n LD_LIBRARY_PATH=/home/user/devel/HybridApp:

  • Pingback: Downtown Doug Brown » Creating Python bindings for a Qt library in Ubuntu 14.04()

  • Jack Diddet

    how to generate wrapper ,by generatorrunner ? please tell me where to donwnload binary