Creating Java Swing components – the List Index Bar

In what may turn out to be a regular feature, I wanted to start talking about some of the work I do involving the creation of custom Java Swing components. As anyone who has seen the GUIs I create will know, I quite often create custom components for a variety of purposes. It’s not difficult to write custom Swing components, but there are a few hints and tips which can make this easier and result in well performing components. Hopefully this series will be interesting to those readers who are using Java Swing and inspire them to create their own interesting custom components.

So… let’s get started!

For one of the Java Swing GUI projects I’m working on there’s a requirement to display a visual representation of the index of a list of items with particular items in this index highlighted according to certain criteria. Think along the lines of the margin shown in intelligent code editors used to highlight warnings and errors in the code (such as the right hand margin in IntelliJ IDEA), and you’re not too far from the mark.

This is quite a specialised component so obviously doesn’t exist in the standard Swing set, but it’s a nice little component to write so I thought I’d share with you how I created it. To show you exactly what this component will look like when finished there’s a screenshot of it in action on the right.

Getting started

This will be a fairly simple component in terms of graphical content and behaviour, and there are no existing Swing components which are similar to it, so we will derive our new component from the base Swing component class, JComponent.

public class ListIndexBar extends JComponent {

}

Breaking the design down into the key aspects, we need to consider:

  • keeping track of how many items there are in the list this component represents
  • keeping track of the indices of all items which need to be highlighted
  • calculating the vertical pixel position and height of each item marker based on the current height and the total number of items in the associated list
  • painting the item markers
  • handling mouse events to change the cursor when hovering over item markers and to handle mouse clicks on item markers

So let’s start with the first couple of items.

private int itemCount;

// set of list indices associated with items to be marked
private Set<Integer> markerSet = new HashSet<Integer>();

public int getItemCount() {
  return itemCount;
}
public void setItemCount(int itemCount) {
  this.itemCount = itemCount;
  recalc();  // more on this later
  repaint();  // we've made changes which affect appearance so trigger a repaint
}

public Set<Integer> getMarkerSet() {
  return markerSet;
}

public void addMarkers(Collection<Integer> markers) {
  markerSet.addAll(markers);
  repaint();
}

public void removeMarkers(Collection<Integer> markers) {
  markerSet.removeAll(markers);
  repaint();
}

public void clearMarkers() {
  markerSet.clear();
  repaint();
}

Calculating item marker sizes

Now let’s do the simple calculations associated with working out where each item marker needs to be painted.

We know the height of the component and the total number of items in the associated list so we can calculate the vertical size each item marker needs to be. From this we can work out the vertical pixel position that each item marker needs to be painted at. Note, this calculation needs to be done whenever there is a change in the component size or when the total number of associated list items changes. Accordingly, we call recalc() in the setItemCount method and we also override the two setBounds methods to call recalc() after calling their superclass method.

private double scaleFactor;
private int markerHeight;

private void recalc() {
  scaleFactor = getHeight() / (double) itemCount;
  markerHeight = Math.max(2, (int) scaleFactor);  // markers have min height of 2
}

@Override
public void setBounds(Dimension dimension) {
  super.setBounds(dimension);
  recalc();
}

@Override
public void setBounds(int x, int y, int width, int height) {
  super.setBounds(x, y, width, height);
  recalc();
}

Painting

So now we get to the guts of the component – the painting code. Hopefully this should be self explanatory – there’s nothing too complex going on here.

@Override
protected void paintComponent(Graphics g) {
  // cast to a Graphics2D so we can do more with it
  Graphics2D g2 = (Graphics2D) g;

  // paint or clear the background depending on whether this component is opaque
  // or not. store the current composite so we can restore it later
  Composite composite = g2.getComposite();
  g2.setColor(getBackground());
  if (!isOpaque()) {
    // if not opaque, set the alpha composite to clear the background
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST));
  }
  g2.fillRect(0, 0, getWidth(), getHeight());
  g2.setComposite(composite);  // restore the previous composite

  // markers will be drawn with the foreground colour
  g2.setColor(getForeground());

  int pos;
  for (Integer marker : markerSet) {
    // for each marker, calculate the appropriate Y position
    // and paint a marker of required size
    pos = (int) (marker * scaleFactor);
    g2.fillRect(0, pos, getWidth(), markerHeight);
  }
}

