Wiki

Clone wiki

Okapi / Okapi_SWT

Making GUI with the Okapi SWT framework

Preface

Okapi provides a framework to allow you quickly and painlessly build rich user interfaces for various Okapi components.

The framework is pure SWT. You can use SWT Designer with the framework, or do things manually if you wish so.

Parts of the framework

The framework is located in the net.sf.okapi.common.ui.abstracteditor package of the okapi-core-ui Maven project.

Examples of use

  • okapi-filter-html-ui

  • okapi-filter-plaintext-ui

  • okapi-filter-table-ui

General concepts

The framework introduces the container-page-control model.

  • A container is a window (shell), hosting one or more pages.

  • A page is a panel (composite), hosting one or more controls, groups, or composites.

  • A control is an SWT widget, like a check-box or push-button.

The framework provides the means for controls’ interoperability. A control on one page can communicate with any other control either on the same page, or on a different page in the same or a different container.

Containers can have any SWT-supported style. In Okapi parameters editors there are normally multi-tab resizable dialogs and pop-up OK/Cancel dialogs. The framework provides base classes for those.

Every tab in the multi-tab dialog is a page. The client area of the OK/Cancel dialog is also a page.

Configuring SWT Designer

If to create the pages you will be using the SWT Designer, you should configure Eclipse in a special way:

In Window/Preferences/WindowBuilder/SWT/Code Generation:

  • check Use the existing code generation settings...

  • in Default code generation settings/Variable generation activate Field

  • in Default code generation settings/Statement generation activate Flat (optional)

In Window/Preferences/WindowBuilder/SWT/Code Generation/Variables/Miscellaneous:

  • check Remember variable name in component(!!! Important !!!)

If you do SWT manually...

When you create a control, don’t forget to put its name in the “name” property:

    myButton = new Button(parent, SWT.NONE);
    myButton.setData("name", "myButton");

The framework in a nutshell

  • Every page is a subclass of the SWT composite plus an implementation of the IDialogPage interface.

  • The multi-tab parameters editors are subclasses of AbstractParametersEditor, OK/Cancel dialogs are subclasses of AbstractBaseDialog. For a query dialog there’s no need to create a new subclass, you can call SWTUtils. inputQuery(), passing it a custom dialog page class.

  • There are 2 kinds of communication in the framework: internal (in-page) and inter-page. Internal is the communication between controls of the same page. It’s provided by the interop() method of the page. Inter-page communication is provided by the editor class, and is contained in its interop().

  • To configure the internal interop, write a body to the auto-generated interop() method, and call it from event listeners of all involved controls.

  • To assign rules for inter-page interop, make a body for the editor’s interop() method, and call addSpeaker(pageClass, controlName) from createPages().

  • SWTUtils provide a range of helpers to easily configure inter-page interop. A few of the helpers are: enableIfSelected(target, source), disableIfNotSelected(target, source).

You can omit the remainder of this document unless you have a time to try an example hands on.

Let’s give it a try...

Now we will create a parameters editor with 2 tab pages, and a dialog box for user input, run from a button on the second page.

The first page has a check-box controlling availability of the second page’s button.

Creating a new parameters editor project

  • Create a new Java Plug-in Project (File/New/Other/Plug-in Development/Plug-in Project)

  • Give it a name, here swt_test

  • Open the manifest MANIFEST.MF

  • On the Dependencies tab add plug-ins:

    • org.eclipse.swt
    • net.sf.okapi.common
    • net.sf.okapi.ui.common
  • On the Runtime tab add swt_test to have your editor visible from outside.

Creating the main editor class

  • Right-click on the src/swt_test package, New/Class

  • In Name type in the standard name for an editor class: Editor. For Superclass type in: net.sf.okapi.common.ui.abstracteditor.AbstractParametersEditor

    (Hint: You can click Browse and start typing AbstractParametersEditor in the SupeclassSelection dialog)

  • Make sure Inherited abstract methods is checked, click Finish.

  • Eclipse will auto-generate 4 methods in the new class:

    • protected void createPages(TabFolder pageContainer) -- creates pages and provides references to the controls participating in inter-page interop.
    • public IParameters createParameters()-- provides the parameters class (return new net.sf.okapi.filters.table.Parameters();)
    • protected String getCaption() -- provides the caption of your editor’s window (return “My Filter Parameters”;)
    • protected void interop(Widget speaker) -- provides interoperability between pages

Creating tabs

  • Select the src/swt_test package

  • Create a new page.

With SWT Designer: File/New/Other/WindowBuilder/SWT Designer/SWT/Composite.

Manually: File/New/Class. In Superclass: org.eclipse.swt.widgets.Composite.

  • Give it a name: FirstTab.

  • In the editor window in class declaration after extends Composite type in implements IDialogPage. This will turn the composite into the framework’s client. You have to do it manually, because SWT Designer doesn’t handle descendants of abstract classes, and wants a direct subclass of a Composite.

  • IDialogPage gets underlined, choose Import IDialogPage(net.sf.okapi.common.ui.abstracteditor)

  • Now FirstTab is underlined, choose Add unimplemented methods.

  • Eclipse will insert 4 method stubs:

    • public boolean canClose(boolean isOK) -- returns true if the dialog can be closed at the moment; parameter indicates if the dialog is being closed with OK.
    • public void interop(Widget speaker) -- provides interoperability between controls of this page, speaker is the control which fired the interop event
    • public boolean load(Object data) -- data contains the parameters being edited, controls should configure themselves according to the given parameters in this method.
    • public boolean save(Object data) -- changes parameters referred by data according to the controls’ settings. Returns true if parameters were saved from GUI successfully.
  • (Note: For compound filters this method is called several times for every internal filter, so the type should be checked before typecasting to the actual parameters class). Returns true if parameters were loaded to GUI successfully.

  • Important note: if the auto-generated canClose(), load(), andSave() return false, change it to return true, otherwise the editor won’t close, and load/save page errors will be displayed.

  • Place groups and controls on the page. (In SWT Designer click theDesign tab, and drop controls from the palette.) Here we put one group namedgroup1 and containing a check-box check1 and a button button1. The SWT Designer will create this code, which of course can be typed in manually:

