ORIGINAL DRAFT

This month’s component allows us to present multiple bounded ranges in a single, integrated view. By providing a simple mechanism that communicating the relationships between ranges, JBalance allows the user to make balanced adjustments to these values. While JBalance is not a common type of user interface element, it provides a unique blend of visual cues which users may find more intuitive that a set of JSlider controls. In the right context, this is a powerful way to communicate with an application user, and one which I hope you’ll find useful in your Swing bag of tricks.

Figure 1 shows a screen shot of the JBalance component, with values associated with different types of resources. There are several useful classes in this component, including a CircleLayout manager. We’ll use the Swing BoundedRangeModel to define each of the adjustable values so that you can attach listeners to them if you like, managing them in a BalanceModel, which contains a String label for each BoundedRangeModel, along the spokes of our widget. What’s more, we’ll keep the view and the editing functionality loosely coupled so that you can use JBalance in different ways, depending on your needs.

Figure 1: JBalance in action.

Figure 1: JBalance in action.

Our implementation necessarily starts with a suitable BalanceModel. We’re primarily concerned with managing a set of BoundedRangeModel instances, each associated with a given String label. We need to support a getCount method to find out how many entries there are. We want to be able to add or remove ranges, get a range or label by it’s index value, and find the index value of a given label or bounded range.

We also expect to translate between a bounded range representation and an amplitude value on a regular basis. This will make it easier to manage the view and editing functionality. An amplitude value is a decimal number between zero and one. We’ll support translation both to and from an amplitude and a bounded range.

public interface BalanceModel
{
  public int getCount();
  public void addRange(String label, BoundedRangeModel range);
  public void removeRange(String label, BoundedRangeModel range);
  public BoundedRangeModel getRange(int index);
  public String getLabel(int index);
  public int indexOf(String label);
  public int indexOf(BoundedRangeModel model);
  public double getAmplitude(int index);
  public void setAmplitude(int index, double amplitude);
  public void addBalanceListener(BalanceListener listener);
  public void removeBalanceListener(BalanceListener listener);
}

Finally, we’ll be using a custom BalanceEvent and associated BalanceListener to consolidate events. A BalanceEvent extends the standard Java EventObject and encapsulates a source object (the BalanceModel), as well as the String label and BoundedRangeModel associated with each event. The BalanceListener interface is very straight forward and looks like this:

public interface BalanceListener
{
  public void balanceChanged(BalanceEvent event);
}

Listing 1 shows the DefaultBalanceModel class, which implements the BalanceModel interface. We manage three ListArray objects to keep track of labels, bounded range models and listeners. The getCount method returns the size of the models array. The addRange and removeRange add/remove a label and bounded range model and register/unregister the model as a ChangeListener so we can watch for changes in each bounded range model.

The getRange and getLabel methods merely cast the indexed object in the two respective array lists. The two indexOf methods pass on the indexOf behavior directly to the array list. The addBalanceListener and removeBalanceListener methods simply add or remove a BalanceListener to the listeners array list. The fireBalanceEvent method provides us with a simple mechanism to fire off a BalanceEvent by passing in a label and bounded range reference. We call it when we receive a ChangeEvent from one of the bounded range models.

The last pair of methods we need to implement are the getAmplitude and setAmplitude methods. The methods do a little bit of simple math to translate to and from relative value within a bounded range. These methods function based on the getMinimum, getMaximum and getValue attributes of the BoundedRangeModel. The setAmplitude method translates from a range between zero and one to the value that represents a relative position in the bounded range.

Figure 2 shows the various classes we need to make JBalance functional. We’ve covered the BalanceModel, DefaultBalanceModel, BalanceEvent and BalanceListener classes already. We’ll skip over the AbstractLayout and CircleLayout implementations though I’ll mention a few things about them.

AbstractLayout is the foundation of all the layout managers I implement. If you read the column on a regular basis you’ve seen it before. It merely deal with the most common layout manager requirements in a unified manner and lets you extend the class to implement specific layout manager behaviors.

Figure 2: JBalance classes.

Figure 2: JBalance classes.

CircleLayout arranges a set of components around a circle, centered on the middle of the parent component. It assumes the components are on the outside of the circle, as you’d find on a clock face. We paint components starting after 12 o’clock, ending in the 12 o’clock position, distributed evenly around the outside in a clockwise order. This class is fairly simple and you can find the code online at www.java-pro.com, along with the rest of the classes in this project.

Listing 2 shows the code for the BalanceView class. The constructor keeps a reference to the parent JBalance component, along with the number of divisions. We implement a number of utility methods to make life easier. The drawRuler method draws a tick-marked line, translated to the desired angle using the Graphics2D setTransform method. The createOutlineShape creates a GeneralPath polygon that goes around the outside of the component. You’ll find a drawBevel and supporting getPoint method which draw this shape with highlighted and shaded lines to simulate a three dimensional bevel around the outside.

The createAmplitudeShape methods, one of which does the real work while the other abstracts out the arguments for the current component, create a GeneralPath polygon based on the current amplitude values in the model. The paintComponent method draws all this together and uses a pair of foreground and background image buffers to reduce the need to redraw the background unnecessarily. By drawing the foreground layer on top of the background, we can keep calculations to a minimum and achieve the desired effect. We draw the background, then the amplitude shape, and finally the foreground image.

The BalanceEdit class extends BalanceView and adds editing functionality so that users can change the amplitudes interactively. Listing 3 shows the code for BalanceEdit, which implements both the MouseListener and MouseMotionListener interfaces. We draw a small selection rectangle along the closest axis as the mouse is moved around. When the user presses a mouse button, we move the cursor to the closes axis as a convenience. Notice that this takes advantage of the new Robot class, available only under Java 1.3. If you need to use this component under Java 1.2, you can remove this code without ill effect.

Finally, the code for JBalance is in Listing 4. It ties the rest of the code together. JBalance assumes you’ll pass in a suitable BalanceModel, which you can retrieve using the getModel method. We register as a BalanceListener and watch for change events from the BalanceModel. This lets us make sure that changes are reflected immediately since we call repaint whenever we see this event. This saves us a lot of overhead and eliminates tight coupling while still satisfying our need to update the display when the model changes. We delegate the addBalanceListener and removeBalanceListener methods so you don’t need to to have direct access to the model in order to register and unregister listeners.

JBalance is not a particularly complicated component, though it does make use of a few simple tricks with rotated 2D graphics and a little bit of trigonometry. The net effect is a visual component that allows you to communicate concisely with an end user, allowing them to see and manipulate a group of ranges in a balanced context. You can pack a large number of attributes into a small area and empower users to use new tools to communicate with your applications. Use it when the opportunity presents itself.