Home · Tutorial · Classes · Functions · Language · QSA Workbench · Qt Documentation · www.trolltech.com

Getting Started with QSA



This guide presents Qt Script for Applications, a tool for making C++ applications scriptable, using Qt Script, an interpreted scripting language. Some basic knowledge of scripting is needed to get the most out of this guide.

This guide provides an introduction to Qt Script for Applications, and its tools.

Here is a brief overview of the chapters:

What is Qt Script for Applications

This chapter gives a brief overview of the Qt Script for Applications toolkit.

Components of Qt Script for Applications

Qt Script for Applications is a cross-platform toolkit for making C++ applications scriptable using an interpreted scripting language, Qt Script. The Qt Script for Applications toolkit is made up of the following components:

The tutorial chapters that follow show you how to make an application scriptable and how to write a simple script for the application.

How to Make Qt Applications Scriptable


This chapter demonstrates how to write a C++ Qt application which integrates Qt Script for Applications to make the application extensible and customizable through scripting. The goal is to write a simple spreadsheet application which can be extended by the user. To do this, the spreadsheet will provide interfaces to its sheets. The script code can access the sheets, so the user can write Qt Script code that presents dialogs to accept user preferences, and which can access and manipulate the spreadsheet data. The code for this example can be found in examples/spreadsheet.

Additional examples that demonstrate other Qt Script for Applications usage are also included in the examples directory.

How to make a Qt/C++ Application Scriptable

1) Include the Qt Script for Applications Library

To make a Qt/C++ application scriptable, you need the libqsa library that is included in the Qt Script for Applications package. To use libqsa and get other QSA specific build options, add the following line to your .pro file:

    CONFIG += qsa

The libqsa library provides the QSInterpreter class. A default instance of QSInterpreter is available, for convenience, by using the function QSInterpreter::defaultInterpreter().

2) Add the Application Objects to the Scripting Engine

To make application functionality available to scripts, the application must provide QObjects or QObject subclasses which implement the application's functionality. By passing an object to the scripting engine (using QSInterpreter::addTransientObject() or QSProject::addObject()), this object and all its signals, slots, properties, and child objects are made available to scripts. Because Qt Script for Applications uses Qt's meta object system, there is no need to implement any additional wrappers or bindings.

If no parent object of the object that is passed into the interpreter has been made available yet, the new object is added as a toplevel object to the scripting engine. This means that it is available through Application.object_name to the script writer.

If a parent object of the object has been previously added via a addObject(), the new object is not added as a toplevel object, and is available through Application.parent1.parent2.object_name (given that parent1 has been added previously via addObject()). The reason for doing that is because the object can be used as a namespace or context later, and code can be added in the context of that object.

In most cases we do not pass QObjects which are directly used in the C++ application to the scripting engine because this would expose too many slots. Instead we implement interface QObjects which offer the signals, slots, and properties that we want to offer to the scripts and which will be simply implemented as forward function calls.

In our spreadsheet example we will add interface objects for the sheets. The interface objects implement slots and properties to query and set selection ranges, retrieve and set cell data, etc.

In other cases it might be possible to use an application's existing QObjects as application objects in the scripting language. An example of this approach is shown in the examples/textedit example, which is a slightly modified, scriptable, version of the Qt textedit example.

To read about how to design and implement application objects, see the How to Design and Implement Application Objects chapter.

3) Open a Scripting Project

Qt Script for Applications always works with one current scripting project that contains all the forms and files in which all the functions and classes are implemented.

An instance of QSInterpreter can be used on its own, but to get full access to the functionality of Qt Script for Applications, use QSProject. To use a stand alone QSInterpreter use QSInterpreter::defaultInterpreter() or QSInterpreter::QSInterpreter(). To create an interpreter that runs with a project, create the project using QSProject::QSProject() and access its interpreter using QSProject::interpreter().

If you work with a project, you can either choose to use functionality in Qt Script for Applications to take care of everything (saving, loading, etc.) or you can decide to take care of most functionality yourself, giving you more flexibility.

If you choose to have Qt Script for Applications take care of everything for you, Qt Script for Applications then loads and saves the whole project from one file or one data block which you can specify. In this case all project data is compressed in the data block or file and extracted temporarily when loading. If you choose to take care of the functionality yourself, then open an empty project and use QSProject's API to add scripts manually and then retrieve them and save or store them however you'd like.

