GUI Scripts and GUI Plugins


GUI Scripts and Plugins


Writing scripts and plugins are important means to simplify analysis of many data sets and to provide new computational capabilities. BrainVoyager QX 2.1 introduces an important extension to the provided scripting and plugin system that allows to create and use cross-platform graphical user interface (GUI) components for scripts as well as for plugins. The new extensions have been defined on the basis of a new scripting layer that replaces the scripting module (QSA) of previous versions. While for compatibility reasons a version of BrainVoyager QX 2.1 will be available with the old scripting module, we strongly suggest that users adapt their scripts to the new scripting environment since only the new environment allows to use the implemented GUI functionality for scripts and plugins. Fortunately, the adaptation of scripts to the new system is very easy since the old and new scripting language are both based on (slightly different versions of) JavaScript.

The main concept of GUI plugins is that a GUI script interfaces between a created dialog and the plugin code (see figure above). The main development steps involve the folloing parts:

  • The GUI component, typically a dialog, is created using a graphical layout program (Qt Designer). The created dialog is saved as a XML file with extension ".ui".
  • A script is defined containing specifying the GUI component (dialogFileName = "MyDialog.ui") that will tell BrainVoyager to load and create the respective dialog from the ".ui" file. On first call, the script also defines callback functions allowing interaction with the GUI component. BrainVoyager keeps the script alive as long as the dialog is not closed allowing the script to handle the specified callback functions, such as responding to a button click.
  • A plugin is defined containing a simple C function (“getGUIScript()”) returning the name of the GUI script. When the plugin is launched from the "Plugins" menu, BrainVoyager asks the plugin to provide a name of a GUI script and if it returns an existing file name, it launches the associated script. The script then in turn will load the specified dialog as described above. The script may then call at any time the "execute()" function of the instantiated plugin object that persists as long as the dialog and script are alive. The script and the plugin exchange variables with each other allowing the plugin code to respond to specific actions performed in the dialog.
In order to allow ongoing interactions, both the GUI script and the GUI plugin create an object that persists in memory until the GUI component (dialog) is closed. This is different to previous plugins that could only be executed once.
This separation of GUI component, script and plugin follows the generally recommended model-view-controller (MVC) design pattern supporting an effective and maintainable programming style. Furthermore, it has specific advantages for the design goals of BrainVoyager plugins:
  • Plugin code remains specific for computational routines. Plugins should be created with the goal in mind to extend the computational capabilities of BrainVoyager. It is not meant to perform “simple” tasks such as batch processing that should be done using scripts. Knowledge of C/C++ programming should be sufficient to implement new routines. To create GUI plugins, only a graphical design tool must be learned as well as some script programming. The GUI code resides in interfacing script code that is clearly separated from the computational implementation.
  • GUI plugins are cross-platform by design: Dialogs are stored in XML code and scripts are stored in JavaScript code. Because of platform-independent XML and script code, the plugin C++ code does not require to link in any platform-specific GUI libraries. Since BrainVoyager interprets the XML and script code, only the computational C/C++ plugin code needs to be compiled on Windows, Linux and Mac OS X to create cross-platform GUI plugins.

GUI Scripts and Plugins 2

As an example, let’s assume we want to create a plugin that allows to perform a few operations on VMR documents, including counting the number of voxels in a certain intensity range, inverting intensity values and an operation to undo performed changes. While these operations will be implemented as a plugin, a dialog will be used to allow a user to chose a function and to present results calculated in the plugin (see figure above). The dialog can be created graphically with the freely available “Qt Designer” program from Nokia (formerly Trolltech). With that program it is very easy to add to an empty dialog the three buttons “Undo”, “Quit” and “Invert” as well as two group boxes called “Intensity range” and “Voxels in intensity fields”. Two spin boxes for changing the minimum and maximum intensity value are moved into the “Intensity range” field. Finally, a “Count” button as well as a text field with a text label “No. of voxels” are placed in the “Voxels in intensity range” field. When the dialog has been completed, it may be saved in an XML file with the “.ui” extension, e.g. as “ExampleGUIPlugin.ui”. Note that the names of dialog elements (not the depicted texts) will be used in the script code to read or set the properties of dialog elements (also called “widgets”).

GUI Scripts and Plugins 3

