Scripting Qt

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

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

by Reginald Stadlbauer

This article covers several aspects of scriptingQt applications. There are two common approaches to scripting Qtapplications. One is to write entire applications in a scriptinglanguage instead of in C++. The other is to extend, automate, orcustomize a C++/Qt application; this article concentrates on thesecond approach.

Содержание

Writing an entire Qt application in a scripting language instead ofC++ is possible with script bindings such as PyQt and PerlQt. This isespecially useful for prototyping, for small programs, and for"throwaway" programs with a limited lifespan. But Qt is most powerfulwhen used through its native C++ API. Therefore the emphasis of thisarticle is on making C++/Qt applications that can be scripted sincethis is of most interest for developers of complex C++/Qtapplications.

[править] Why Make an Application Scriptable?

Scriptable applications provide many benefits compared with standardapplications, for example:

  • Extensibility for power users. When creating complex applicationssuch as scientific applications, it isn't always possible or desirableto implement all the functionality that users might want. Often itis better to implement the core functionality in C++ andprovide a scripting API so that users can build their ownfunctionality on top of what's provided as standard. This satisfiesthe customers' needs and keeps the core of the application clean andmaintainable.
  • Adding value for support engineers. Scriptable applications makeit easier to support customers. Technical support staff canimplement bug workarounds and additional functionality specific to aparticular customer or market. And by solving problems by scriptingthe application, the expense of changing the core application can beavoided or deferred.
  • Hybrid applications. Another way where scripting comes in handy isthe creation of hybrid applications. These applications consist of anumber of components implemented in C++ for maximum performance, butwhere the main application logic is implemented in a scriptinglanguage which provides a scripting API to the C++ components. Thisis especially useful when a variety of small programs need to bewritten that "glue" different components together. For example, a setof image filters might be available as C++ components that arecomposed together to perform particular tasks using a scriptinglanguage.
  • Batch processing. For applications that perform time-consumingtasks it is very important that in addition to the GUI a scriptinginterface is made available so that long-running tasks can beautomated or run as processes separate from the GUI.
  • Automated testing. To automatically test an application, it isnecessary to provide a scripting interface to the application so thatscripts can automatically exercise the application to verify that everything works as expected.


[править] Scripting Technologies

There are a few different scripting technologies available for Qtthat enable programmers to integrate scripting into C++/Qtapplications. While there are solutions available for many differentscripting languages, there are two dominant approaches toproviding Qt interfaces in a scripting language:

  • Dynamic script bindings
  • Generated script bindings

Here we will review both these technologies, discussing their pros andcons and the available implementations, and present a simple exampleof each type of approach.

[править] Dynamic Script Bindings

This approach makes use of Qt's Object Model, the Qt Meta Object System. This systemprovides introspection functionality to query the registered classdescriptions, properties, signals, and slots. Additionally it ispossible to dynamically invoke slots, access and modify propertyvalues, and connect and disconnect signals and slots. Dynamic scriptbindings make use of these Qt features by generically wrapping QObjects.