If you choose the first option, do the following:

Create a new QSProject and load in a scripting project using QSProject::load().

Loading a project can be called in one of two ways:

  1. Call QSProject::load() with a filename (including the path). Qt Script for Applications will open the specified project file.
  2. Call QSProject::loadFromData() with a byte array that contains the scripting project's data. This is useful if you don't want to save the project as an individual file on disk; for example, you might prefer to keep the project data embedded in the document. If this approach is taken, the data block which contains the project is passed to load(). If an empty byte-array is passed, a new project will be created. Use QSProject::saveToData() to retrieve a project as a byte-array suitable for use with the QSProject::loadFromData() overload.

If you choose the second option, simply create a new QSProject and use its functions to add scripts and save the project at your convenience.

Of course it is possible to use a combination of both approaches, e.g. using the first approach, but later adding script code programmatically.

Note that when saving a project, the functions in QSInterpreter will add transient content to the interpreter, while the functions in the project will add persistent content to the interpreter. This means that content that is added to the project can be saved and will remain even if the interpreter state is cleared, while the content added using the QSInterpreter will not be saved and will be cleared along with the interpreter state.

In the spreadsheet example we use one scripting project for the whole application, and we let Qt Script for Applications save this to disk as an individual file that is opened on startup. The following code is used to initialize QSProject (adding application objects and opening the project):

4) Allow the User to Create and Edit Scripts

In some cases it might be sufficient to offer a basic editor widget which allows the user to write code. For example, you might want to embed the code editor directly into your application's user interface, and you don't want to open another toplevel window (such as QSA Workbench) for the user. In other cases, you want the users to be able to add and remove scripts, to have intelligent completion and hints in the editor, and to use GUI controls in the script. For these cases, including QSA Workbench is the best option.

There are two ways for the end user to use QSA Workbench.

  1. Open QSA Workbench and create or edit a script.
  2. Define a macro.

There are different ways to provide the scripting functionality to the end user depending on the type of application. For a typical end-user application you can offer one or both of the approaches to scripting mentioned above. For example you can provide a menu option and a toolbar button to launch QSA Workbench, and an editable combobox which lists all the global functions. If the user enters a function name that doesn't exist they are given the opportunity to create a new function of that name. If the user chooses an existing function, QSA Workbench is launched with the cursor at that function, ready for editing. A 'Run' toolbar button can be placed beside the combobox, so that the user can choose a function and click 'Run' to execute it.

Other approaches include enabling a user to: define functions to validate data of data entry forms, to customize the functionality of an editor, to customize the user interface of a complex 3D graphics application or to provide scripting modules for an image processing application.

The usage of application scripting can greatly vary depending on the type of application. The spreadsheet application described in this chapter is an example of a typical end-user application. This example will make you familiar with most of the important scripting concepts. Following this example will teach you how to use Qt Script for Applications to make your applications scriptable, even if the way your end users will use application scripting might be very different from what we describe here.


We define a macro as a stand-alone global function. Create a QSScript using QSProject::createScript() to create a script in global context. Call QSScript::addFunction() to add a new macro to the script. You can then open the editor and edit the newly created function.

Launching QSA Workbench to Create or Edit a Script

In the spreadsheet example the following slot is called to open QSA Workbench:

Adding and Editing Macros

In our spreadsheet example we want to enable the user to add macros as actions to the toolbar and menu which they can associate with a function that will be called when they activate the action. To add macros, we provide a dialog through which the user can either choose an existing global function to edit, or add a new function (as described in Macros). If the user adds a new function, a new action and icon are created along with a menu option and toolbar button.

The following code is used in the macro dialog to initialize the combo box which lets the user choose a script function:

When the user clicks OK in this dialog, the following slot is executed. If the function the user specified doesn't exist, the user is asked if this function should be added to the project, in which case, addFunction() is called:

At the end of the function, the newScript() signal which is connected to the addScript() slot in the spreadsheet is emitted. The addScript() function creates an action for the macro and adds a menu option and toolbar button for the macro. In addition, the action's triggered() signal is connected to runScript(). To find out which function this macro (action) will call, the action and its associated function are inserted into the scripts map:

5) Allow the User to Run Scripts Directly from the Application

It would be tedious for users if they had to launch QSA Workbench and click Run every time they want to execute a script. For this reason it is normal practice to provide a means by which the user can execute a function from within the application itself. How this is acheived depends to some extent on the application and on the functionality of the script.

One approach to providing the user with access to their script functions is to provide a list, e.g. a popup list, from which they can pick the function they wish to execute. (This approach is taken in the textedit example.) A list of existing global functions in the current project is obtained by calling QSInterpreter::functions(). To call a script function, use QSInterpreter::call().

In the spreadsheet example we have seen that each macro (global function) is associated with an action and has a corresponding menu option and toolbar button. Now we'll see how clicking a macro menu option or toolbar button will cause the macro to be executed.

When the user invokes an action, the runScript() slot is triggered by the action, and we have to find which function should then be executed. For every slot, we call sender() (implemented in QObject), to find out what action triggered that slot. We cast the sender() to a QAction pointer (since we know it is a QAction) and then look up this pointer in the scripts map. Each action is mapped to the name of the function that it is associated with, so we can now call QSInterpreter::call() with the action's associated function name to execute it:

Establishing Connections to Signals and Running the Interpreter

It is possible to connect script functions to an application objects signals by letting the user edit scripts in QSA Workbench. These connections are established when the project is evaluated. When the user opens QSA Workbench, the project is paused for as long as QSA Workbench is open, and no connections are active during this time. When a scripting function is executed while QSA Workbench is opened or when play is pressed, the project is run again each time so that changes to the script become active. When QSA Workbench is closed again, the project is re-run once more and all connections are re-established.

Error Handling

If an error occurs, the QSInterpreter emits a QSInterpreter::error() signal.

Instantiating QObjects from Qt Script

We have shown that script programmers can easily access application instances of QObject subclasses if the class is made available to the interpreter. This is sufficient for most situations, but sometimes it may be desirable to allow script programmers to instantiate their own object instances. One solution is to expose an application object which has a slot that acts as a factory function, returning new QObject instances. Another solution is to allow the script writer to directly instantiate their own objects from C++ classes, with script code like this:

     var a = new SomeCppObject( arg1, arg2 );

To make a QObject subclass available as a constructable object in Qt Script, use the QSObjectFactory class. This class makes it possible to create new C++ data-types and make them available to Qt Script.

Wrapping Non-QObject C++ Datatypes

Qt Script for Applications automatically wraps every QObject you pass to it. It also wraps every QObject which is returned from a slot or passed into a slot. But you often have non-QObject datatypes in C++ which you want to make available to the script writer as well. One possibility is to change your C++ API and convert all those datatypes to QObject subclasses. From a design and efficiency point of view, this is a bad way to go; imagine the effects of having every item of a listview being a QObject subclass.

Qt Script for Applications provides an innovative solution by offering the QSWrapperFactory class. This class allows you to define non-QObjects that you can wrap. A QSWrapperFactory basically offers a QObject which can wrap a known C++ datatype. If Qt Script runs accross an unknown C++ datatype it will ask all installed QSWrapperFactories if it knows the type. If one of the QSWrapperFactories knows the datatype, a wrapper for that datatype is instantiated and used.

We have demonstrated the flexibility that Qt Script offers for making applications scriptable. In the next chapter, we will extend the applications functionality to end users by teaching them to create scripts with a simple, but complete example.

How to Design and Implement Application Objects


This chapter explains how to implement application objects and provides the necessary technical background material.

Making a C++ object available to Scripts Written in Qt Script

Making C++ classes and objects available to a scripting language is not trivial since scripting languages are more dynamic than C++ and it must be possible to introspect objects (query information such as functions names, function signatures, properties, etc., at runtime). Standard C++ doesn't provide for this.

We can achieve the functionality we want by extending C++, using C++'s own facilities so our code is still standard C++. The Qt meta object system provides the necessary additional functionality. It allows us to write using an extended C++ syntax, but converts this into standard C++ using a small utility program called moc (Meta Object Compiler). Classes that wish to take advantage of the meta object facilities are either subclasses of QObject, or use the Q_OBJECT macro. Qt has used this approach for many years and it has proven to be solid and reliable. Qt Script for Applications uses this meta object technology to provide scripters with dynamic access to C++ classes and objects.

