Trident animation library for Java applications is nearing release 1.1 (code-named Bogeyman), and it’s time to take a look at the new APIs added in this version. This entry is going to talk about supporting Java UI toolkits.

UI toolkit handlers

Graphical applications are a natural fit for animations, and Trident core has built-in support for Swing and SWT. This support covers threading rules, custom property interpolators and repaint timelines. Application code that needs to support additional Java-based UI toolkits should register a custom UI toolkit handler.

Most modern UI toolkits have threading rules that the applications must respect in order to prevent application freeze and visual artifacts. The threading rules for both Swing and SWT specify that the UI-related operations must be done on a special UI thread, and the methods in the org.pushingpixels.trident.UIToolkitHandler are used to determine the relevance of these threading rules. The UIToolkitHandler interface is illustrated by the core support for SWT:

public class SWTToolkitHandler implements UIToolkitHandler {
	@Override
	public boolean isHandlerFor(Object mainTimelineObject) {
		return (mainTimelineObject instanceof Widget);
	}

	@Override
	public boolean isInReadyState(Object mainTimelineObject) {
		return !((Widget) mainTimelineObject).isDisposed();
	}

	@Override
	public void runOnUIThread(Runnable runnable) {
		Display.getDefault().asyncExec(runnable);
	}
}

This is a very simple implementation of a UI toolkit handler that respects the relevant threading rules:

  • The isHandlerFor associates this handler with all SWT widgets
  • The isInReadyState marks disposed widgets to skip the property interpolation / callback invocations
  • The runOnUIThread runs the UI related logic on the SWT thread

Registering custom UI toolkit handlers

Trident provides two ways to register custom UI toolkit handlers – customization APIs and plugins.

The TridentConfig class has the following APIs to work with UI toolkit handlers:

  • addUIToolkitHandler(UIToolkitHandler) – registers the UI toolkit handler
  • removeUIToolkitHandler(UIToolkitHandler) – unregisters the UI toolkit handler
  • getUIToolkitHandlers() – retrieves an unmodifiable collection of all registered (core and custom) UI toolkit handlers

The UIToolkitHandler entries in the plugin descriptor files allow application code to support additional Java-based UI toolkits. The value associated with this key must be the fully qualified class name of an application class that implements the org.pushingpixels.trident.UIToolkitHandler interface.

Respecting the threading rules

The UIToolkitHandler.isHandlerFor(Object) is used to determine whether the main timeline object is a component / widget for the specific UI toolkit. At runtime, all fields registered with the Timeline.addPropertyToInterpolate methods will be changed on the UI thread using the UIToolkitHandler.runOnUIThread method.

In the simple Swing example that interpolates the foreground color of a button on mouse rollover, the timeline is configured as

Timeline rolloverTimeline = new Timeline(button);
rolloverTimeline.addPropertyToInterpolate("foreground", Color.blue,
	Color.red);

If you put a breakpoint in the JComponent.setForeground(Color) – which is called on every timeline pulse – you will see that it is called on the Swing Event Dispatch Thread. Internally, this is what happens:

  • When the timeline is created, all registered UI toolkit handlers are asked whether they are handlers for the specified object
  • The org.pushingpixels.trident.swing.SwingToolkitHandler registered in the core library returns true for the button object in its isHandlerFor(Object)
  • On every timeline pulse, a Runnable object is created internally. The run() method calls the setters for all registered fields – using the PropertyInterpolator.interpolate method of the matching property interpolator
  • This Runnable is passed to the UIToolkitHandler.runOnUIThread method of the matching UI toolkit handler.

And this is how SwingToolkitHandler.runOnUIThread() is implemented:

@Override
public void runOnUIThread(Runnable runnable) {
	if (SwingUtilities.isEventDispatchThread())
		runnable.run();
	else
		SwingUtilities.invokeLater(runnable);
}

Running custom application code on UI thread

The flow described above works for the fields registered with the Timeline.addPropertyToInterpolate methods. What about the custom application callbacks registered with the Timeline.addCallback()? If the callback methods need to respect the UI threading rules of the matching toolkit, the TimelineCallback implementation class needs to be tagged with the org.pushingpixels.trident.callback.RunOnUIThread annotation.

Callback implementations marked with this annotation will have both onTimelineStateChanged and onTimelinePulse invoked on the UI thread, making it safe to query and change the UI. The UIThreadTimelineCallbackAdapter is a core adapter class that is marked with this annotation.

Querying the readiness of the timeline object

The isInReadyState(Object) is the third and final method in the UIToolkitHandler interface. After the specific UI toolkit handler has declared that it will handle the main object of the specific timeline (by returning true from the isHandlerFor(Object) method), it will be used to interpolate the registered fields and run the registered callbacks. However, some UI toolkits may impose additional restrictions on when the UI object is ready to be queried / changed.