Handling mouse events and finishing off

We’re nearly finished now – the only thing left is to add the code to handle mouse events and initialise the object in its constructor.

We need to handle three types of mouse event:

  • motion events so that we can change the cursor when hovering over an item marker to give a visual cue to the user that the item can be clicked on
  • mouse exit events so we can clear the highlighted index and reset the cursor when the pointer leaves the component
  • mouse click events when hovering over item markers

We also need to keep track of the index of the last highlighted index so it can be included in the selection notification event. If there is no currently highlighted index we set this to -1. We are re-using the existing Swing ListSelectionListener and associated ListSelectionEvent classes here for our notifications for convenience as they are a close match to what we need. We could quite easily enough have used our own custom listener and event classes which are more tailored to this particular situation – but it doesn’t make much difference.

And finally, we do the last bit of initialisation in the constructor, including setting the initial number of items and setting an initial minimum and preferred size so that the component appears with a sensible size when first created. Oh, and we provide an alternative no-arg constructor which uses a default item count of 1!

// the index of the currently highlighted marker index
// gets set when the pointer hovers over a marker and cleared when the mouse is moved off a marker
// or the pointer leaves the component completely
private int highlightedIndex = -1;

// keep track of listeners interested in marker selection events
private List<ListSelectionListener> listeners = new ArrayList<ListSelectionListener>();

public ListIndexBar(int itemCount) {
  this.itemCount = itemCount;
  recalc();

  // add a mouse motion listener to track the current highlighted marker
  addMouseMotionListener(new MouseMotionAdapter() {
    @Override
    public void mouseMoved(MouseEvent e) {
      // calculate the list index which is under the mouse pointer
      int pos = (int) (ListIndexBar.this.itemCount * (e.getPoint().getY() / getHeight()));
      if (markerSet.contains(pos)) {
        // we're over one of the markers so record the index and change the cursor
        highlightedIndex = pos;
        setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else {
        // we're not over any marker so clear the highlighted index
        // and reset the cursor
        highlightedIndex = -1;
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }
    }
  });

  // add a mouse listener to handle mouse clicks on markers
  addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent e) {
      if (highlightedIndex != -1) {
        ListSelectionEvent event = new ListSelectionEvent(ListIndexBar.this, highlightedIndex, highlightedIndex, false);
        for (ListSelectionListener listener : listeners) {
          listener.valueChanged(event);
        }
      }
    }

    @Override
    public void mouseExited(MouseEvent e) {
      // clear the highlighted index when we leave this component
      highlightedIndex = -1;
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
  });

  // give the component a min and preferred size
  setMinimumSize(new Dimension(16, 60));
  setPreferredSize(new Dimension(16, 60));
}

public ListIndexBar() {
  this(1);
}

public void addSelectionListener(ListSelectionListener listener) {
  listeners.add(listener);
}

public void removeSelectionListener(ListSelectionListener listener) {
  listeners.remove(listener);
}

And that’s it!

Wrapping up

This is not a particularly complex custom component so hopefully the code has been easily understandable and I hope it’s given you an insight into how you can create effective custom components without having to write too much code.

I’ve attached a complete copy of the component code together with an example class making use of the component below.

ListIndexBar component code and example

New Android dev tools – visual UI designer

I watched a video of one of the Google I/O 2011 talks last night by Xavier Ducrohet and Tor Norbye about the new Android development tools and was particularly interested in the new visual UI designer in ADT 11. I was very impressed with what I saw but one downside is that I believe it is only currently provided as an Eclipse plugin whereas I am 100% an IntelliJ IDEA man. Hopefully an equivalent IDEA plugin will appear before too long.

