Skip to main content

SVG and Java UIs part 4: beneath the hood

Posted by kirillcool on September 2, 2006 at 1:54 PM PDT

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

So, how it all works together in the flamingo test applications? The most important class is the org.jvnet.flamingo.common.ResizableIcon that defines the following extension of the Icon interface:

/**

 * Interface for icons that have resizability behaviour.

 

 @author Kirill Grouchnikov

 */

public interface ResizableIcon extends Icon {

  /**

   * Changes the dimension of <code>this</code> icon.

   

   @param newDimension

   *            New dimension for <code>this</code> icon.

   */

  public void setDimension(Dimension newDimension);



  /**

   * Changes the height of <code>this</code> icon.

   

   @param height

   *            New height for <code>this</code> icon.

   */

  public void setHeight(int height);



  /**

   * Changes the width of <code>this</code> icon.

   

   @param width

   *            New width for <code>this</code> icon.

   */

  public void setWidth(int width);



  /**

   * Requests <code>this</code> icon to revert to its original dimension.

   * May be safely ignored by the icon if it doesn't support such behaviour.

   */

  public void revertToOriginalDimension();

}

This interface is used extensively throughout the ribbon component (buttons, in-ribbon galleries, popup galleries). In addition, the SVG file previewer introduced in the previous entries uses this class as well. The implementation of this interface is up to the application - it may be Java2D-based, SVG-based or any other custom code.

The first sample SVG implementation is based on the SVG Salamander renderer and is quite straightforward thanks to the API provided by the base SVGIcon class in that library:

public class SvgSalamanderResizableIcon extends SVGIcon implements ResizableIcon {

  private Dimension origDim;



  public SvgSalamanderResizableIcon(URI svgUri, Dimension dim) {

    super();

    this.setSvgURI(svgUri);

    this.setAntiAlias(true);

    this.origDim = dim;

    this.setScaleToFit(true);

    this.setPreferredSize(this.origDim);

  }



  public SvgSalamanderResizableIcon(URL svgUrl, Dimension dim) {

    super();

    try {

      this.setSvgURI(svgUrl.toURI());

    catch (URISyntaxException urise) {

      throw new IllegalStateException(urise);

    }

    this.setAntiAlias(true);

    this.origDim = dim;

    this.setScaleToFit(true);

    this.setPreferredSize(this.origDim);

  }



  public void revertToOriginalDimension() {

    this.setPreferredSize(this.origDim);

  }



  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));

  }



  @Override

  public void paintIcon(Component c, Graphics g, int x, int y) {

    try {

      super.paintIcon(c, g, x, y);

    catch (Throwable t) {

      // swallow it

    }

  }

}

Note that the try-catch block in the paintIcon implementation is due to some exceptions being thrown in the base code (reported here).

The second sample implementation is based on the Apache Batik SVG renderer and is more complex due to the lack of Icon-extending base class in Batik (reported here). In addition, Batik behaves inconsistently on resizing SVG files that use pixel-based (such as the Tango set) and point-based (such as the GNOME set) coordinates. The implementation below works correctly for pixel-based SVG files. In order to work on the point-based SVG files, remove all manipulations with affine transformations.

public class SvgBatikResizableIcon extends JSVGComponent implements

    ResizableIcon {

  private AffineTransform originalTransform;



  private boolean isLoaded;



  private Dimension initialDim;



  public SvgBatikResizableIcon(URL location, final Dimension initialDim) {

    super();

    this.initialDim = initialDim;

    this.isLoaded = false;

    this.addGVTTreeBuilderListener(new GVTTreeBuilderAdapter() {

      public void gvtBuildCompleted(GVTTreeBuilderEvent e) {

        originalTransform = getRenderingTransform();

        Dimension2D fileContentsDim = getSVGDocumentSize();

        double coef = initialDim.getWidth()

            / fileContentsDim.getWidth();

        AffineTransform at = AffineTransform.getScaleInstance(coef,

            coef);

        setSize(initialDim);

        if (at != null) {

          at.concatenate(originalTransform);

          setRenderingTransform(at, true);

          isLoaded = true;

          if (getParent() != null) {

            getParent().doLayout();

            getParent().repaint();

          }

        }

      }

    });

    this.loadSVGDocument(location.toString());

    this.setOpaque(false);

    // Apparently the JGVTComponent doesn't respect the

    // opacity in paintComponent.

    this.setBackground(new Color(0x0true));

  }



  public void revertToOriginalDimension() {

    this.setDimension(this.initialDim);

  }



  public void setDimension(Dimension dim) {

    if (!isLoaded)

      return;

    setSize(dim);

    Dimension2D fileContentsDim = getSVGDocumentSize();

    double coef = dim.getWidth() / fileContentsDim.getWidth();

    AffineTransform at = AffineTransform.getScaleInstance(coef, coef);

    if (at != null) {

      at.concatenate(originalTransform);

      setRenderingTransform(at);

    }

  }



  public void setHeight(int height) {

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

  }



  public void setWidth(int width) {

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

  }



  public void paintIcon(Component c, Graphics g, int x, int y) {

    if (!isLoaded)

      return;

    Graphics2D graphics = (Graphics2Dg.create();

    graphics.setTransform(AffineTransform.getTranslateInstance(-x, -y));

    this.paintComponent(graphics);

    graphics.dispose();

  }



  @Override

  public void paint(Graphics g) {

    super.paint(g);

  }



  public int getIconHeight() {

    if (!isLoaded)

      return this.initialDim.height;

    return this.getSize().height;

  }



  public int getIconWidth() {

    if (!isLoaded)

      return this.initialDim.width;

    return this.getSize().width;

  }

}

Some of the complexity comes from the asynchronous load mode in the base class (which is an excellent feature although it has some problems), while some comes from the fact that this is not a real icon. As the base class extends JComponent, i had to make some changes to the code that uses the resizable icons in the ribbon implementation to account for the ResizableIcon which is also a JComponent.

Note that these two implementations are sample only - if you decide to use them, most probably they'll have to be made more robust. In addition, i didn't explore the option of using the TinyLine SVG renderer and its wrapper from the XUI framework

Related Topics >>