Skip to main content

SVG and Java UIs part 5: improving integration with Batik

Posted by kirillcool on September 3, 2006 at 5:33 PM PDT

This entry is the fifth part in the ongoing series about using SVG-based icons in Swing-based applications.

  • The first part outlined the need for using scalable icons in next-generation Java UIs and proposed using SVG images as a possible format, showing the ribbon component which employs heavy icon rescaling.
  • The second entry introduced an SVG file previewer based on the breadcrumb bar component, a ribbon button from the ribbon component and SVG Salamander renderer
  • The third part showed how the Apache Batik SVG renderer can initialize and scale SVG-based application in an asynchronous and non-blocking fashion.
  • The fourth entry showed what happens beneath the hood and described some of the problems found with Batik integration.

Cameron McCormack from Batik project has responded to some of the bug reports that i have filed. The most interesting one is bug 40397 - GNOME icons can't be parsed by Batik. Cameron's comments suggest that it's a problem with GNOME icons and not with Batik - we'll wait to hear from GNOME people. The relevant issues are 354103,
354192,
354194,
354205 and 354221. Thanks to Cameron for a prompt response.

The most annoying for me was RFE 40394 - provide an Icon-extending implementation. Cameron was kind enough to attach a base implementation and it took me the better part of the afternoon (listening to the new Nelly Furtado's album sounding alarmingly Paula Abdulesque... may be she's planning to judge the next "American Idol"... but i digress again... must finish entry...) to take this implementation and add asynchronous loading and resizability support. Most of the code for asynchronous loading was borrowed from the JGVTComponent (still one thread per icon and no thread pool yet), while the resizability support is modeled after the SVG Salamander API. The current implementation of Batik-based resizable icon is:

public class SvgBatikResizableIcon extends SvgBatikIcon implements

    ResizableIcon, AsynchronousLoading {

  private Dimension initialDim;



  /**

   * The listeners.

   */

  protected List asyncListeners = Collections

      .synchronizedList(new LinkedList());



  public SvgBatikResizableIcon(URL location, final Dimension initialDim) {

    super(location.toString(), initialDim.width, initialDim.height);

    this.initialDim = initialDim;

    this.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {

      @Override

      public void gvtRenderingCompleted(GVTTreeRendererEvent e) {

        fireAsyncEvent(asyncCompletedDispatcher, null);

      }

    });

  }



  public void revertToOriginalDimension() {

    this.setPreferredSize(this.initialDim);

  }



  public void setDimension(Dimension dim) {

    this.setPreferredSize(dim);

  }



  public void setHeight(int height) {

    this.setPreferredSize(new Dimension(height, height));

  }



  public void setWidth(int width) {

    this.setPreferredSize(new Dimension(width, width));

  }



  public void addAsynchronousLoadListener(AsynchronousLoadListener l) {

    asyncListeners.add(l);

  }



  public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {

    asyncListeners.remove(l);

  }



  public void fireAsyncEvent(Dispatcher dispatcher, Object event) {

    EventDispatcher.fireEvent(dispatcher, asyncListeners, event, true);

  }



  static Dispatcher asyncCompletedDispatcher = new Dispatcher() {

    public void dispatch(Object listener, Object event) {

      ((AsynchronousLoadListenerlistener).completed();

    }

  };

}

The most interesting part is in the constructor - it registers an GVT tree renderer listener and fires a complete event when the rendering is complete. The reason for this is that the icon doesn't live on its own and has a paintIcon method that gets a graphic context from the owner component. This icon implements a new AsynchronousLoading interface - a component that uses such icon can check if the icon is asynchronously-loading and register a completion listener:

    ResizableIcon buttonIcon = this.ribbonButton.getIcon();

    this.iconLabel = new JLabel(this.ribbonButton.getIcon());

    if (buttonIcon instanceof AsynchronousLoading) {

      ((AsynchronousLoadingbuttonIcon)

          .addAsynchronousLoadListener(new AsynchronousLoadListener() {

            public void completed() {

              ribbonButton.repaint();

            }

          });

    }

    this.ribbonButton.add(this.iconLabel);

This implementation addresses most of the issues that i had with Batik - the Icon-implementing class, preserving asynchronous loading and correct resizing of both Tango and GNOME icons. To launch the SVG file viewer using Batik renderer, click on the image below - note how the icons are shown and resized asynchronously leaving the UI responsive. Play with the slider to resize all currently shown icons. Note that this application is signed since (obviously) it requires access to the local disk. If you don't want to run it, download and watch the video link below.

In case you don't want / can't run the WebStart demoes or download the stuff from the projects pages, this video (1.3MB, 0:35 min, original WMV format renamed to AVI) shows the SVG file previewer with various animation effects (from the Substance look-and-feel) and responsive UI thanks to Batik.

Related Topics >>