Beware of Maven resource filtering – AGAIN!

I recently blogged about problems I’d encountered with Maven filtering resource files that I didn’t actually want filtering resulting in corrupted resources in my target artifact. So you’d think I’d more careful from that point on, right?

Well, it’s just happened again! In the first situation I blogged about, the resource files in question were TrueType font files. In this latest occurrence I couldn’t understand why some native DLLs which I am packaging with my app appeared not to be loading correctly. After much head scratching, it finally dawned on me that they could be getting corrupted during the Maven build. When I checked the POM I found that I’d inadvertently switched on filtering for all resources by mistake with the result that the DLLs were being filtered and ending up corrupted. Once I’d corrected the filtering switch everything started working again.

So the moral is always be aware of the implications of switching Maven resource filtering on!

Passing arguments to surefire when using the Maven release plugin

I’ve recently been using the Maven release plugin more and more at work to simplify the process of releasing various Maven artifacts that we produce. I’ll not go into detail about the release plugin as you can read more about it here, but what I will say is that it does a lot of the manual grunt work for you associated with the release of an artifact e.g. checking for unresolved SNAPSHOT dependencies, updating POM versions, committing to your SCM, creating SCM tags etc. There are a few gotchas and quirks to getting it working reliably (hey, this is Maven we’re talking about!) but once it’s working it makes life a little easier.

We use Hudson extensively as our Continuous Integration server to build and test our Maven projects, and we’ve got several jobs configured to allow releases to be performed using the M2 release Hudson plugin. This was all working just fine until we attempted to release something which had unit tests requiring certain properties to be set defining the environment the tests should be executed in. Doing this from the command line involves passing a couple of properties to the surefire plugin using the argLine plugin parameter as discussed here. However, when the tests were executed as part of the release plugin lifecycle, these properties just weren’t being recognised.

Eventually after some Googling (how often is that the case!) we came across a blog post which discussed a little-documented feature of the release plugin that allows arguments to be passed to the surefire plugin using the -Darguments option. And with a bit of careful nesting of single and double quotes were finally able to get the required properties into the surefire plugin as part of the release plugin lifecycle as follows:

-Darguments=”-DargLine=’-Denv=dev -Dsite=london'”

Substance custom table cell renderers

I use the Substance Look & Feel in most of my Java Swing GUIs these days. It’s a very well engineered, mature, configurable implementation of a Swing look & feel which can transform the appearance of any Swing GUI. The author Kirill Grouchnikov has done a great job with it over the years and I’d like to thank him for his work on this project (and his other projects such as the Trident animation library and Flamingo component suite).

Although Substance is extremely configurable, one area that I’ve had problems with in the past is that of custom table cell renderers. Substance provides default renderers for common cell value classes such as strings, numbers, booleans etc. which covers most use cases. However, if you want to customise the cell renderer behaviour and still benefit from all of the Substance effects such as row striping, selection highlighting animation etc. you’re quite limited because Substance enforces that your custom renderer is sub-classed from the SubstanceDefaultTableCellRenderer, which itself sub-classes DefaultTableCellRenderer. This is fine as long as your custom renderer is happy to be, effectively, a sub-class of JLabel, but for anything involving custom painting it’s no good.

This is one area that I think Substance could possibly be improved to make it a bit more extensible and flexible.

One specific situation I encountered this problem with recently was where I wanted to display a mini bar-chart in a table cell. The actual custom bar component was a trivial amount of custom painting but I couldn’t see an easy way of getting this into a Substance table cell renderer. I tried creating a simple cell renderer which extended the bar component and implemented the obligatory TableCellRenderer interface, and although this worked to a degree, I lost all of the nice Substance effects mentioned above by virtue of the fact that the renderer wasn’t derived from SubstanceDefaultTableCellRenderer. I managed to get row background striping back quite easily by calling the SubstanceStripingUtils.applyStripedBackground() method but that wasn’t good enough.