The figure above shows part of the script file and relevant commands to “bind” the created dialog to the script. The first command (line 2) creates a JavaScript object (“new Object” and assigns it to the variable “scriptObj”. This step is important because the created object will be kept “alive” as long as the dialog is in use and all attached properties and functions are accessible during the dialog’s lifetime. The next two commands set values to the two important properties that are interpreted by BrainVoyager in a special manner. The first property “dialogFileName” specifies the name of the GUI dialog that will be used in association with this script. Since we want to use the previously specified dialog, its name (“ExampleGUIPlugin.ui”) is used here. When BrainVoyager evaluates the script, it will look for the respective filename within the “Plugins_32/64” folder (or in a subfolder within that folder if specified); if the file exists, BrainVoyager reads the XML file and shows the dialog to the user; the dialog looks the same as previously defined. The second property, “dialogAccessName” allows the script programmer to specify a name that he wants to use within the script to access the dialog (“PluginDialog” in this example). Access to the dialog automatically provides access to all of its child elements as nested property values using the usual point notation, e.g. the notation “PluginDialog.invertButton” will access the “Invert” button. Since BrainVoyager internally defines the dialog itself as a property of the persistent script object, you need to access the dialog using the “this” pointer within functions of the “scriptObj” object, e.g. “this.PluginDialog.invertButton”.
While the described way to access the dialog is already sufficient to set values in the dialog, we need as the most important GUI functionality a way to call specific functions in the script when the user interacts with the dialog; when the user, for example, clicks the “Invert” button, a corresponding function to handle this situation should be called in the script. Such connections between actions in the dialog and script callback functions can be easily specified. After BrainVoyager has loaded and displayed the dialog, it will call once the script objects “initDlg()” function that should contain all connections between GUI actions and specific script functions. Line 10 in the script code above shows one example of such a connection command:

this.PluginDialog.invertButton.clicked.connect(this, this.onInvert);

The part “this.PluginDialog.invertButton” accesses the “Invert” button as described above. When actions are performed on GUI elements, they emit certain “signals” that can be linked to specific script functions. These signals can be specified as properties of a GUI element. A button, for example, has the “clicked” signal that is emitted each time when the button is clicked. In order to specify the script function that should be called, the “connect” function is appended to the name of the signal. The connect function takes two parameters with the second parameter specifying the script function to be called when the signal is emitted. Since the connected function must be a property of the script object, we use the notation “this.[function-name]”. In the example code, the second parameter of the connect function is “this.onInvert” indicating that the script objects’ “onInvert()” function should be called when the “Invert” button is clicked. The first parameter must be always “this” when connecting dialog elements to script functions. From line 14 onwards the script function “onInvert()” is defined as a property of the script object. The first command within the function calls the “PrintToLog()” command of the provided BrainVoyager interface that writes the provided string to the Log pane.

Until this point we have defined a GUI script but not yet a GUI plugin. to support GUI plugins, scripts may not only interact with a GUI component but also with the plugin object defined in C/C++ plugin code. The BrainVoyager interface has been extended with the “ExecutePlugin()” command that allows a script to directly call the “execute()” function of a plugin object:

BrainVoyagerQX.ExecutePlugin();

Furthermore, the BrainVoyager interface allows to create string and numerical variables that can be read and written from both GUI scripts and GUI plugins. In line 18 above, the command

BrainVoyagerQX.SetPluginStringParameter("Command", "Invert");

defines a string variable “Command” and sets it value to “Invert”. As we will see below, the plugin code will read the value of the “Command” variable and runs code that depends on the provided value.

GUI Scripts and Plugins 4

The figure above shows part of the C/C++ plugin code. BrainVoyager decides whether a plugin is a GUI plugin by interrogating the plugin for a script file name using the C function “getGUIScript()”. If that file name is not empty and exists on disk, a GUI plugin is assumed, otherwise a purely computational plugin. In the latter case, BrainVoyager calls the two functions “initPlugin()” and “execute()” once in succession and if the plugin code returns, it deletes the plugin object. If the plugin is a GUI plugin, BrainVoyager only calls the “initPlugin()” function when clicking a plugin’s name in the “Plugins” menu. It then evaluates the referenced script file which itself loads the referenced dialog. The script takes control and may call the “execute()” function of the plugin object repeatedly. In order to read and set variables created by the script, the plugin may call the new variable exchange commands. In the example code above, the plugin reads the value of the “Command” variable with the “qxGetStringParameter()” command that was created in the GUI script file.

The code of the described example (“ExampleGUIPlugin”) is available at out web site and can be used as the basis for own GUI plugins. More detailed documentation is available in the updated User’s Guide as well as in new script documentation files coming with BrainVoyager QX 2.1.