To completely understand how to make C++ objects available to Qt Script, some basic knowledge of the Qt meta object system is very helpful. We recommend that you read the Qt Object Model. The information in this document and the documents it links to are very useful for understanding how to implement application objects, however this knowledge is not essential.

To make an object available in Qt Script, it must derive from QObject. All classes which derive from QObject are introspective and can provide the information needed by the scripting engine, e.g. classname, functions, signatures, etc., at runtime. Because we obtain the information we need about classes dynamically at run time, there is no need to write wrappers for QObject derived classes.

Making C++ Class Member Functions Available in Qt Script

The meta object system makes information about slots dynamically available at runtime. This means that for QObject derived classes, only the slots are automatically made available to scripts. This is very convenient, because in practice we normally only want to make specially chosen functions available to scripters.

When you create a QObject subclass, make sure that the functions you want to be available to scripters are public slots:

    class MyObject : public QObject
        MyObject( ... );
        void aNonScriptableFunction();
    public slots: // these functions (slots) will be available in Qt Script

        void calculate( ... );
        void setEnabled( bool enabled );
        bool isEnabled() const;

In the example above, aNonScriptableFunction() is not declared as a slot, so it will not be available in Qt Script. The other three functions will automatically be made available in Qt Script.

Making C++ Class Properties Available in Qt Script

In the previous example, if we wanted to get or set a property using Qt Script we would have to write code like the following:

    var obj = new MyObject;
    obj.setEnabled( true );
    debug( "obj is enabled: " + obj.isEnabled() );

Scripting languages often provide a property syntax to modify and retrieve properties (in our case the enabled state) of an object. Many script programmers would want to write the above code like this:

    var obj = new MyObject;
    obj.enabled = true;
    debug( "obj is enabled: " + obj.enabled );

To make this possible, you must define properties in the C++ QObject subclass. The class declaration of MyObject must look like the following to declare a property enabled of the type bool, which should use the function setEnabled(bool) as its setter function and the function isEnabled() as its getter function:

    class MyObject : public QObject
        // define the enabled property

        Q_PROPERTY( bool enabled WRITE setEnabled READ isEnabled )
        MyObject( ... );
        void aNonScriptableFunction();
    public slots: // these functions (slots) will be available in Qt Script

        void calculate( ... );
        void setEnabled( bool enabled );
        bool isEnabled() const;

The only difference from the original code is the use of the macro Q_PROPERTY, which takes the type and name of the property, and the names of the setter and getter functions as arguments.

Reacting to C++ Objects Signals in Scripts

In the Qt object model, signals are used as a notification mechanism between QObjects. This means one object can connect a signal to another object's slot and every time the signal is fired (emitted) the slot is called. This connection is established using the QObject::connect() function. This mechanism is also available to Qt Script programmers. The C++ code for declaring a signal is no different for a C++ class that is to be used by Qt Script than a C++ class used with Qt.

    class MyObject : public QObject
        // define the enabled property

        Q_PROPERTY( bool enabled WRITE setEnabled READ isEnabled )
        MyObject( ... );
        void aNonScriptableFunction();
    public slots: // these functions (slots) will be available in Qt Script

        void calculate( ... );
        void setEnabled( bool enabled );
        bool isEnabled() const;
    signals: // the signals

        void enabledChanged( bool newState );

The only change this time is to declare a signals section, and declare the relevant signal in it.

Now the script writer can write a function and connect to the object like this:

    function enabledChangedHandler( b )
        debug( "state changed to: " + b );
    function init()
        var obj = new MyObject;
        // connect a script function to the signal

        connect( obj, "enabledChanged(bool)", this, "enabledChangedHandler" );
        obj.enabled = true;
        debug( "obj is enabled: " + obj.enabled );

Design of Application Objects

The previous section described how to implement C++ objects which can be used in Qt Script. Application objects are the same kind of objects, and they make your application's functionality available to Qt Script scripters.

Since the C++ application is already written in Qt, many objects are already QObjects. The easiest approach would be to simply add all these QObjects as application objects to the scripting engine. For small applications this might be sufficient, but for larger applications this is probably not the right approach. The problem is that this method reveals too much of the internal API and gives script programmers access to application internals which should not be exposed.