For example, once an SWT control is disposed, it will throw an SWTException in the setForeground method. So, if the application code is running a slow animation that changes the foreground color of a button, and the application window containing this button is disposed in the meantime, the call to setForeground should be skipped.

Substance 5.3 official release

September 28th, 2009

I am extremely pleased today to announce the availability of the final release or version 5.3 of Substance look-and-feel (code-named Reykjavik). The release notes for version 5.3 contain the detailed information on the contents of this release which include the following:

Click on the button below to launch a signed WebStart application that shows the available Substance features.

The following sub-projects are also available:

You are more than welcome to take Substance 5.3 for a ride. Sample screenshots of Substance 5.3 in action:

This has been on my to-do list for quite a long time, and over the last couple of months i have finally started to add unit tests to the Flamingo component suite. Before long, i have found myself stuck deep in a quagmire of nested runnables, complex interactions with the Event Dispatch Thread (EDT) and code that is hard to read, extend and maintain. And even then the tests behaved unpredictably, with some failing randomly – indicating incorrect interactions with the EDT.

At that moment i have realized that the true potential of open source is collaboration and reuse of existing libraries – unless you’re feeling that you can do better, of course :) The final tipping point came in July when Alex has published a blog entry on how FEST Swing library can facilitate writing clean and maintainable interactions with UI components – and this was it. Since then i have been adding FEST-driven unit tests to test different aspects of Flamingo components, including model manipulation, layout, and interaction with mouse and keyboard. Here, i’m going to show a small subset of these tests – illustrating the relevant parts of FEST Swing.

Before starting to delve into the project documentation, start with the blog entry on writing EDT-safe UI tests. It’s short, it’s clean and it’s quite illuminating. Then, head over to the main project page, read a little bit about the library and download the latest distribution (i’m using the latest 1.2a3 version). Once you’re done, add the following jars to your classpath:

  • fest-assert-1.1.jar
  • fest-reflect-1.1.jar
  • fest-swing-1.2a3.jar
  • fest-swing-junit-1.2a3.jar
  • fest-util-1.1.jar
  • junit-4.3.1.jar

Now let’s take a look at how a unit test class looks like. It starts by extending the base FEST class:

public class ActionCommandButtonTestCase extends FestSwingJUnitTestCase {

The biggest advantage of using this base class is that it automatically installs a custom repaint manager that tracks EDT violations – keeping both your unit tests and your main code clean from them. Next, it’s time to create the main window and a command button for the testing:

   JFrame buttonFrame;
   int count;
   JCommandButton button;

   @Override
   @Before
   public void onSetUp() {
      URL resource = ActionCommandButtonTestCase.class.getClassLoader()
            .getResource("utest/common/edit-paste.svg");
      Assertions.assertThat(resource).isNotNull();
      final ResizableIcon icon = SvgBatikResizableIcon.getSvgIcon(resource,
            new Dimension(32, 32));
      Pause.pause(new Condition("Waiting to load the SVG icon") {
         @Override
         public boolean test() {
            return !((AsynchronousLoading) icon).isLoading();
         }
      });

      GuiActionRunner.execute(new GuiTask() {
         @Override
         protected void executeInEDT() throws Throwable {
            buttonFrame = new JFrame();
            buttonFrame.setLayout(new FlowLayout());

            button = new JCommandButton("test", icon);
            button.setDisplayState(CommandButtonDisplayState.BIG);
            buttonFrame.add(button);
            buttonFrame.setSize(300, 200);
            buttonFrame.setLocationRelativeTo(null);
            buttonFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            buttonFrame.setVisible(true);

            count = 0;
            button.addActionListener(new ActionListener() {
               @Override
               public void actionPerformed(ActionEvent e) {
                  count++;
               }
            });
         }
      });

      GuiActionRunner.execute(new GuiTask() {
         @Override
         protected void executeInEDT() throws Throwable {
            Point locOnScreen = buttonFrame.getLocationOnScreen();
            locOnScreen.move(10, 20);
            robot().moveMouse(locOnScreen);
         }
      });
   }

While the documentation describes two ways of locating components for testing – fixtures and finders – i found it simpler to just store references to the relevant components, especially for test windows that have a very small number of controls.

The onSetUp method has the following main stages:

  • Create an SVG-based icon and wait for it to load. Here, Pause.pause is used to wait for the specific Condition – the name of the test() method is quite unfortunate, and IMO should be renamed to better reflect its purpose.
  • Then, GuiActionRunner.execute(GuiTask) is called to create the main window and the command button. This is by far my favorite method in FEST Swing – it runs the specified code on EDT and waits for it to finish. And while closures could have made the code even more readable, even in its present form it has enormous potential to simplify UI-related tests.
  • Finally, the same method is used to move the mouse away from the button – for subsequent tests of mouse interaction.

Once we have the setup method in place, time for the a very simple unit test:

