Customizing your AIBench application

The configuration files

AIBench comes with several configuration files in order to change its default behavior. They are all inside the src/main/global-resources/conf directory.

  • aibench.conf. Basic configuration of the AIBench runtime and bootstrap process.
  • core.conf. Core plugin configuration. It basically configures the thread pool size, overrides names and menu-location of Operations, and enables mutable Datatypes observation.
  • workbench.conf. Workbench plugin configuration. Override components visibility and placement, built-in icons, etc.
  • template.xml. Configure the Workbench layout.

Note

Each plugin has its own configuration files, so which ones are used in a multi-plugin application? You have only one plugin per project, so the .conf files of the plugin you run AIBench with (./target/run.sh) are the only configuration files that will be considered. The configuration files in dependent plugins are ignored.

Note

Why plugin.xml and .conf files? As you have seen, we have a plugin.xml file inside each plugin, as well as configuration files. Moreover, some configurations can be done in .conf, as well as in plugin.xml (for example, the path defining the operations place on the menu bar). You should keep in mind two things:

  • Configuration files override plugin.xml options. The plugin.xml is focused on default behavior, and configuration files are more focused on final customization of the application. For example, we would like to place a operation in File/Import menu when developing the plugin (we should use plugin.xml), but if we reuse this plugin in another project, we may move this operation to Dataset/Import (here we should use the configuration).
  • It is a good idea to define options in the plugin.xml and, optionally in the configuration files. If your plugin will be reused in another AIBench project, remember that your configuration file will not be active in that project.

Main window configuration

The workbench.conf allows you establish the following options to customize the main window:

  • mainwindow.title: it allows you to define the title of the main window.
  • mainwindow.maximized: it can be true or false and allows you to define if it must be maximized when the application starts or not.

Changing the Splash Screen

You can use your own splash by changing the splashimage parameter in aibench.conf. The parameter value should be relative to src/main/global-resources/. For example:

# You should create the file: src/main/global-resources/my-splash.png
splashimage = my-splash.png

Adding icons

Basic icons

You can add icons for Operations and for Datatypes. They are defined in plugin.xml:

<extension uid="aibench.workbench" name="aibench.workbench.view" >
  <icon-operation
    operation="sampleplugin.sumoperationid"
    icon="icons/oneicon.png"/>

  <big-icon-operation
    operation="sampleplugin.sumoperationid"
    icon="icons/onebigicon.png"/>

  <icon-datatype
    datatype="sampleplugin.OneClass"
    icon="icons/othericon.png"/>
</extension>

The icon attribute indicates the path to the icon file. This path is relative to src/main/resources of your plugin.

Overriding Workbench built-in icons

If you want to replace the Workbench (the user interface) default icons, you have the following options in the workbench.conf configuration file.

# Clipboard root icon
icon.clipboard = icons/clipboard.gif
icon.datatype = icon/datatype.png

# Custom help icon
paramswindow.helpicon = icons/dialog-help.png

#  Dialog buttons customization
#  Ok button label and text
paramswindow.buttonicon.ok = icons/ok.png

#  Cancel button label and text
paramswindow.buttonicon.cancel = icons/cancel.png

#  Help button label and text
paramswindow.buttonicon.help = icons/help.png

Note

Icon files are relative to the AIBench root directory, so you should place them inside src/main/global-resources. In the above configuration file, you should create the icons subdirectory in src/main/global-resources.

Configuring the Workbench Layout

The Workbench main window shows (i) the Clipboard and the History trees at both sides (ii) the Views showing Clipboard data in de center and (iii) additional components like log at the bottom.

You can reconfigure this behavior very easy without recompiling the application. The Workbench is implemented via a “table layout” configured in the src/main/global-resources/conf/template.xml file.

Here it is a possible configuration of the layout.

<table>
  <row>
    <cell width="25%" oneTouchExpandable="true">
      <table>
        <row>
          <cell>
            <components id='left' />
          </cell>
        </row>
      </table>
    </cell>
    <cell width="75%">
      <table>
        <row height="80%">
          <cell width="75%">
            <document_viewer/>
          </cell>
          <cell width="25%" oneTouchExpandable="true">
            <components id='right' hidetabs="true"/>
          </cell>
        </row>
        <row height="20%" oneTouchExpandable="true">
          <cell>
            <components id='bottom'/>
          </cell>
        </row>
      </table>
    </cell>
  </row>