Generally, the best way of making application functionality available to scripters is to code some QObjects which define the applications public API using signals, slots, and properties. This gives you complete control of the functionality you make available. The implementation of these objects simply calls the functions in the application which do the real work. So instead of making all your QObjects available to the scripting engine, just add the wrapper QObjects. For an example of this technique, see the implementation of an application object in the SheetInterface (examples/spreadsheet/sheetinterface.{cpp|h}).

Creating Qt Scripts

In this chapter we'll demonstrate how to create scripts for a scriptable application using QSA Workbench, a scripting environment for managing, creating, and running scripts. QSA Workbench provides a code completion feature that makes writing scripts easier. We will explain how to create a dialog using Qt Script and then create and implement a convertToEuro() function. We will write the scripts using the spreadsheet application that we created in the previous chapter.

Settings for Euro Converter Finished Dialog

Creating a New Macro

A 'Macro' is simply a global Qt Script function.

Run the spreadsheet application if it isn't already running. Click the New button located on the Scripts toolbar to invoke the New Macro dialog. Once we create a new macro, a toolbar button will appear on the Scripts toolbar as a shortcut to execute the macro.

New Macro Toolbar Button

Follow the steps below to create the new macro:

Click OK when you have entered the information in the New Script dialog. The Add Function message box will pop up, saying The function doesn't exist. Do you want to add it?. Click Yes.

Adding the New Function

QSA Workbench opens with the new empty function added to it. We will implement the scripting function in the following section.

Implementing the Macro and Creating a Dialog

In this section, we will implement the functionality of the convert-to-euro macro. We want to present the user with a dialog in which they can: 1) enter a range of rows and columns which are read from the spreadsheet, 2) specify where in the spreadsheet the results should be written, and 3) select a currency type to convert into Euros.

