by Reginald Stadlbauer |
This article covers several aspects of scripting Qt applications. There are two common approaches to scripting Qt applications. One is to write entire applications in a scripting language instead of in C++. The other is to extend, automate, or customize a C++/Qt application; this article concentrates on the second approach.
Writing an entire Qt application in a scripting language instead of C++ is possible with script bindings such as PyQt and PerlQt. This is especially useful for prototyping, for small programs, and for "throwaway" programs with a limited lifespan. But Qt is most powerful when used through its native C++ API. Therefore the emphasis of this article is on making C++/Qt applications that can be scripted since this is of most interest for developers of complex C++/Qt applications.
Why Make an Application Scriptable? |
Scriptable applications provide many benefits compared with standard applications, for example:
Scripting Technologies |
There are a few different scripting technologies available for Qt that enable programmers to integrate scripting into C++/Qt applications. While there are solutions available for many different scripting languages, there are two dominant approaches to providing Qt interfaces in a scripting language:
Dynamic Script Bindings |
This approach makes use of Qt's Object Model, the Qt Meta Object System. This system provides introspection functionality to query the registered class descriptions, properties, signals, and slots. Additionally it is possible to dynamically invoke slots, access and modify property values, and connect and disconnect signals and slots. Dynamic script bindings make use of these Qt features by generically wrapping QObjects.
The big advantage of this approach is the minimal overhead of the script bindings. Only one class, namely QObject, must be wrapped. No extra bindings code needs to be generated (except the code generated by the moc, but since this is necessary for Qt in any case, we don't count this as an overhead of the script bindings).
Another advantage is the flexibility of this approach. Since QObjects are wrapped generically, any custom QObject implemented in the application or another library can be accessed via the script bindings without any additional effort.
But there is also a disadvantage that we can't ignore. Since this approach wraps the Qt Meta Object System, only QObject subclasses and functions (slots), properties and signals which are introspectable via the Qt Object Model, are available to the scripting language. This means that neither the complete Qt API nor the complete application API is available to script users. But depending on the usage case, this may not be a problem in practice. For example, when we're talking about scripting Qt applications, the scripting API should be limited anyway. On the other hand, for creating Qt applications in a scripting language, developers would want access to the entire Qt API, something that cannot be achieved by wrapping QObjects alone.
Currently there are two implementations of this approach available, both providing Qt bindings for a JavaScript interpreter:
Generated Script Bindings |
Another approach to script bindings is to create bindings code. This is independent of Qt's Object Model and is therefore a bit more independent of the toolkit. The idea is that a code generator parses the code to be wrapped and generates code which makes use of the scripting 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 parsed API (the complete Qt API in our case) can be wrapped and the script bindings are not limited to the Qt Object Model.
But there's also a major disadvantage: the overhead involved. Since code must be generated for every wrapped class, the overhead is proportional to the amount of classes wrapped. This might not be a problem when creating Qt applications via a scripting language, but it certainly becomes an issue when providing an embedded script interpreter to customize or automate C++/Qt applications. In those cases, size certainly does matter. For embedding scripting languages, the overhead can be cut down by carefully selecting the classes to wrap instead of blindly wrapping the entire Qt API.
Another disadvantage is that generated bindings cannot directly access dynamic parts of the application, for example plugins, since no wrappers are generated for them. Dynamic bindings don't suffer from this limitation.
There are several different Qt bindings available implementing this approach. The most prominent ones are: PyQt (Python), PerlQt, and RubyQt.
Examples |
Now that we have discussed the different technologies that are available and their implementations we're ready to look at how things are done in practice. The decision about the implementation to choose usually depends on several factors such as the preferred scripting language, the overhead of the bindings, the available support, and the quality of the documentation. Here we will look at a small example where 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 show the dynamic approach, and one using PyQt to show the generated approach.
For our example, we will take Qt's application example and give the user the possibility to access the "editor" widget (a QTextEdit) 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 QSA within the application.
The first step is achieved by adding the following line to the project's .pro file (application.pro in our case):
load(qsa)
and by including the appropriate header files. In the case of our application example, add the following headers to application.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 the following code at the end of the ApplicationWindow class's constructor:
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 accessible through its QObject name, which is "editor". We also instantiate the input dialog factory, which gives us a convenient API to display standard input and message dialogs. Finally we create and open the QSA Workbench on the scripting project.
QSA Workbench is a simple and convenient IDE (Integrated Development Environment) that provides a color syntax highlighting editor with support for code completion, in an environment that users can use to create, edit, save, load, and execute scripts.
If you don't want to open an extra window for editing scripts, or you don't want to overwhelm your users with a scripting IDE, you could use the QSEditor class to embed a simple script editor into your application. There is also an API available to query the functions available in a script; this is useful for example if you want to display them in a convenient menu through which the user can choose a script function to execute.
Now let's implement the following script which provides a 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) }
This begins by retrieving the editor's text by accessing the text property of the editor object we added to the scripting project. Note that it only took a single function call to make this object and all of its API available to the scripter. Then we replace all the newlines in the text by spaces and split the text by spaces. This will return an array containing all the words in the text. So the length property of the returned array equals the number of words in the editor. Finally we display the information we've discovered by opening a MessageBox using QSA's convenient dialog API.
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, the QSProject class provides a simple mechanism for achieving this. All that is necessary is for the application to call QSProject::save() on exit, and to call QSProject::load() as part of its initialization. In addition, the user can save and load scripts for themselves using QSA Workbench.
This shows how simple it is with QSA's dynamic approach to make an application scriptable, and to give the script developer access to application objects and their APIs.
PyQt Example |
Unlike QSA, PyQt doesn't provide a solution to embed scripting into a Qt application out-of-the-box. Nonetheless, it is not difficult to embed Python in a Qt application. For our example we're using the Pythonize library a simple C++ wrapper around Python's C API. With the following code in the ApplicationWindow constructor we can 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 scripting Qt applications in PyQt. This means that we can't just add an application object to make it accessible from the script. But we can work around this by evaluating script code. Since we have access to the whole Qt API from Python, this is actually quite flexible. We want to provide the scripter with the editor object just like in the QSA example. This is achieved by evaluating the following 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 the main widget's text editor child. The result is stored in the editor variable which can now be accessed from scripts the user writes.
Unlike QSA, PyQt doesn't provide an embeddable IDE, so we must create our own. A simple approach would be to create a widget with a QTextEdit for writing script code, and a "run" QPushButton that would call python->runString() on the QTextEdit's contents. The code for this simple scripting console is straightforward, so we'll omit it.
The script to count the number of words in the editor in Python would look 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 explicitly cast the text from a QString to a Python string to use Python's split() function.
Unfortunately, PyQt doesn't provide any equivalents to QSA's QSProject::load() and QSProject::save() functions, so you must write your own code to load and save PyQt scripts.
While PyQt isn't explicitly designed for application scripting, this example shows that PyQt can be used as an application scripting solution.
Conclusion |
This article shows that scripting and Qt play very well together. There are different solutions available which give Qt developers very powerful tools to create scriptable applications that are more flexible and customizable than standard applications.
Copyright © 2004 Trolltech | Trademarks |