Then I had a mini flash of inspiration triggered when I noticed that Substance provided a default renderer for icons…

Because the Substance default renderer is based on a label, I could set the icon property to an implementation of the javax.swing.Icon interface to get my custom painting “injected” into the renderer. The steps involved were:

  1. Make my custom bar component implement the Icon interface. This was simply a case of moving my custom painting code from the paintComponent method to paintIcon and also providing implementations of the getIconWidth and getIconHeight methods.
  2. Create a simple SubstanceDefaultTableCellRenderer sub-class which wraps an instance of my bar component. In the renderer constuctor I call setIcon to attach the bar component to the renderer. The renderer also needs to override the two setBounds methods so that it can set the size of the wrapped bar component, otherwise it wouldn’t resize with the table cell! Oh, and of course an overridden setValue method needs to update the wrapped bar component value as appropriate.

As a result, my two classes looked something like this:

BarIconComponent

package com.purplegem.substanceplay;

import javax.swing.*;
import java.awt.*;

/**
 * Simple custom component which renders a mini bar chart.
 * Implements the Icon interface so it can be used where an Icon can.
 */
public class BarIconComponent extends JComponent implements Icon {

  private double value = 0.5d;

  public BarIconComponent() {
    setSize(40, 20);
  }

  public double getValue() {
    return value;
  }

  public void setValue(double value) {
    this.value = value;
  }

  @Override
  public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.GREEN);
    g2.fillRect(0, 0, (int) (getIconWidth() * value), getIconHeight());
  }

  @Override
  public int getIconWidth() {
    return getWidth();
  }

  @Override
  public int getIconHeight() {
    return getHeight();
  }

  @Override
  protected void paintComponent(Graphics g) {
    paintIcon(this, g, 0, 0);
  }

}

BarCellRenderer

package com.purplegem.substanceplay;

import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer;

import java.awt.*;

/**
 * Custom Substance table cell renderer which renders a mini bar
 * chart using the wrapped BarIconComponent instance
 */
public class BarCellRenderer extends SubstanceDefaultTableCellRenderer {

  private BarIconComponent barComponent = new BarIconComponent();

  public BarCellRenderer() {
    // attach our bar component as an icon
    setIcon(barComponent);
  }

  @Override
  public void setBounds(int x, int y, int width, int height) {
    super.setBounds(x, y, width, height);
    // ensure we update the size of the wrapped bar component
    barComponent.setSize(width, height);
  }

  @Override
  public void setBounds(Rectangle r) {
    super.setBounds(r);
    // ensure we update the size of the wrapped bar component
    barComponent.setSize((int) r.getWidth(), (int) r.getHeight());
  }

  @Override
  protected void setValue(Object value) {
    if (value != null) {
      // update the value of the bar component for this particular table cell
      barComponent.setValue((Double) value);
    }
  }

}

Once I’d implemented all of this I then had exactly the result I was expecting – a mini bar chart in a table cell with all the expected Substance table cell effects :-)

Uploading files using scp and the Maven Wagon plugin

I’ve been struggling with a little Maven problem for a while but only just managed to find time to look into it in any detail. What I’ve been trying to do is copy an artifact to a remote server using scp. The artifact in question is a WAR which I want to copy to a server hosting Tomcat, so this is not a typical deploy-artifact-to-repository type of requirement.

(As an aside, I know all about the Cargo plugin for deploying web apps to servlet containers but in this instance I’m more interested in the more general issue of copying any artifact, be it a WAR or a JAR or something else, using scp)

In principle this sounds like a very simple thing to do. The Maven Wagon plugin is the tool for the job but the documentation is woefully inadequate and I just could not get it to do what I wanted.