private Group group1;
private Button check1;
private Button button1;

public FirstTab(Composite parent, int style) {

    super(parent, style);
    setLayout(new GridLayout(1, false));

    group1 = new Group(this, SWT.NONE);
    group1.setLayout(new GridLayout(2, false));
    group1.setText("First group");
    group1.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
    group1.setData("name", "group1");

    check1 = new Button(group1, SWT.CHECK);
    check1.setText("Enable buttons");
    check1.setSelection(true);
    check1.setData("name", "check1");

    button1 = new Button(group1, SWT.NONE);
    button1.setData("name", "button1");
    button1.setText("Don't click me!");
}
  • Create the second page. Give it the name SecondTab. Drop a group group2 and a button button2. The code is:
private Group group2;
private Button button2;

public SecondTab(Composite parent, int style) {

    super(parent, style);
    setLayout(new GridLayout(1, false));

    group2 = new Group(this, SWT.NONE);
    group2.setLayout(new GridLayout(1, false));
    group2.setText("Second group");
    group2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
    group2.setData("name", "group2");

    button2 = new Button(group2, SWT.NONE);
    button2.setData("name", "button2");
    button2.setText("Click Me!");
}
  • Add the created pages to the editor. Open Editor.java, and add the following code to createPages():
    addPage("General", FirstTab.class);
    addPage("Options", SecondTab.class);

Testing

  • In okapi-ui-harness open net.sf.okapi.testutilities.uiharness.Okapi_GUI_Tester.java

  • right-click on Okapi_GUI_Tester.java, Build Path/Configure Build Path...

  • Projects tab, Add, swt_test, OK

  • Add the name of the new editor class to the GUI_CLASSES list (lines 66 on): swt_test.Editor.class.getName()

  • Click Ctrl+F11 to run the GUI Tester, and double-click on swt_test.Editor. It will display a warning that parameters have not been created, and display the editor.

Creating a user input dialog

  • Create a third page. Give it the name LoginPage.

  • Type in implements IInputQueryPage in the class declaration.

  • Add unimplemented methods.

  • Change return false to return true. Or better find a place in Eclipse where to change the default.

  • Drop 2 labels and 2 text edits on it. The code is:

private Label lblName;
private Label lblPassword;
private Text name;
private Text password;

public LoginPage(Composite parent, int style) {

    super(parent, style);
    setLayout(new GridLayout(2, false));

    lblName = new Label(this, SWT.NONE);
    lblName.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
    lblName.setData("name", "lblName");
    lblName.setText("User Name:");

    name = new Text(this, SWT.BORDER);
    name.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
    name.setData("name", "name");

    lblPassword = new Label(this, SWT.NONE);
    lblPassword.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
    lblPassword.setData("name", "lblPassword");
    lblPassword.setText("Password:");

    password = new Text(this, SWT.BORDER);
    password.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
    password.setData("name", "password");
}
  • Open the SecondTab.java. Assign the following code to the select event of button2:
button2.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {

        if (SWTUtils.inputQuery(LoginPage.class, getShell(), "Enter your user name and password",  null, null)) {
        }
    }
});
  • Check in GUI Tester.

In-page interop

Now we want the check-box on the first page control the enabled state of the button on the same first page. We need to do the two things:

  • Set an event listener to the check-box to call interop:
check1.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {

        interop(null);
    }
});
  • change our interop() method in FirstTab.java:
public void interop(Widget speaker) {

    button1.setEnabled(check1.getSelection());
}

Inter-page interop

And the final fun we want the check-box on the first page to control the enabled state of the button on the other (second) page.

Also we want the login dialog to display a user name and password.

And we do those thing without touching the pages’ code. We do two things in swt_test.Editor.java:

  • Set references to the controls we want bound. Also bind them with SWTUtils helpers.
protected void interop(Widget speaker) {

    Control check1 = findControl(FirstTab.class, "check1");
    Control button2 = findControl(SecondTab.class, "button2");

    Control name = findControl(LoginPage.class, "name");
    Control password = findControl(LoginPage.class, "password");

    SWTUtils.enableIfSelected(button2, check1);
    SWTUtils.disableIfNotSelected(button2, check1);

    SWTUtils.setText(name, "user");
    SWTUtils.setText(password, "********");
}
  • Set "speakers", or controls initiating interop (we have only check1 speaker):
protected void createPages(TabFolder pageContainer) {

    addPage("General", FirstTab.class);
    addPage("Options", SecondTab.class);

    addSpeaker(FirstTab.class, "check1");
}

Where to now

You can try the Table filter parameters editor as it uses the framework to the upmost. The classes are located in okapi-filter-table-ui, also the Table filter employs the Options tab of the Plain Text filter (okapi-filter-plaintext-ui).

Updated