</table>

Here is an example of the AIBench layout running:

_images/workbench-layout.png

As you can see in the file, the layout is very similar to an HTML table layout, defined by rows (similar to tr) and cells (similar to td). Rows and cells can specify a default height and width, respectively. In addition, there are two additional tags:

  • document_viewer. This tag is where the views of the Views are shown (to view results). Normally, located at the center of the window.
  • components. This tag defines a “slot” where one or more components could be placed. This slot has an id, so you can place custom components at these slot ids (see Adding custom components).

There are built-in components, such as the Clipboard tree or the Session (or History) tree, among others. You can configure where they are placed or if they are visible in the workbench.conf file:

clipboardtree.visible=true
clipboardtree.slot=right

Customizing input dialogs

If you don’t like the default generated input dialog for some operation, you can make your own dialog. Basically you have to implement a class which implements the es.uvigo.ei.aibench.workbench.InputGUI interface and connect it to the Workbench in the plugin.xml file.

<extension uid="aibench.workbench" name="aibench.workbench.view" >
   <gui-operation operation="OperationUID" class="samplePlugin.ClassName"/>
</extension>

Here, samplePlugin.ClassName is your class implementing InputGUI.

To implement this interface, you can start from scratch (more complex, more flexible), or you can extend an existing class (easier, less flexible). But before continuing, you have to learn how to specify parameter values for operations in AIBench programmatically.

Specifying operation parameter values in AIBench

Note

Why do we not give simple values and use ParamSpec? AIBench is intended to make reproducible experiments, so each value should come from a known place, in order to be recreated in, for example, an automated re-execution of all the user steps. This is why we use ParamSpec and ParamSource.

You will need to create ParamSpec instances containing (i) the value of your parameter and (ii) the origin of such value, in order to be able to recreate the value in the future (specially in the case of complex objects).

public ParamSpec(
  String name,
  Class<?> type,
  Object value,
  ParamSource source) throws IllegalArgumentException {
    ...
}

public ParamSpec(
  String name,
  Class<?> type,
  ParamSpec[] values) throws IllegalArgumentException{
    ...
}

The first constructor is used to specify non-array values, and the second constructor is to give array values. The ParamSource defines where the value comes from:

public enum ParamSource {
      STRING,
      STRING_CONSTRUCTOR,
      ENUM,
      CLIPBOARD,
      SERIALIZED,
      MIXED;
}

Here you can see, where the values can be. Basically, in AIBench, a value which is forwarded to an Operation, could be:

  • A kind of “primitive” value:

    • STRING. A simple string value.
    • STRING_CONSTRUCTOR which is a value of a class that can receive a String in the constructor to create it (for example: Integer, Float, Double, ... and its primitive counterparts: int, float, double).
    • ENUM. An user-defined enum constant.
  • A complex object:

    • CLIPBOARD. The value must be a ClipboardItem, that is, a value previously generated with a past operation execution. You can retrieve this kind of items interacting directly with the Core, calling:

      Core.getInstance().getClipboard().getAllItems();
      
    • SERIALIZED. A String with a Base64-encoded serialized Java Object.

  • A recursive structure, that is, an array of ParamSpec. Here we use MIXED.

Note

Create objects in this way is tedious. We provide you with a “smart” utility that creates ParamSpec instances for you, trying to guess the correct ParamSource. It is the CoreUtils.createParams(...) method set. You will need to depend on the Core plugin (see Creating a dependency between plugins).

Creating your own dialog from scratch

In this alternative, you have to implement es.uvigo.ei.aibench.workbench.InputGUI interface into your viewer class and return an array of parameter specifications: es.uvigo.ei.aibench.core.ParamSpec[].

public interface InputGUI {

  public void init(ParamsReceiver receiver, OperationDefinition<?> operation);

  public void onValidationError(Throwable t);

  public void finish();
}

