Trolltech | Documentation | Qt Quarterly | « Early Easter Eggs

Fun, Fast and Flexible Qt Script
by Reginald Stadlbauer & Monica Vittring
Qt Script for Applications (QSA) is a toolkit for making C++ applications end-user scriptable. QSA provides an interpreter for Qt Script, a language derived from ECMAScript (JavaScript). Qt Script programs can access the whole Qt API and any part of an application's API that developers choose to make available to their users.

The toolkit includes Qt Script for Applications Developer (QSA Developer), a visual integrated development environment (IDE). QSA Developer works just like Qt Designer and has additional functionality including an advanced code editor and an integrated debugger.

QSA is undergoing beta testing and is expected to ship later this year (2002) for Windows, Unix/Linux and Mac OS X.

Why Provide Scripting?

Two major challenges for developers working on mature and powerful applications are:

  1. No matter how much functionality the application provides, and no matter how many options, there are always situations where users find themselves constrained by what the developers chose to include.
  2. It can be very expensive to extend an application's functionality between releases, and to provide bug fixes and customizations in a fast and flexible manner.

Application scripting addresses the challenges posed above and provides four key benefits:

  1. Scripting allows users to create their own solutions and customizations, based on the core functionality provided by the application.
  2. Scripting allows support departments to create solutions to customer problems quickly and easily and to create work-arounds for bugs without interfering with the application's code base.
  3. Scriptable applications are more attractive to value-added resellers. They can sell complete packaged solutions built from the application and their own scripts.
  4. Scriptable applications free developers from having to produce a seemingly endless number of functionality variations; instead they can produce the core functionality and leave the user to create their own variations.

Scripting is a well established and widely used solution, from Microsoft Office's VBA to Emacs Lisp. Some Qt customers already provide scripting for their applications using third-party software, and the demand for an official Qt scripting solution can now be met by Qt Script.

Making an Application Scriptable

Making an application scriptable is a straightforward five-step process.

  1. Link against the QSA library.

    This is a simple matter of adding a couple of lines to the application's .pro file:

        unix:LIBS  += -lqsa
        win32:LIBS += $(QTDIR)\lib\qsa100.lib
    

  2. Add the application objects to the scripting engine.

    Application objects are made scriptable by calling QtApplicationScript::addObject(). For example, if the fadeEffects object is added, it will be accessible within Qt Script as Application.fadeEffects. Once an object is added, the script writer can call the object's slots, and can retrieve and modify any of the object's properties. If the object emits signals, these can be connected to Qt Script functions. Objects can also be removed from the scripting engine's view with removeObject().

    Any QObject subclass can be made available to script programmers. In practice, developers will create a few QObject subclasses that they use to control access to their application's internals. To make a QObject subclass suitable for scripting, it must use the Q_OBJECT macro (as QObject subclasses normally do), and should use the Q_PROPERTY macro for any properties that should be scriptable. All the object's properties, signals and public slots will be available to the scripting engine.

  3. Open a scripting project.

    A scripting project is a container for a set of functions and classes. Some applications might always use a single scripting project; others might use several. QSA can load and save a scripting project to a file or to a datastream. For example, when an application initializes, it might instantiate a variable that points to the application-wide QtApplicationScript object, then call addObject() for the objects it wishes to expose, and then open the scripting project that the application will use using QtApplicationScript::open().

  4. Allow the user to create and edit scripts.

    If users are permitted to create their own dialogs (e.g. to retrieve parameters for the functions they create), they can be given access to QSA Designer, which can be used to create, edit, run and debug scripts. This is achieved with a single call: QtApplicationScript::self()->openDeveloper().

    If the user is only allowed to create non-GUI functions, they can either use QSA Designer with its GUI-building functionality switched off, e.g. QtApplicationScript::self()->openDeveloper(FALSE), or they can use a text editor widget (e.g. QTextEdit) to write their code.

  5. Allow the user to run scripts.

    Scripts can be executed from within QSA Designer. Scripts created with a text editor can be executed by passing the string that contains the script to QtApplicationScript::evaluate(). In practice we want to give users access to their scripts within the application itself. One approach is to automatically create a QAction for every global function that the user creates, for example:

        void MyApp::addScript( const QString &funcName, 
                               const QPixmap &pixmap )
        {
            QAction *a = new QAction( funcName, pixmap, 
                                      funcName, 0, this, 
                                      funcName.latin1() );
            a->addTo( scriptsToolbar );
            a->addTo( scriptsMenu );
            connect( a, SIGNAL( activated() ), 
                     this, SLOT( runScript() ) );
        }
    

    In the above example, we create a new action, add it to the scripts menubar and toolbar, and connect the action's activated() signal to our runScript() slot.

    When the user invokes the script via the menu or toolbar, it can be executed using call():

        void MyApp::runScript()
        {
            QAction *action = (QAction*)sender();
            QtApplicationScript::self()->call( 
                action->text(), QValueListQVariant>() );
        }
    

    First we get the QAction that was invoked, then we call() the script named by the action. The empty QValueList is passed because we are calling the function without passing it any parameters. If parameters are required, the user would create a dialog and take the parameters from the dialog's widgets.

    The QtApplicationScript::globalFunctions() function returns a list of all the global functions that exist in the current scripting project.

As we've seen from the above, making an application scriptable is straightforward. The application must be linked against the QSA library to give it access to the scripting engine and QSA Designer. Once the QSA library is available, adding application objects to the scripting engine, opening script projects and giving users the ability to create, edit, run and debug scripts can all be achieved with just a few lines of code.

Qt Script for Applications Developer

