Coding tutorials in scripts

We have started seeing how tutorials can be written in C++. Now we will see how to write tutorials in a script language.

KTutorial uses Kross to give support to scripting languages. Kross is a scripting framework that acts as a bridge between C++ and scripts. It doesn't implement interpreters or bindings for the scripting languages, but offers access to the already existing ones. If you are curious, I wrote the results of my experiments checking what can and what can't be done with Kross in Some notes about Kross.

In short, KTutorial exposes a C++ API to the scripts, and from the scripts you just use that API. The API is made of Qt objects, and you can access to their properties, slots and methods marked as invokable. Basic types like int, double, strings and so on are also supported through QVariant.

The example tutorial will be the same shown in the C++ example, that is, it will just ask the user if he prefers to write one text or another and then it will wait for the user to clean the text using the Clean action. You should read before the C++ example, as only the script specific matters will be explained, but not the KTutorial concepts themselves.

This example will be written in JavaScript. The reason is that JavaScript backend is always installed in Kross, as it is part of kdelibs. However, Python and Ruby backends must be installed explicitly, as they are part of KDE Bindings. So JavaScript tutorials will be always recognized, unlike Python and Ruby tutorials that may not be understood by Kross.

The tutorial will be written in a JavaScript file, for example, SimpleTutorial.js. The scripted tutorials must be explicitly installed somewhere for KTutorial to use them. KTutorial looks for tutorials in the tutorials directory of the application data directory. So there is where the scripted tutorials must be installed, which can be done easily in your CMakeLists.txt as shown below.


In the C++ tutorial we created a class deriving from Tutorial class. In scripted tutorials we don't need to create a custom class, it is created implicitly for us. If you are looking to the script through the C++ glass, think about the script like the constructor of our C++ Tutorial derived class (although supporting even functions and classes defined on it).

The tutorial object is exposed in the script in a variable called tutorial (nobody expected it, neither the Spanish Inquisition). As not every Kross backend supports WrapperInterface, tutorialInformationAsObject method, which returns a QObject*, has to be used instead of the more natural tutorialInformation method, which returns a TutorialInformation*. Further information about this subject near the end of Some notes about Kross.

To internationalize strings, Kross provides a module called kdetranslation that offers the typical i18n, i18nc... functions as methods of the module object (although they receive a list of values instead of a value for each string argument, as it will be shown later). Note that in other script languages, like in Python, you would need to import Kross before using it. The nice thing is that as it uses KDE i18n system, you don't have to do anything special for it to work, just enclose strings in double quotes and extract the messages from script files like you would do from C++ files (that is, just add -o -name '*.js' to find in your Messages.sh file). Further information about this subject in Internationalization in Kross scripts.


As already said, KTutorial exposes a C++ API to be used from the scripts, getting and setting the properties and calling slots and invokable methods. However, Kross can't create objects of a C++ class. To build the tutorial we need to create steps, options and so on. So, how do we get new objects?

There is a special object, of scripting::ScriptingModule C++ class and exposed in the variable ktutorial, that acts as a factory for new objects (and also to find objects by name).

There is also another difference from C++ tutorials when adding an Option or a WaitFor. In C++, we specified an object and the slot to call when the condition was satisfied. We still can in a script, but there is also another possibility, more appropriate for scripts. Using the same syntax, we can call a function of the script when the condition is satisfied, refering to the script as self and specifying the name of the function as a string.

Finally, take into account that variables in JavaScript (and other script languages) are defined when used for first time and are deleted when their scope ends. So when you assign a variable inside a function that variable must have been defined in the global scope before the function is called, or just a local variable will be created and destroyed inside the function.


If you didn't understand what I meant when I said that string arguments were passed as a list instead of a value for each argument, you can see it here. The C++ i18n function receives the string to be internationalized/localized as its first argument. And then, if that string contained any argument to be replaced on it, each string argument was added as a different parameter of the function. When it is accessed through Kross, all the string arguments are grouped in a list of values instead (one or more comma separated expressions wrapped by [ and ]). It works the same way with the other i18n family functions.

In this example we can also see how ktutorial object is used to find objects by their name.

Let's talk now about WaitFor objects. Unlike Step or Option, there are several types of WaitFor objects. Adding one method to ScriptingModule for each WaitFor subclass provided in the library would clutter ScriptingModule API. However, KTutorial supports custom WaitFor subclasses to be added by an application if needed, so there is no way for ScriptingModule to provide a method for those subclasses.

So instead of one method for each WaitFor type, there is only one which receives an string with the desired class name. Once the object is returned, its initialization can be ended as needed. For example, in the case of WaitForSignal, there is an invokable method to set the emitter object and the signal to wait for.

Finally also note that toPlainText is a normal method in QTextEdit, so it can't be used through Kross. However, plainText is a Qt property, so we can read and (if allowed) write to it without problems. The returned QString is automatically converted to a JavaScript string.


And now, just add the two pending steps in the tutorial: one to wait for the text to be cleared, and the end one.