GUI Scripts and GUI Plugins
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.
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.
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”).
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:
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:
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
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.
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.