Embedding Python into Qt Applications

by Florian Link

Embedding scripting languages into C++ applications has become very common. Alongside many mainstream products, such as Microsoft Office and Macromedia Director, there is a growing trend with smaller, more specialized applications to offer integrated scripting to their users.

For several years, there have been only two mainstream solutions for embedding scripting languages into commercial Qt applications: QSA (JavaScript 2.0) from Trolltech and PyQt (Python) from Riverbank Computing. The Scripting Qt article in Qt Quarterly issue 10 gives a good overview of QSA, PyQt and some other solutions in action.

There have been some developments since that article was written, and, as of today, there are two new script bindings to look at:

While both QtScript and PythonQt make it very easy to embed scripting into your existing Qt Application, this article will focus on the PythonQt binding, leaving an in-depth look at QtScript for a later article to cover.

The Benefits of Scripting

Making a C++ application scriptable has several benefits:

Scripting APIs can range from a simple interface that allows activities such as batch processing of common application tasks to a fully-fledged interface that allows the user to customize/extend the menus and dialogs, and even to access the core functionality of the application (e.g., JavaScript in Web Browsers).

When considering scripting solutions for Qt applications, the following features are considered to be beneficial:

About PythonQt

Unlike PyQt and Qt Jambi, PythonQt is not designed to provide support for developers writing standalone applications. Instead, it provides facilities to embed a Python interpreter and focuses on making it easy to expose parts of the application to Python.