When the user requests the execution of a given operation, the init method of your dialog class is invoked. Here you have to start interacting with the user, for example bringing up a modal dialog. This method receives two parameters: a ParamsReceiver object, which must be used to send back the parameter values of the operation and OperationDefinition, which contains all the needed operation metadata (i.e.: its ports).

Using OperationDefinition, you could construct an user interface showing the port names, the correct component, or not showing anything to the user if you want, for example, to treat a given port a “hidden” parameter.

When you have calculated all the parameters, you have use ParamsReceiver

public interface ParamsReceiver {
      public void paramsIntroduced(ParamSpec[] params);
      public void cancel();
      public void removeAfterTermination(List<ClipboardItem> items);
}

With this object you can send the parameters via the paramsIntroduced method. This method receives a ParamSpec[] array, corresponding to each input port in its corresponding order. You have to construct a ParamSpec instance for each port and send it in the array (see Specifying operation parameter values in AIBench). If you do not want to invoke the operation, you should call cancel() instead. Finally, you can request the Core to remove some clipboard items after the operation execution, you can use removeAfterTermination before calling paramsIntroduced.

Once paramsIntroduced is called, the Core will try to run the operation. However, it will first validate the parameter values (see Validating user input). If the parameters were not validated, your onValidationError(Throwable t) method will be invoked. If the parameters are ok and the operation can run, the finish() method will be called instead.

Overriding default dialogs

You have to extend es.uvigo.ei.aibench.workbench.inputgui.ParamsWindow, which is the class that AIBench uses as default dialog. ParamsWindow defines the getParamProvider(...) method, intended to be overriden in order to change the component that will be used for a given port. This visual component and the parameter value are specified via the returning instance of the ParamProvider interface. The getComponent() method is used to specify the component and the getParamSpec() method is used for the parameter’s value (see Specifying operation parameter values in AIBench).

Let’s see the example:

public class SearchInputDialog extends ParamsWindow {
    private JTextField txt = new JTextField("Example");

    protected ParamProvider getParamProvider(
         final Port port, final Class<?> arg1, final Object arg2) {

      // change the default behavior for the port named "PortName"
      if (port.name().equals("PortName")) {

          return new AbstractParamProvider() {
            public JComponent getComponent() {
              return txt;
            }

            public ParamSpec getParamSpec()
             throws IllegalArgumentException {

              return new ParamSpec(
               port.name(), arg1, txt.getText(),
               ParamSource.STRING_CONSTRUCTOR);
               // more easy:
               // return CoreUtils.createParam(arg1);
            }

            public Port getPort() {
              return port;
            }
          };
       }

       // use the default behavior for the other ports
       return super.getParamProvider(port, arg1, arg2);
   }
}

Customizing error notifier

AIBench includes a default error dialog that is shown when an operation throw an uncontrolled exception during execution.

_images/defaulterrordialog.png

As can be seen, this dialog will show the message and the stack trace of the exception thrown. Although this is very useful during operation development, you may want to change it for final applications or in special cases where you want to show some specific information.

AIBench provides a way to change this dialog by just implementing an extension point of the Workbench plugin.

Creating your own error dialog from scratch

The first step to create a custom error dialog is to create a class that implements the es.uvigo.ei.aibench.workbench.error.ErrorNotifier interface.

public interface ErrorNotifier {

    public void showError(MainWindow mainWindow, Throwable exception);

    public void showError(
        MainWindow mainWindow, Throwable exception, String message);

}

Once you have your own ErrorNotifier, you must declare it as an extension of the extension point aibench.workbench.error.notifier of the aibench.workbench plugin. Go to the plugin.xml file of your plugin and add the following configuration to the <extensions> block.

<extensions>
    <extension uid="aibench.workbench"
               name="aibench.workbench.error.notifier"
               class="name.of.your.custom.ErrorNotifier"
    />
</extensions>

Where the name.of.your.custom.ErrorNotifier should be the complete class name of your custom error notifier.

Once this configuration is added your AIBench will start using your custom error notifier.

Dealing with multiple error notifiers

In some cases you may have several error notifiers configured in your plugin or in several plugins. When this happens, AIBench will use the first error notifier that it can find in the plugins.