In the QSA Workbench editor, we write the code to calculate the input values which are the input column, start row, end row, and ouput column. We initialize these variables to 1, but if the user selects a range in the spreadsheet, we then use the selection for the initial values.

        var inputCol = 1;
        var startRow = 1;
        var endRow = 1;
        var outputCol = 1;
        if ( Application.sheet1.numSelections > 0 ) {
            var sel = Application.sheet1.selection( 0 );
            inputCol = sel.x + 1;
            outputCol = inputCol + 1;
            startRow = sel.y + 1;
            endRow = sel.bottom + 1;

In Qt Script, we have some global objects. The most important one for our example is called Application. This object contains application objects. Application objects are the objects that the C++ application makes available to our script. In the spreadsheet example, the sheets are available this way, e.g., Application.sheet1.

To create a new dialog, write the following code. An explanation of the code will follow.

        d = new Dialog;
        d.caption = "Settings for Euro Converter";
        d.okButtonText = "Calculate";
        d.cancelButtonText = "Cancel";

Every dialog includes an Ok and Cancel button by default. After creating the new dialog, simply change the caption property to Settings for Euro Convertor. Change the text of the OK Button to 'Calculate'. Change the text property of the Cancel button to 'Cancel'.

Adding Widgets to the Dialog

The Settings for Euro dialog consists of three spinboxes for selecting the columns and rows on the spreadsheet, a spinbox to output the result of the conversion, a group box to lay out the spinboxes, three radiobuttons in a group box to select the currency to convert from, and text labels for each of the widgets. If you run the dialog application and resize it, all the widgets scale properly. The layout of the widgets is determined by the order in which you add them to the dialog or the group box.

Add A Group Box and Spin boxes

We'll start with the first group box and its widgets. Write the following code in the editor to create the group box:

        var g = new GroupBox;
        g.title = "Conversion Range:";
        d.add( g );

This code creates the new group box. Set its title to Conversion Range:. Then add the group box to the dialog. Note that every time a widget is created, it must be added its parent.

Write the following code to add multiple spin boxes and text labels to the group box:

        var spinColumn = new SpinBox;
        spinColumn.label = "Column:";
        spinColumn.minimum = 1;
        spinColumn.maximum = 100;
        spinColumn.value = inputCol;
        g.add( spinColumn );
        var spinStartRow = new SpinBox;
        spinStartRow.label = "Start at Row:";
        spinStartRow.minimum = 1;
        spinStartRow.maximum = 100;
        spinStartRow.value = startRow;
        g.add( spinStartRow );
        var spinEndRow = new SpinBox;
        spinEndRow.label = "End at Row:";
        spinEndRow.minimum = 1;
        spinEndRow.maximum = 100;
        spinEndRow.value = endRow;
        g.add( spinEndRow );
        var spinOutput = new SpinBox;
        spinOutput.label = "Output Column:";
        spinOutput.minimum = 1;
        spinOutput.maximum = 100;
        spinOutput.value = outputCol;
        g.add( spinOutput );

With this code, we create the first spin box in the group box and and name it spinColumn. This spin box corresponds to the column in the spreadsheet from which the numbers will be read. Set the label property to Column:. Set the minimum property to 1 and the maximum property to 100. Set the number property to the calculated input column. Add the spin box to the group box.

Create the second spin box and name it spinStartRow. This spin box corresponds to first row in the spreadsheet from which the numbers will be read. Set the label property to Start at Row:. Set the minimum property to 1 and the maximum property to 100. Set the number property to the calculated start row. Add the spin box to the group box.

Create the third spin box and and name it spinEndRow. This spin box corresponds to the last row in the spreadsheet from which the numbers will be read. Set the label property to Start at Row:. Set the minimum property to 1 and the maximum property to 100. Set the number property to the calculated end row. Add the spin box to the group box.

Create the fourth spin box and and name it spinOutput. This spin box corresponds to the column in the spreadsheet to which the converted values will be returned. Set the label property to Start at Row:. Set the minimum property to 1 and the maximum property to 100. Set the number property to the calculated output column. Add the spin box to the group box.

Widget Lay Out

We'll add a column to the dialog to set layout so that the new group box will be added beside the current group box.:


Add a Group Box and Radio Buttons

Now we'll add the second group box and its widgets. Write the following code to create the second group box:

        var g = new GroupBox;
        g.title = "Conversion Range:";
        d.add( g );

This code creates the new group box similar to the first group box we created earlier in the chapter. Change the title property to Convert From:. Add the group box to the dialog.

Write the following code to add multiple radio buttons to the group box:

        var radioUSD = new RadioButton;
        radioUSD.text = "USD";
        radioUSD.checked = true;
        var radioYEN = new RadioButton;
        radioYEN.text = "YEN";
        var radioNOK = new RadioButton;
        radioNOK.text = "NOK";

We create the first radio button and name it radioUSD. Set its text property to USD . Set the checked property to true to make this radio button checked. Add the radio button to the group box.

Create the second radio button and name it radioYEN. Set its text property to YEN . Add the radio button to the group box.

Create the third radio button and name it radioNOK. Set its text property to NOK . Add the radio button to the group box.

Running the Dialog

To run the dialog, click the Call Function button in QSA Workbench. The Call Function dialog pops up with a drop down list of functions you created. Select convertToEuro and click OK. The Output Window displays errors found in the code.

The first block of code reads what currency the user chose and then initializes the divisor accordingly.

        var divisor;
        if( radioUSD.checked )
            divisor = 0.930492;
        else if( radioYEN.checked )
            divisor = 0.007677;
        else if ( radioNOK.checked )
            divisor = 0.133828;

The second block of code reads the range that the user has entered in the dialog.

        inputCol = spinColumn.value - 1;
        outputCol = spinOutput.value - 1;
        startRow = spinStartRow.value - 1;
        endRow = spinEndRow.value - 1;

The third block of code checks that the entered range is valid. If it is not valid, a warning is issued and the operation is canceled.

        if ( endRow < startRow ) {
            MessageBox.critical( "Cannot calculate!", "Error" );

The fourth block of code reads the value from the spreadsheet, calculates the results and then writes the results to the spreadsheet.

        for ( var i = startRow; i <= endRow; ++i ) {
            var input = Application.sheet1.text( i, inputCol );
            Application.sheet1.setText( i, outputCol, 
                                        Math.round( input / divisor * 100 ) / 100 );

Running the EuroConvertor Macro

We are now ready to run the macro and invoke the dialog we just created.

We have now coded a dialog and written the code to provide its functionality, all purely using Qt Script. This should provide a taste of the power and flexibility that Qt Script for Applications can provide for your Qt C++ applications.

Copyright © 2006 Trolltech Trademarks
QSA 1.2.2