QSA Designer is a tool that end-users and developers use to create, edit, run and debug scripts. QSA Designer is included in the QSA library and is available to all scriptable applications. Besides having a really long name, QSA Designer includes a GUI builder that is very similar to Qt Designer, a Qt Script code editor and an integrated debugger.

QSA Designer

The IDE takes the same approach to creating layouts as Qt Designer. Users can place widgets in approximate positions and then select them and click on the layout toolbar buttons to create automatically scalable dialogs. The IDE also makes it easy to create signal-to-slot connections using point and click.

If it's inappropriate for users to create their own dialogs, developers can make QSA Designer available to their users with its GUI-building facilities switched off. In this mode, users can create, edit, run and debug non-GUI functions.

QSA Designer's debugger is fully integrated and provides break points, code stepping, variable watching and a call stack window. During debugging, hovering the mouse over a variable will cause a tooltip to display the variable's type and value. The IDE's code editor provides color syntax highlighting, code-completion, argument hints and folding (outlining).

Under the Hood

Application scripting makes it possible to integrate the object model of a C++ application with a scripting interpreter. For QSA this means that most of Qt and especially the Qt object model must be made available to Qt Script.

Making the functionality encapsulated in compiled code available to a scripting engine is called 'binding'. Various approaches to creating bindings exist, but the most common is to use a code generator that parses the C++ class declarations and, based on the information it gathers, outputs wrapper code (typically subclasses) for every class it encounters. The scripting engine calls the wrapper code to obtain information about the wrapped classes and to make use of them. This approach has two significant drawbacks: it creates considerable code bloat, since for every class a wrapper subclass is created; and it is tedious, because a wrapper must be created for every class that the developer wishes to expose to the scripting engine.

The QSA approach is quite different, and is based on Qt's meta object system. The meta object system provides the signals and slots mechanism that all Qt programmers are familiar with. It also provides Qt's property system. The meta object system can be used at run-time to create and delete signal-to-slot connections, to query, retrieve and modify properties, and to access run time type information. None of this dynamic functionality requires any wrapper subclasses or other binding code to be written.

The small amount of code that makes this functionality available is already produced by the moc (Meta Object Compiler). As a result, every QObject and QObject subclass and all their properties, signals and public slots, are automatically made available to the scripting engine. And non-QObject classes can easily be made scriptable by wrapping them in a QObject. This approach even works for plugins, whereby a QObject or QObject subclass loaded in a plugin can be scripted without the application developer needing access to the class's source code.

QSA

The diagram above shows the relationship between the scripting engine (the QSA Library), an application, and the application's scripts. Script programmers can access the QObjects that are made available by addObject() calls as if they were objects built into the Qt Script language itself. The QObjects may be application QObjects or QObjects which developers have created specially to provide script programmers with a clean interface to control access to the application's internals. QSA Designer is embedded within the Qt Scripting Engine to makes it easy for developers to provide a tool for creating, editing, running and debugging scripts.

The Qt Script Language

Qt Script is based on ECMAScript 4.0 (also called JavaScript 2.0 or JScript.NET). Qt Script is fully object-oriented with an object model very similar to Qt. It has modern language features such as high-level data-types and exception handling, and provides the complete Qt API. Qt Script's syntax is similar to C++'s and Java's, but less complex. Qt Script provides richer functionality than ECMAScript requires; for example, the Qt Script String class provides all the functionality of QString in addition to that defined by ECMAScript. Such extensions are permitted by the ECMAScript standard.

The code below is an example of a Qt Script slot.

    function buttonCalculate_clicked()
    {
        var divisor;

        switch ( comboCurrency.currentText ) {
            case "EUR":
                divisor = 1.13091;
                break;
            case "GBP":
                divisor = 0.700417;
                break;
            case "JPY":
                divisor = 131.446;
                break;
        }

        const spinOutCol = spinOutputColumn.value - 1;
        const spinCol = spinColumn.value - 1;

        for ( var i = spinStartRow.value - 1; 
                  i <= spinEndRow.value - 1; ++i )
            with ( Application.sheet1 ) 
                setText( i, spinOutCol, 
                         text( i, spinCol ) / divisor );

        accept();
    }

Variables are declared with var rather than a specific type name because, like most scripting languages, Qt Script is weakly typed for flexibility. The comboCurrency object, the spinStartRow object, and the other 'spin' objects, are widgets in the same dialog as the button that this slot responds to. The with statement allows users to omit explicit scopes. For example, Application.sheet1.setText() and Application.sheet1.text() are written as setText() and text() in the code shown above. The slot uses the Application object to access global objects.

The core language of Qt Script -- arithmetic and logical operators, for loops, while loops, if and switch statements, etc. -- is already familiar to vast numbers of people who have used JavaScript and JScript. This familiarity, along with its powerful functionality and ease of use, makes Qt Script an ideal scripting language for application end-users. Nonetheless, in some industries and market sectors, end-users are already familiar with other scripting languages, for example, Python and Tcl. The QSA binding technology is scripting-language neutral and other languages will be supported in future versions of QSA as demand warrants.

Summary

Scriptable applications provide benefits and flexibility for support departments and end-users, and can free developers from having to produce endless variations on functionality. QSA is a scripting solution that makes the entire Qt API available to script programmers, along with whatever functionality developers choose to expose.

The QSA library is a complete package for developers to incorporate in their applications, providing both the interpreter for the scripting language and QSA Designer, a familiar and powerful IDE for use by end-users to create, edit, run and debug their scripts. QSA makes it easy to make applications scriptable. If you want to know more about QSA please contact qsa@trolltech.com.


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

Copyright © 2002 Trolltech. Trademarks Qt Quarterly