   @Test
   public void sanityCheck() {
      String buttonText = GuiActionRunner.execute(new GuiQuery<String>() {
         @Override
         protected String executeInEDT() throws Throwable {
            return button.getText();
         }
      });
      Assertions.assertThat(buttonText).isEqualTo("test");
   }

Here, my second best-favorite feature – GuiActionRunner.execute(GuiQuery) – is used. It provides an EDT-safe way to query the current state of the specific UI component.

At this point it is important to note that UI testing can only be done to a certain degree. While the test above checks that the getText() returns the right value, it does not check that the visual representation of the button on the screen actually display this text (or any text for that matter). Testing the correctness of the painting routines is not simple. Even a straightforward approach of using an offline collection of “expected” screenshots will not work under look-and-feels that use the font settings of the current desktop – which varies not only across operating systems, but also across different DPI settings. As such, at the present moment my tests are focusing on checking the model, layouts and mouse / keyboard interactions.

Here are three tests to check that the associated action listener is invoked when the command button is activated with mouse, keyboard or API:

   @Test
   public void activateButtonWithMouse() {
      robot().click(button);
      robot().waitForIdle();
      Assertions.assertThat(count).isEqualTo(1);
   }

   @Test
   public void activateButtonWithSpace() {
      robot().moveMouse(button);
      robot().pressAndReleaseKeys(KeyEvent.VK_SPACE);
      robot().waitForIdle();
      Assertions.assertThat(count).isEqualTo(1);
   }

   @Test
   public void activateButtonWithAPI() {
      GuiActionRunner.execute(new GuiTask() {
         @Override
         protected void executeInEDT() throws Throwable {
            button.doActionClick();
         }
      });
      robot().waitForIdle();
      Assertions.assertThat(count).isEqualTo(1);
   }

Here i’m using different APIs of the Robot class to simulate the user interaction with keyboard and mouse, as well as EDT-safe invocation of the AbstractCommandButton.doActionClick() API.

Having these building blocks in place allows you to create more complex scenarios, testing various flows through your model classes. Here is a test case that checks that pressing the mouse, moving it away from the button and then releasing it does not activate the registered action listener:

   @Test
   public void pressButtonAndMoveAwayBeforeRelease() {
      robot().pressMouse(button, AWT.centerOf(button));
      robot().waitForIdle();

      robot().moveMouse(button, new Point(-10, 10));
      robot().waitForIdle();

      robot().releaseMouseButtons();
      robot().waitForIdle();

      // no action listener should have been invoked
      Assertions.assertThat(count).isEqualTo(0);
   }

Finally, a more complex scenario tests the API that activates the registered action listener on mouse press – as opposed to mouse release:

   @Test
   public void fireActionOnPress() {
      GuiActionRunner.execute(new GuiTask() {
         @Override
         protected void executeInEDT() throws Throwable {
            button.getActionModel().setFireActionOnPress(false);
         }
      });

      Assertions.assertThat(GuiActionRunner.execute(new GuiQuery() {
         @Override
         protected Boolean executeInEDT() throws Throwable {
            return button.getActionModel().isFireActionOnPress();
         }
      })).isFalse();

      // press mouse over the button
      robot().pressMouse(button, AWT.centerOf(button));
      robot().waitForIdle();
      // no action listener should have been invoked
      Assertions.assertThat(count).isEqualTo(0);

      // release mouse
      robot().releaseMouseButtons();
      robot().waitForIdle();
      // action listener should have been invoked
      Assertions.assertThat(count).isEqualTo(1);

      // mark the button to fire the action listeners on mouse press
      GuiActionRunner.execute(new GuiTask() {
         @Override
         protected void executeInEDT() throws Throwable {
            button.getActionModel().setFireActionOnPress(true);
         }
      });

      Assertions.assertThat(GuiActionRunner.execute(new GuiQuery() {
         @Override
         protected Boolean executeInEDT() throws Throwable {
            return button.getActionModel().isFireActionOnPress();
         }
      })).isTrue();

      // press mouse over the button
      robot().pressMouse(button, AWT.centerOf(button));
      robot().waitForIdle();
      // action listener should have been invoked
      Assertions.assertThat(count).isEqualTo(2);

      // release mouse
      robot().releaseMouseButtons();
      robot().waitForIdle();
      // no action listener should have been invoked
      Assertions.assertThat(count).isEqualTo(2);
   }

This entry showed a very small subset of what FEST Swing can do. I am still exploring this library as i continue adding more test cases to cover the command buttons – towards unit tests that will cover the ribbon component.

I am extremely pleased today to announce the availability of release candidate for version 5.3 of Substance look-and-feel (code-named Reykjavik). The release notes for version 5.3 contain the detailed information on the contents of this release which include the following:

Click on the button below to launch a signed WebStart application that shows the available Substance features.

The following sub-projects are also available as release candidates:

You are more than welcome to take Substance 5.3RC for a ride and report any problems in the project mailing lists, forums or issue tracker. The final release is scheduled for September 28. Only bugs will be fixed until that date.

Sample screenshots of Substance 5.3 in action: