Embedding Python into Qt Applications

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 23 | Документация


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 forembedding scripting languages into commercial Qt applications: QSA (JavaScript2.0) from Trolltech and PyQt (Python) from Riverbank Computing.The Scripting Qtarticle in Qt Quarterly issue 10 gives a good overview of QSA, PyQt and someother solutions in action.

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

  • QtScript, an ECMAScript interpreter with Qt bindings, is shipped as partof Qt 4.3.
  • PythonQt, used by MeVisLab, is a dynamic script binding to the Pythoninterpreter.

While both QtScript and PythonQt make it very easy to embed scripting intoyour existing Qt Application, this article will focus on the PythonQtbinding, 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:

  • A well designed application interface can provide an easy access pointfor both novice and power users.
  • Applications can be easily extended without requiring users to have deepC++ knowledge.
  • Scripting makes it easy to create macros and do batch processing.
  • Automated testing can be realized with scripting.
  • Scripting is cross-platform, so the scripts will work on all platformsthe application runs on.
  • Scripting can speed up the prototyping stage a lot; e.g., your supportteam can add a feature/workaround by scripting much faster than it would take todevelop it in C++ and redeploy the application.

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

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

  • Easy integration into an existing Qt application.
  • Based on a well-known scripting language, so that new users do not needto learn a new language.
  • Good integration with the Qt framework; e.g., it should know aboutsignals, slots and properties.
  • Support for marshalling data types between the scripting language and Qt,ideally supporting all QVariant types.
  • Support for debugging---when scripts get bigger, debugging becomes acrucial feature.

[править] About PythonQt

Unlike PyQt and Qt Jambi, PythonQt is not designed to provide support fordevelopers writing standalone applications. Instead, it provides facilitiesto embed a Python interpreter and focuses on making it easy to expose partsof the application to Python.

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

The following sections will highlight some core features of PythonQt. For adetailed 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 integratePythonQt 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 thePython interpreter itself. We then obtain a smart pointer(PythonQtObjectPtr) to Python's __main__ module (this is where thescripts 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 APIof your application that best suits your users, developers and supportstaff. Basically, you invent a domain-specific language for your applicationthat 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 toPython and then let the users, developers or support staff create smallscripts to change aspects of your applications via scripting.

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

Creating specialized API objects is often the preferred solution, making iteasier to keep a stable interface and to document what parts of theapplication 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 objectscontaining arbitrary QVariant values.

About Python

Python (http://www.python.org) is an object-oriented programming language with a growing user community and a huge set of standard modules.

Python was designed to be "extended and embedded" easily. Libraries written in C and C++ can be wrapped for use by Python programs, and the interpreter can be embedded into applications to provide scripting services.

There are several well known applications which support Python scripting:

[править] GUI Scripting

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

center

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.php = '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 QStringwhen it is assigned to a QString property.

Signals from C++ objects can be connected to Python functions. We definea normal function, and we connect the button's clicked() signal andthe 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 themain module. Here, we have included the script as a file in Qt's resourcesystem, 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 textfrom the line edit is appended to the text in the browser using the PythonappendLine() 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 beable to create new objects of certain types to supply to the application.

To meet this need, PythonQt contains a Python module named PythonQtwhich you can use to access constructors and static members of all knownobjects. 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 onlyslots are callable from Python. There is no way to do dynamic scripting onC++ methods because Qt's meta-object compiler (moc) does not generaterun-time information for them.

PythonQt introduces the concept of the "decorator slot", which reuses themechanism used to dynamically invoke Qt slots to support constructors,destructors, static methods and non-static methods in a very straightforwardway. The basic idea is to introduce new QObject-derived"decorator objects" (not to be confused with Python's own decorators) whose slotsfollow 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 additionalmembers 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 &amp;p)
            { return QSize(p.x(), p.y()); }
 
        QPushButton *new_QPushButton(const QString &amp;text,
                                     QWidget *parent = 0)
            { return new QPushButton(text, parent); }
 
        QWidget *static_QWidget_mouseGrabber()
            { return QWidget::mouseGrabber(); }
 
        void move(QWidget *w, const QPoint &amp;p) { w->move(p); }
        void move(QWidget *w, int x, int y) { w->move(x, y); }
    };

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

  • A QSize variant constructor that takes a QPoint.
  • A constructor for QPushButton that takes a string and a parentwidget.
  • A new static mouseGrabber() method for QWidget.
  • An additional slot for QWidget, making move() callable.(move() is not a slot in QWidget.)
  • An additional slot for QWidget, overloading the above move()method.

The decorator approach is very powerful, since it allows new functionalityto be added anywhere in your class hierarchy, without the need to handleargument conversion manually (e.g., mapping constructor arguments from Pythonto Qt). Making a non-slot method available to PythonQt becomes a one-linestatement 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'thave space to cover here. Some of the most interesting are:

  • Wrapping of non- QObject-derived C++ objects.
  • Support for custom QVariant types.
  • An interface for creating your own import replacement so that, forexample, Python scripts can be signed/verified before they are executed.

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

[править] Future Directions

PythonQt was written to make MeVisLab scriptable, and has now reached asatisfactory level of stability. It makes it very easy to embed Python scriptingsupport into existing Qt applications that do not require the extensivecoverage 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 sourceproject 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 atflorian (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.