The big advantage of this approach is the minimal overhead of thescript bindings. Only one class, namely QObject, must be wrapped. Noextra bindings code needs to be generated (except the code generated bythe moc, but since this is necessary for Qt in any case, we don'tcount this as an overhead of the script bindings).

Another advantage is the flexibility of this approach. SinceQObjects are wrapped generically, any custom QObject implementedin the application or another library can be accessed via the scriptbindings without any additional effort.

But there is also a disadvantage that we can't ignore. Since thisapproach wraps the Qt Meta Object System, only QObject subclasses andfunctions (slots), properties and signals which are introspectable viathe Qt Object Model, are available to the scripting language. Thismeans that neither the complete Qt API nor the complete application API isavailable to script users. But depending on the usage case,this may not be a problem in practice. For example, when we're talkingabout scripting Qt applications, the scripting API should be limitedanyway. On the other hand, for creating Qt applications in a scriptinglanguage, developers would want access to the entire Qt API, somethingthat cannot be achieved by wrapping QObjects alone.

Currently there are two implementations of this approach available,both providing Qt bindings for a JavaScript interpreter:

  • QSA from Trolltech, available under a commercial license on allQt platforms, and under the GNU GPL for free software development onX11 and Mac OS X.
  • KJSEmbed, which is part of the KDE project and which is availableunder the GNU LGPL.

[править] Generated Script Bindings

Another approach to script bindings is to create bindings code. Thisis independent of Qt's Object Model and is therefore a bit moreindependent of the toolkit. The idea is that a code generator parsesthe code to be wrapped and generates code which makes use of thescripting language's C/C++API to create the bindings between Qt and the scripting language.

The biggest advantage of this approach is that the complete parsedAPI (the complete Qt API in our case) can be wrapped and the scriptbindings are not limited to the Qt Object Model.

But there's also a major disadvantage: the overhead involved. Sincecode must be generated for every wrapped class, the overhead isproportional to the amount of classes wrapped. This might not be aproblem when creating Qt applications via a scripting language, but itcertainly becomes an issue when providing an embedded scriptinterpreter to customize or automate C++/Qt applications. In thosecases, size certainly does matter. For embedding scripting languages,the overhead can be cut down by carefully selecting the classes towrap instead of blindly wrapping the entire Qt API.

Another disadvantage is that generated bindings cannot directly accessdynamic parts of the application, for example plugins, since nowrappers are generated for them. Dynamic bindings don't suffer fromthis limitation.

There are several different Qt bindings available implementing thisapproach. The most prominent ones are: PyQt (Python), PerlQt, andRubyQt.

[править] Examples

Now that we have discussed the different technologies that areavailable and their implementations we're ready to look at how thingsare done in practice. The decision about the implementation to chooseusually depends on several factors such as the preferred scriptinglanguage, the overhead of the bindings, the available support, and thequality of the documentation. Here we will look at a small examplewhere we take one of Qt's example programs and make it scriptable.This could be achieved using any of the approaches we've discussed,but for brevity, we'll show just two approaches, one using QSA to showthe dynamic approach, and one using PyQt to show the generatedapproach.

For our example, we will take Qt's application example and givethe user the possibility to access the "editor" widget (aQTextEdit) from a script.

[править] QSA Example

There are two simple steps to using QSA in an application. Firstly,make QSA available to the application, and secondly make use of QSAwithin the application.

The first step is achieved by adding the following line to theproject's .pro file (application.pro in our case):

load(qsa)

and by including the appropriate header files. In the case of ourapplication example, add the following headers toapplication.cpp:

#include <qsinputdialogfactory.h>
#include <qsinterpreter.h>
#include <qsproject.h>
#include <qsworkbench.h>

Now all we need to do to make the application scriptable is to add thefollowing code at the end of the ApplicationWindow class'sconstructor:

QSProject *project = new QSProject();
project->addObject(e); // e points to the QTextEdit
QSInputDialogFactory *factory = new QSInputDialogFactory();
project->interpreter()->addObjectFactory(factory);
QSWorkbench *wb = new QSWorkbench(project, this);
wb->open();

This starts by creating a scripting project. Then we add the editor(e) as a scriptable object to the project. It will be accessiblethrough its QObject name, which is "editor". We alsoinstantiate the input dialog factory, which gives us a convenient APIto display standard input and message dialogs. Finally we create andopen the QSA Workbench on the scripting project. QSA Workbench is a simple and convenient IDE (Integrated DevelopmentEnvironment) that provides a color syntax highlighting editor withsupport for code completion, in an environment that users can use tocreate, edit, save, load, and execute scripts.

If you don't want to open an extra window for editingscripts, or you don't want to overwhelm your users with a scriptingIDE, you could use the QSEditor class to embed a simple scripteditor into your application. There is also an API available to querythe functions available in a script; this is useful for example ifyou want to display them in a convenient menu through which the usercan choose a script function to execute. Now let's implement the following script which providesa simplistic count of the number of words in the editor:

function countWords()
{
    var text = editor.text;
    text = text.replace("\n", " ")
    var words = text.split(" ")
    MessageBox.information("Number of words: " + words.length)
}
center

This begins by retrieving the editor's text by accessing the textproperty of the editor object we added to the scripting project. Notethat it only took a single function call to make this object and allof its API available to the scripter. Then we replace all the newlines inthe text by spaces and split the text by spaces. This will return anarray containing all the words in the text. So the length property ofthe returned array equals the number of words in the editor. Finallywe display the information we've discovered by opening aMessageBox using QSA's convenient dialog API.

center

As a final step, the application should save the scripts the user wrote and load them the next time the application is started. In QSA, theQSProject class provides a simple mechanism for achieving this.All that is necessary is for the application to callQSProject::save() on exit, and to call QSProject::load() aspart of its initialization. In addition, the user can save and loadscripts for themselves using QSA Workbench.

This shows how simple it is with QSA's dynamic approach to make anapplication scriptable, and to give the script developer access toapplication objects and their APIs.

[править] PyQt Example

Unlike QSA, PyQt doesn't provide a solution to embed scripting into aQt application out-of-the-box. Nonetheless, it is not difficult toembed Python in a Qt application. For our example we're using the Pythonize library a simple C++ wrapper around Python's C API. Withthe following code in the ApplicationWindow constructor wecan embed a Python interpreter and initialize the PyQt bindings:

Pythonize *python = new Pythonize;
python->runString("from qt import *");

As mentioned earlier, there is no explicit support for scriptingQt applications in PyQt. This meansthat we can't just add anapplication object to make it accessible from the script. But we canwork around this by evaluating script code.Since we have access to the whole Qt API from Python, this is actuallyquite flexible. We want to provide the scripter with the editor objectjust like in the QSA example. This is achieved by evaluating thefollowing Python code in the initialization step:

python->runString("mw = qApp.mainWidget()");
python->runString("editor = mw.child('editor', 'QTextEdit')");

This retrieves the application's main widget and then retrieves themain widget's text editor child. The result is stored in theeditor variable which can now be accessed from scripts the userwrites.

Unlike QSA, PyQt doesn't provide an embeddable IDE, so we must createour own. A simple approach would be to create a widget with aQTextEdit for writing script code, and a "run" QPushButtonthat would call python->runString() on the QTextEdit'scontents. The code for this simple scripting console isstraightforward, so we'll omit it.

The script to count the number of words in the editor in Python wouldlook like this:

text = editor.text()
text = text.replace('\n', ' ')
words = str(text).split()
QMessageBox.information(None, 'Info', 'Number of words: %d' % len(words))

This is very similar to the QSA script, except that we've explicitlycast the text from a QString to a Python string to usePython's split() function.

Unfortunately, PyQt doesn't provide any equivalents to QSA'sQSProject::load() and QSProject::save() functions, so you mustwrite your own code to load and save PyQt scripts.

While PyQt isn't explicitly designed for application scripting, thisexample shows that PyQt can be used as an application scriptingsolution.

[править] Conclusion

This article shows that scripting and Qt play very well together.There are different solutions available which give Qt developers verypowerful tools to create scriptable applications that are moreflexible and customizable than standard applications.