When this happens, it is recommended to specify which error notifier should be used. To do so, you should add the error.notifier.class property to the workbench.conf file with the name of your custom error notifier class. For example:

# Custom error notifier (use "default" value for default error dialog
error.notifier.class = my.aibench.project.CustomErrorNotifier

In addition, in case you want to use the default error notifier instead of any custom error notifier, you can assign the default value to this property.

# Custom error notifier (use "default" value for default error dialog
error.notifier.class = default

Adding a Toolbar

The AIBench toolbar is implemented as a shortcut system.

To get the Toolbar working you have to:

  • Add a new attribute in the operation-description tag in the plugin.xml, shortcut=<number_for_position>. If it is present, the Workbench plugin will use it to place it in a toolbar. The specified number will be used for positioning.

    <extension
      uid="aibench.core"
      name="aibench.core.operation-definition"
      class="sampleplugin.Sum">
    
      <operation-description
        name="Sum Operation"
        path="10@Sample/1@SubmenuExample"
        uid= "sampleplugin.sumoperationid"
        shortcut="3"/>
    
    </extension>
    
  • You can also add an extra icon to show in the toolbar, for instance a larger icon than the default operation icon.

    <extension
      uid="aibench.workbench"
      name="aibench.workbench.view" >
    
         <icon-operation
          operation="sampleplugin.sumoperationid"
          icon="icons/oneicon.png"/>
         <big-icon-operation
          operation="sampleplugin.sumoperationid"
          icon="icons/onebigicon.png"/>
         <icon-datatype
          datatype="sampleplugin.OneClass"
          icon="icons/othericon.png"/>
    
    </extension>
    
  • To configure the toolbar in the workench.conf you have four new options:

    # default false
    toolbar.visible=true
    
    # a comma-separated list of positions where a separator should
    # be placed after
    toolbar.separators=1,5,6
    
    # default true
    toolbar.showOperationNames=true
    
    # toolbar position: default NORTH
    toolbar.position=NORTH
    

Adding Help to your Application

  1. Place your JavaHelp files in a global folder (for example: src/main/global-resources/help).
  2. Associate JavaHelp topics or URLs to:

1. Operations. Use the help attribute in the operation-description tag in your plugin.xml. The Workbench will display a help button in the input dialogs of this operations.

2. Datatype Views. Use the help attribute in the view tag in your plugin.xml

Example:

<extension uid="aibench.core" name="aibench.core.operation-definition" class="sampleplugin.Sum">
  <operation-description
    name="Sum Operation"
    path="10@Sample/1@SubmenuExample"
    uid= "sampleplugin.sumoperationid"
    help="mytopic.subtopic"/> <!-- using JavaHelp -->
</extension>

<extension uid="aibench.workbench" name="aibench.workbench.view" >
  <view name="Sample Datatype View"
    datatype="sampleplugin.OneClass"
    class="sampleplugin.OneViewComponent"
    help="http://myapp.com/help/topic1.html"/> <!-- your help could be online -->
</extension>

Moreover, the Workbench plugin offers you a configured button (es.uvigo.ei.aibench.workbench.utilities.HelpButton) that opens this JavaHelp. For example, this button can be be easily added to the toolbar with:

Workbench.getInstance().getToolBar().add(new HelpButton());

Adding custom components

You can create custom components, which are any class extending JComponent, by plugging them in your plugin.xml

<extension uid="aibench.workbench" name="aibench.workbench.view" >
  <component
    slotid="bottom"
    componentid="aibench.shell.shellWindow"
    name="AIBench Shell"
    class="es.uvigo.ei.sing.aibench.shell.ShellComponent"/>
</extension>

The slotid should exist in your template.xml file as the id of a components tag (see Configuring the Workbench Layout).

Adding a welcome screen

You can set any component extending JComponent as welcome screen by plugging it in your plugin.xml. This component will be added as initial tab in the views area when the application starts.

<extension uid="aibench.workbench" name="aibench.workbench.view" >
  <welcomescreen class="org.myorg.MyComponent" title="Welcome screen" closeable="true"/>
</extension>

The closeable property allows you to specify whether this welcome screen is closeable or not.