Anyway, after a lot of Googling and, crucially, inspecting Maven debug output from failed attempts at using the plugin I’ve finally cracked it.

Everything I’d seen written about this involved the following aspects…

Configuring details about the server to be copied to (typically in your main Maven settings.xml configuration):

<server>
  <serverId>my-server-id</serverId>
  <user>my-user-name</user>
  <password>my-password</password>
</server>

Using the Wagon plugin to perform the actual copy:

<build>
  <extensions>
    <extension>
       <groupId>org.apache.maven.wagon</groupId>
       <artifactId>wagon-ssh</artifactId>
       <version>1.0-beta-6</version>
     </extension>
   </extensions>

   <plugins>
     <plugin>
       <groupId>org.codehaus.mojo</groupId>
       <artifactId>wagon-maven-plugin</artifactId>
       <version>1.0-beta-3</version>
       <configuration>
         <fromFile>${project.build.directory}/${project.build.finalName}.war</fromFile>
         <url>scp://my-server-id.fully.qualified.domain/path/to/destination</url>
       </configuration>
       <executions>
         <execution>
           <id>upload-war-to-server</id>
           <phase>deploy</phase>
           <goals>
             <goal>upload-single</goal>
           </goals>
         </execution>
       </executions>
     </plugin>
   </plugins>

 </build>

All looks logical… but it simply refused to work, complaining about authentication failures. I knew the corresponding <server> configuration block was using the correct username and password, so the symptoms suggested that it wasn’t finding the <server> configuration. I’d made sure the host part of the server domain in the scp:// URL matched the server id element but it just wouldn’t match them up.

And then I noticed something in the Wagon plugin’s debug output – mention of a serverId property in the configuration. I’d not seen this documented anywhere before, but I thought I’d try adding it to my Wagon plugin configuration all the same…

      <configuration>
        <serverId>my-server-id</serverId></pre>
        <fromFile>${project.build.directory}/${project.build.finalName}.war</fromFile>
        <url>scp://my-server-id.fully.qualified.domain/path/to/destination</url>
      </configuration>

…and all of a sudden it started working! So, in my situation that appears to have been the missing link between my Wagon plugin and the server configuration details.

Beware when filtering TrueType font resources in Maven

I’ve just come up against an interesting problem while loading custom TrueType fonts bundled with a Java Swing GUI built using Maven.

Initially, for simplicity and because I’d never actually tried creating custom fonts from TTF files before, I loaded the TTF file from an absolute location i.e. not relative to the JAR classpath. Everything worked fine so I proceeded to package the TTF file as a regular resource file packaged into my JAR from the standard Maven src/main/resources directory, and changed the font loading code to load it relative to the JAR classpath. That’s when strange things started to happen…

I noticed that certain glyphs in the font were very subtlely wrong. For example, the top of all “S” glyphs was squashed slightly. I switched my code back to using an absolute file path (referencing the /src/main/resources location) and everything looked fine again. That got me wondering if the font file could be getting corrupted somehow when being packaged into the JAR.

And then it dawned on me… I’m filtering other resources so could that be the problem?

You bet it was!

It turns out that the TTF file was being filtered when packaged into the JAR. As soon as I excluded TTF files from this filtering, everything worked as expected again.

So, the moral of this is watch out for resource filtering when using TrueType font files (or any other file that could potentially be damaged by undesirable filtering).

I’m loving MiG Layout :-)

My Java Swing layout manager of choice to date has been the good old GridBagLayout (with a healthy dose of BorderLayout where applicable). However, there’s a new kid on the block (well, at least on my block)… and that is the MiG Layout (http://www.miglayout.com/)

I heard about MiG Layout ages ago and from what I read about it, it sounded pretty cool. But I’d never actually used it – until now.

I’ve just been doing some GUI work which required some hairy layout management so I thought I’d give MiG Layout a try to see how it fared. And I’m glad to report that it handled it with ease. So I’d definitely recommend you give it a try if you haven’t already.