PythonQt makes extensive use of the Qt 4 meta-object system. Thus, PythonQt can dynamically script any QObject without any prior knowledge of it, using only the information supplied by QMetaObject (defined using Qt's moc tool in C++ applications). This dynamic approach allows several different script bindings to be embedded in the same application, each of which can use the same scripting API; e.g., JavaScript (QSA or QtScript) and Python.

The following sections will highlight some core features of PythonQt. For a detailed description of PythonQt's features and more sophisticated examples, visit the project's website.

Getting started

The following example shows the steps that are needed to integrate PythonQt with your Qt Application.

    #include "PythonQt.h"
    #include <QApplication>
    
    int main(int argc, char **argv)
    {
        QApplication qapp(argc, argv);
    
        PythonQt::init();
        PythonQtObjectPtr mainModule = 
                          PythonQt::self()->getMainModule();
        QVariant result = mainModule.evalScript(
                        mainModule, "19*2+4", Py_eval_input);
        return 0;
    }

We first initialize a PythonQt singleton, which in turn initializes the Python interpreter itself. We then obtain a smart pointer (PythonQtObjectPtr) to Python's __main__ module (this is where the scripts will be run) and evaluate some Python code in this module.

The result variable in this case will contain 42, evaluated by Python.

Creating an Application Scripting API

The art of application scripting is to find the level of detail for the API of your application that best suits your users, developers and support staff. Basically, you invent a domain-specific language for your application that makes it easy for your users to access exactly the features they want, without needing to have a C++ compiler to hand.

A typical use case of PythonQt is to expose a single application object to Python and then let the users, developers or support staff create small scripts to change aspects of your applications via scripting.

Typically, you will create a new QObject-derived API class and use it as an adapter to various other classes in your application. You may also expose any existing QObjects of your application directly, but normally this exposes too many details to the script users, and it forces you to keep the interfaces of all exposed classes stable---otherwise your user's scripts will break or behave in unexpected ways.

Creating specialized API objects is often the preferred solution, making it easier to keep a stable interface and to document what parts of the application a script can access. PythonQt supports all QVariant types, so you can create rich application APIs that return simple types such as QDateTime and QPixmap, or even hierarchical QVariantMap objects containing arbitrary QVariant values.

GUI Scripting

Let's consider a simple example in which we create a small Qt user interface containing a group box, which we expose to Python under the name "box".

Pythonqt-Gui

The C++ code to define the user interface looks like this:

    QGroupBox *box = new QGroupBox;
    QTextBrowser *browser = new QTextBrowser(box);
    QLineEdit *edit = new QLineEdit(box);
    QPushButton *button = new QPushButton(box);
    
    button->setObjectName("button1");
    edit->setObjectName("edit");
    browser->setObjectName("browser");
    
    mainModule.addObject("box", box);

Now let us create a Python script that uses PythonQt to access the GUI. Firstly, we can see how easy it is to access Qt properties and child objects:

    # Set the title of the group box via the title property.
    box.title = 'PythonQt Example'

    # Set the HTML content of the QTextBrowser.
    box.browser.html = 'Hello <b>Qt</b>!'

    # Set the title of the button.
    box.button1.text = 'Append Text'

    # Set the line edit's text.
    box.edit.text = '42'

Note that each Python string is automatically converted to a QString when it is assigned to a QString property.

Signals from C++ objects can be connected to Python functions. We define a normal function, and we connect the button's clicked() signal and the line edit's returnPressed() signal to it:

    def appendLine():
        box.browser.append(box.edit.text)

    box.button1.connect('clicked()', appendLine)
    box.edit.connect('returnPressed()', appendLine)

The group box is shown as a top-level widget in the usual way:

    box.show()

To evaluate the above script, we need to call a special function in the main module. Here, we have included the script as a file in Qt's resource system, so we specify it with the usual ":" prefix:

    mainModule.evalFile(":GettingStarted.py");

Now, whenever you press return in the line edit or click the button, the text from the line edit is appended to the text in the browser using the Python appendLine() function.

The PythonQt Module

Scripts often need to do more than just process data, make connections, and call functions. For example, it is usually necessary for scripts to be able to create new objects of certain types to supply to the application.

To meet this need, PythonQt contains a Python module named PythonQt which you can use to access constructors and static members of all known objects. This includes the QVariant types and the Qt namespace.

Here are some example uses of the PythonQt module:

    from PythonQt import *

    # Access enum values of the Qt namespace.
    print Qt.AlignLeft

    # Access a static QDate method.
    print QDate.currentDate()

    # Construct a QSize object
    a = QSize(1,2)

Decorators and C++ Wrappers

A major problem that comes with the dynamic approach of PythonQt is that only slots are callable from Python. There is no way to do dynamic scripting on C++ methods because Qt's meta-object compiler (moc) does not generate run-time information for them.

PythonQt introduces the concept of the "decorator slot", which reuses the mechanism used to dynamically invoke Qt slots to support constructors, destructors, static methods and non-static methods in a very straightforward way. The basic idea is to introduce new QObject-derived "decorator objects" (not to be confused with Python's own decorators) whose slots follow the decorator naming convention and are used by PythonQt to make, for example, normal constructors callable.

This allows any C++ or QObject-derived class to be extended with additional members anywhere in the existing class hierarchy.

The following class definition shows some example decorators:

    class PyExampleDecorators : public QObject
    {
        Q_OBJECT
    
    public slots:
        QVariant new_QSize(const QPoint &p)
            { return QSize(p.x(), p.y()); }
    
        QPushButton *new_QPushButton(const QString &text,
                                     QWidget *parent = 0)
            { return new QPushButton(text, parent); }
    
        QWidget *static_QWidget_mouseGrabber()
            { return QWidget::mouseGrabber(); }
    
        void move(QWidget *w, const QPoint &p) { w->move(p); }
        void move(QWidget *w, int x, int y) { w->move(x, y); }
    };

After registering the above example decorator with PythonQt (via PythonQt::addDecorators()), PythonQt now supplies:

The decorator approach is very powerful, since it allows new functionality to be added anywhere in your class hierarchy, without the need to handle argument conversion manually (e.g., mapping constructor arguments from Python to Qt). Making a non-slot method available to PythonQt becomes a one-line statement which can just be a case of forwarding the call to C++.

Other Features

PythonQt also provides a number of other "advanced" features that we don't have space to cover here. Some of the most interesting are:

The examples supplied with PythonQt cover many of the additional features we have not addressed in this article.

Future Directions

PythonQt was written to make MeVisLab scriptable, and has now reached a satisfactory level of stability. It makes it very easy to embed Python scripting support into existing Qt applications that do not require the extensive coverage of the Qt API that PyQt provides.

I would like to thank my company MeVis Research, who made it possible for me to release PythonQt as an open source project on SourceForge, licensed under the GNU Lesser General Public License (LGPL). See the project's home page at pythonqt.sourceforge.net for more information.

I am looking for more developers to join the project. Please contact me at florian (at) mevis.de if you would like to contribute!

The full source code for the examples shown in this article is available as part of the PythonQt package, available from SourceForge.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2007 Trolltech Trademarks