Monday, November 27, 2006

Waiting and Blocking Input

Those pesky users are always trying to submit forms more than once, or creating new files willy nilly while they're waiting for feedback.

This article addresses two strategies for avoiding Impatient User Syndrome. First, provide some feedback to show that the program is busy, and second, don't allow any additional input.

Now, Java provides a JProgressBar, which you can throw up in a Dialog, but those aren't always appropriate. Sure if you want to block all application input (or in the case of Java 1.6, all frame input), that'll work, and you can always disable one component at a time. But suppose, for instance, that you've got a single frame, with a couple of different sections or docked panels. If their operation is independent of one another, you certainly don't want to block them all with a dialog.

The following demo shows how to block input on individual components, groups of components, or an entire frame, as well as providing "busy" feedback. The class WaitIndicator installs a component in the JLayeredPane above the target component, which prevents any mouse input from reaching the target component, and displays an hourglass cursor over the target component. It also installs a key event handler to ensure that no key events reach the target component.

The WaitIndicator also provides a paint method, which you can use to provide whatever visual feedback is appropriate to your program. The default implementation fades to the target component's background color. The demo itself uses a SpinningDialWaitIndicator, which paints an Apple-like spinner (which you can also see in the animated icon demo applet a few entries back).





Source

Tree (and tree table) with checkboxes

When the user needs to select a non-trivial number of items from a hierarchy, normal tree item selection is too prone to losing the selection through mistaken clicks. The standard solution to provide a more robust selection method is to include check boxes next to each selectable item in the tree.

Hat tip to some previous online implementations (each has a different implementation):

Bare bones

With a TreeCellEditor

More recent variations


After a couple of iterations of checkbox trees (and tree tables), I rolled this up into a standalone TreeCellRenderer so that it can be applied more easily to any tree-oriented component. This implementation also properly handles rollover effects on the checkbox, which is lacking in the above implementations (most visible under w32; Java's Metal LAF has no checkbox rollover effects).

The checkbox painting is handled by using a real checkbox and painting its contents into an icon. The state of the checkbox is set just prior to painting to ensure the proper rollover state. The original cell renderer's icon is replaced with a composite icon of the original plus the checkbox (hit ^-shift-O to switch component orientation).

The renderer provides the current checked selection as an array of TreePath or an array of int representing the rows.

I also applied this to my own tree table implementation (similar to SwingX JXTreeTable) by just overriding a few protected methods which translate mouse coordinates into rows/paths.




Source

Tuesday, October 10, 2006

Iconic Inversion

Here's a problem and solution described in an applet.






The goal here is to make the icon look consistent with the selected text, which means using the JList's selection fg/bg colors where the icon uses the component's normal fg/bg colors.

This applet uses a SelectionIcon, which applies a custom composite to the original icon. The custom composite looks for the list fg/bg colors in the original icon (and some colors "nearby"), and swaps them out for the selection fg/bg. It also applies a slight tint based on the selection background.

The SelectionIcon can be applied to any existing icon. It's useful for selections because you don't know a priori what your selection colors are, so it's hard to make a dedicated selection version of your original icons that will look good in all cases. Assuming that the selection fg/bg are set reasonably, your icon will look at least as good as the selection text. The high contrast colors in the above example are from Win2k, but you never know what the user may have set.

Source

Friday, September 15, 2006

Animated Icon Redux

Lately we've been putting animated icons all over the UI to provide feedback about actions that are in progress. The spinning arrows are an animated GIF, while the spinning dial is dynamically generated with Java2D. Both icons are used just like any other icon (set and forget). No special coding is needed to get them to animate. This is how animated icons should behave.






The Icon interface's paintIcon method carries enough information for the icon to trigger its own repaint when it needs to display the next frame. Internally, the spinning arrows icon tracks the component on which it was painted as well as the location. Using this information, each time we want to display a new animation frame, we just walk the list of repaint locations and request a repaint on each of the stored items.

After testing this method out on a variety of components, I realized that this would be a superior method of animating icons in trees, tables, and lists (or any other component that makes use of CellRendererPane. With just a few tweaks, the logic was refactored to wrap animated GIF icons so that when they are used within a tree, table, or list, they are automatically refreshed as necessary. This is a much cleaner solution than the one I posted earlier.

I have a generic icon loader for my apps, so if the loader detects an animated GIF, it automatically wraps it in an AnimatedIcon and I don't have to worry about manually tracking animations.

Source

Wednesday, August 30, 2006

Swing Drag Images, Improved

Performance is improved, generating the drag image once instead of repeatedly.




Source

Saturday, August 12, 2006

Drop Target Navigation, or You Drag Your Bags, Let the Doorman Get the Door

When I'm dragging a 75lb bag and a five year old, it's rather nice to have someone open the door. When I'm dragging things around in my application, it's also nice not to have to open all the doors on the way to my destination before I start dragging.

Specifically, I've got a set of forms in an instance of JTabbedPane. I know one of the tabs holds a field that defines my preferred download directory, which I happen to have open in my file browser. Drag from the file browser onto the JTabbedPane, and -- oops, I'm not on the right pane. It'd be nice to have the tabs change as I drag across them so that I can find the right destination.

Since this is system-wide behavior, I'd like to enable the behavior once at application startup and forget about it, rather than having to add listeners or subclass every instance of JTabbedPane and JTree that's going to accept a drop.

DropTargetNavigator.enableDropTargetNavigation();

In order to make this happen, we install a global listener to the default DragSource, which lets us respond to any drags initiated within the same VM. Since that provides us with the current drag location, we can scan through the currently available components using Frame.getFrames() and SwingUtilities.getDeepestComponentAt() to see if what's under the cursor needs to be navigated. If we do make a modification, a Runnable which will undo the modification is added to a queue so that we can restore the component to its original state if the drop ends up going somewhere else or being canceled.

To handle drags originating from outside the VM requires a little hacking, since there isn't any sort of global drop target listener. During a native drag, you won't see any MouseEvents. As of 1.4+, there is a MouseEvent (actually a SunDropTargetEvent) that gets processed by Component.dispatchEventImpl(), but it gets swallowed before the component passes events off to AWTEventListeners. You can't override dispatchEvent, and wouldn't want to anyway because you'd have to do it on every component. Instead, we can install a custom EventQueue which can watch for the drop target events and forward them to our drop target listener.

The navigator class has built-in support for JTabbedPanes and JTrees. Custom classes that wish to support navigation can register themselves with a simple interface that performs custom navigation and rollback. This way you can enable auto navigation on whatever tree table implementation you happen to be using.

In order to avoid unintended navigation when the mouse is dragged rapidly across a component, I've added a configurable delay to when the navigation starts. You need to hover over the same spot for roughly half a second to activate the navigation.


Tuesday, August 08, 2006

Mighty Mitochondria! Them Cells Are Animated!

Java provides effortless loading and display of animated GIFs. Just tell ImageIcon where it can find the file, then plug the ImageIcon into a JLabel or JButton. Couldn't be simpler.




Now how about putting that animated icon into a JTree as feedback for loading status.





Oops. What happened? Did we get the right icon? Why isn't it animated? Try selecting one of the tree nodes, then rapidly move the selection up and down. Watch what happens.

What happened is that no one told the JTree it needed to repaint itself after it painted the initial image. Selection changes trigger repaint messages, which is why the icons move a little bit when you move the selection around. Any animation is driven by regular repaints of the component where it is drawn. JLabels and JButtons are no exception. Take a look at the source for ImageIcon.paintIcon:

public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
if(imageObserver == null) {
g.drawImage(image, x, y, c);
} else {
g.drawImage(image, x, y, imageObserver);
}
}

When you create your ImageIcon, it doesn't have an image observer, so the component where it's being drawn is used instead (note that java.awt.Component implements ImageObserver):

public boolean imageUpdate(Image img, int infoflags,
int x, int y, int w, int h) {
int rate = -1;
if ((infoflags & (FRAMEBITS|ALLBITS)) != 0) {
rate = 0;
}
// ...
if (rate >= 0) {
repaint(rate, 0, 0, width, height);
}
return (infoflags & (ALLBITS|ABORT)) == 0;
}

So whenever the image reports that it has another frame ready (infoflags&FRAMEBITS != 0), it calls this method.

That's just fine for a label or button which only displays a single icon, but what about a tree, which might display this icon in every row? We need an image observer which can call repaint with the cell bounds of every cell that contains the animated icon. Using the tree itself as the observer could work, but we'd end up redrawing the entire tree on every frame. It's preferable to use a decorator which keeps track of which individual cells need updating.



In this case, let's use the tree cell renderer as the entry point or trigger to start or stop animation, since that's normally where a tree's icons get customized.


JTree tree = ...;
TreeCellRenderer r = new DefaultTreeCellRenderer() {
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded,
boolean leaf, int row, boolean focused) {
Component c =
super.getTreeCellRendererComponent(tree, value, selected, expanded,
leaf, row, focused);
TreePath path = tree.getPathForRow(row);
if (isLoading(path.getLastPathComponent()))
setIcon(LOADING_ICON);
// The following line is what you'd normally use; the rest is just illustrative
//CellAnimator.animate(tree, c, path);
// Let the CellAnimator utility figure out if there's an animated icon here
ImageIcon icon = (ImageIcon)getIcon();
if (CellAnimator.isAnimated(icon)) {
CellAnimator.animate(tree, new CellAnimator.TreeUpdater(tree, row), icon);
}
else {
CellAnimator.stop(tree, row);
}
return c;
}
};
tree.setCellRenderer(r);


The CellAnimator needs to do several things. At any point if it is determined that no animation is required, the animation support is disposed for the current location.

  1. Obtain the icon in use, if any
  2. If the icon is animated, install an ImageObserver
  3. Identify the location to receive repaint notifications, and add it to the observer's list


Whenever the animated icon posts an update, the ImageObserver for that icon will walk its list of repaint locations and trigger repaints. The actual implementation takes care of a number of other details, like using weak references for UI components and remove locations if the location or component is no longer valid.

The CellAnimator provides one-line methods to enable animation on standard Swing components and is easily extensible to enable animation on custom components that use the cell renderer technique.

Here is a demo of the cell animator applied to a lazy-loading tree.


The source includes JUnit-based unit tests which use the Abbot library.

Friday, August 04, 2006

UI Testing on the Sly

One of the drawbacks of testing your UI components (you are testing them, right?) is that to properly run a test, it needs to run on a display. Unfortunately, if you want to use that display for something else (like writing code and fixing bugs), you run into problems with either the tests writing your code, or the code you're writing supplying input to your UI tests.

One good solution is to have a dedicated machine for the tests, which you can continually monitor and can easily be updated with changes from your dev machine. Since that isn't always an option, I'll present a few alternatives for several different platforms which allow you to run UI tests without having a separate machine to run them on.

OS X


VNC is a great tool for controlling remote computers of any platform. If you have fast user switching enabled, you can actually use OSXvnc to display the desktop for a user other than the one currently using the display. If you configure OSXvnc to not quit when switching to another user, you will always be able to connect to the desktop where OSXvnc was first launched.

Set up a "test" user, and set up OSXvnc to launch when that user logs in. Set up the "Startup" tab like this:

Once OSXvnc is running, you can access the "test" desktop while using your normal desktop. Run your tests in the background, peek in on them to check the results.

Linux


Run vncserver, either manually or as a daemon. This will give you an additional X display which is totally separate from your physical one. Connect to the new display with vncviewer, and you have a window onto your "test machine".


Windows


Windows isn't quite as functional when it comes to sharing a display, but you can still get some mileage out of UI testing on the same machine. There's a peculiar little mode in windows, I'll call it "service mode", where you think you have a display but you don't. This prevents java.awt.Robot from working properly, but you can still get some mileage from UI tests that don't depend on screen captures or native drag and drop. Abbot (a UI testing library) will work just fine in this mode, and does so by artificially generating events directly into the AWT event queue in order to drive a UI.

The easiest way to run tests in this manner is to install cygwin sshd. It runs as a service, so by default it doesn't have access to the desktop (there is a checkbox in the services admin control panel that indicates whether a service is allowed desktop access). Once that is installed and configured (google "cygwin sshd install"), you can use ssh to get a shell prompt within the service mode "sandbox". If you've got an ant script to run your tests, just invoke it. Abbot automatically detects whether this mode is in effect, so if you're using that library, everything works out of the box. If you only run your tests interactively through an IDE, you're out of luck, since you won't be able to interact with the IDE if you launch it in service mode.

Monday, July 17, 2006

Drag Images for Everyone...and we do mean Everyone

You may have seen Sun's tutorial on using Swing's built-in drag and drop support. One of the demos from the tutorial looks like this:



Today's demo puts a drag image on each of the displayed drag-enabled components, and does so without modifying the original jar file. It also does so without any special knowledge of the components that are drag enabled. (Web start doesn't let you reference a file on another host, so you'll have to trust me when I say the original jar is used unmodified).



A few sights to take in after you've checked that "Turn on Drag and Drop" checkbox.


  • Try a drag from each component.
  • Try dragging a multiple, discontiguous selection from each component.
  • Watch what happens when your drop is rejected.
  • Move the main window, and drag some more. Notice the nice images across both windows (the original main has simply been called twice).


Source Here.

The technique depends on all drag sources to use the default DragSource as the drag initiator. To date, all drags implemented within the JRE use the default, as do all the drag & drop examples I've seen on the web, so it always works in practice.

The global drag source listener technique used here could also be used to automatically switch the visible tab in a JTabbedPane, expand a node in a JTree, or even autoscroll components that don't implement Autoscroll. Watch for an upcoming article to address those options.

Tuesday, July 11, 2006

Bugs in the Library

One of the great benefits of Java is the availability of a wide variety of libraries providing all sorts of functionality. However, managing bugs, workarounds, and fixes becomes more complicated when tracking multiple third-party libraries.

With a simple source code base or a single application, it's relatively simple to take library updates to get bug fixes, or even to apply patches to a local copy of a library. There are many situations where this isn't possible and you need to determine for a given section of client code whether a bug is present. One codebase may need to support several VM releases and what's a workaround in one may introduce a bug in another.

I keep track of library-based bugs with a class Bugs which comprises static methods which check the environment for the presence of a given bug. For instance, Mac OSX between Java releases 1.4 and 1.5 incorrectly mapped mouse button events generated by java.awt.Robot. So Bugs would have a corresponding method,


// This bug has been submitted to Apple, bug #XXXXXXX
// It was officially fixed in version 1.5
public static boolean hasRobotButtonSwapBug() {
return Platform.isMac() && Platform.JAVA_VERSION >= 0x1400
&& Platform.JAVA_VERSION < 0x1500;
}


The method contains checks which describe as precisely as possible the environment where the bug is present. In this case, several java releases were tested; the bug first appeared in the Java 1.4 release and persisted until 1.5. The method should narrowly identify only those environments shown to produce the bug (see below).

In the corresponding BugsTest, we implement a method which actually checks for the bug. This serves several purposes. First, it provides a test case to submit along with a bug report to whoever is responsible for the bug. Second, it provides a standard check when used with other automated tests to flag changes in released libraries. Checking the "bug presence" method at the start of the test avoids test failures where we already know the bug exists, and tests for its presence in any environment we haven't yet tested. A system property may be set to force running the test.


public void testRobotButtonSwapBug() throws Exception {
// skip the test if we already know the bug exists, unless forcing a bug check
if (!Boolean.getBoolean("check_all_bugs") || hasRobotButtonSwapBug()) return;
// perform tests to reproduce the bug
final List events = new ArrayList();
Robot robot = new java.awt.Robot();
Window w = new Window();
w.add(new JLabel("Swap test"));
w.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
events.add(e);
}
});
showWindow(w);
Point where = w.getLocationOnScreen();
where.x += w.getWidth()/2;
where.y += h.getWidth()/2;
robot.mouseMove(where.x, where.y);
robot.mousePress(InputEvent.BUTTON2_MASK);
robot.mouseRelease(InputEvent.BUTTON2_MASK);
robot.waitForIdle();
assertEquals("Robot generated incorrect button event for button 2",
InputEvent.BUTTON2_MASK,
((MouseEvent)events.get(0)).getModifiers());
// and so on...
}


With each new library release, the test is run. If it fails with a new version or a new platform, extra checks can be added to the method in Bugs.

The Bugs method test can now be used in client code to determine whether workarounds need to be applied. Such checking allows the client code to be used consistently regardless of the environment in use on the project where it is used. If you release an application that runs on Java 1.2 and above, you may need to selectively apply workarounds depending on the actual JRE in use by a user. In the example above, a workaround to swap button definitions was applied (and still is) if the user happens to be using the affected Java release on OSX.

This pattern makes bug checks and workarounds very explicit. Compare these two implementations. First, the ad hoc workaround:

JTree tree;
...
// tree row size isn't updated automatically, so hack it
tree.setRowSize(tree.getRowSize());
tree.revalidate();
tree.repaint();


Or this, which is only slightly more structured, but entirely clear in intent. You can also tell exactly what code can go away when the bug does:

JTree tree;
...
if (Bugs.hasTreeRowSizeUpdateBug()) {
tree.setRowSize(tree.getRowSize());
tree.revalidate();
tree.repaint();
}


The code explicitly identifies a bug that has been at least partially analyzed, and indicates workaround code. If a programmer needs further information on the bug, she can just look up the corresponding information in Bugs or BugsTest

Wednesday, June 28, 2006

Fancy Drops

Just run the demo.



Again, a very simple method override to enable drop target functionality. Paint a marquee on the full list if it's empty, otherwise mark a space at the end of the list. Most of the code is just futzing with the data model on a drop or moving/sizing the decoration.

JList list = ...;
DataFlavor[] acceptableFlavors = { DataFlavor.stringFlavor };
new DropHandler(list, DnDConstants.ACTION_COPY, acceptableFlavors) {
private Marquee marquee;
/** Always drop at the end of the list. */
protected void drop(DropTargetDropEvent e, int action) throws UnsupportedFlavorException, IOException {
final List data = new ArrayList();
for (int i=0;i < list.getModel().getSize();i++) {
data.add(list.getModel().getElementAt(i));
}
data.add(e.getTransferable().getTransferData(DataFlavor.stringFlavor));
list.setModel(new AbstractListModel() {
public int getSize() {
return data.size();
}
public Object getElementAt(int index) {
return data.get(index);
}
});
}
protected void paintDropTarget(DropTargetEvent e, int action, Point location) {
if (action != DnDConstants.ACTION_NONE && location != null) {
if (marquee == null) {
marquee = new Marquee(list);
}
int count = list.getModel().getSize();
if (count == 0) {
Dimension size = list.getSize();
marquee.setDecorationBounds(new Rectangle(0, 0, size.width, size.height));
}
else {
Rectangle r = list.getCellBounds(count-1, count-1);
r.y += r.height;
marquee.setDecorationBounds(r);
}
}
else if (marquee != null) {
marquee.dispose();
marquee = null;
}
}
};
Compare that API with the raw D&D APIs (which are still available to override in DropHandler, by the way):

// DropHandler
void drop(DropTargetDropEvent e, int action);
// DropTargetListener
void dragEnter(DropTargetDragEvent);
void dragOver(DropTargetDragEvent);
void dropActionChanged(DropTargetDragEvent);
void dropExit(DropTargetEvent);
void drop(DropTargetDropEvent);
Most of the complexity comes in figuring out the appropriate sequence of calling back methods on the DropTargetEvents, which communicates whether a drag is acceptable and if so, which action is actually to be accepted. This could be clarified a great deal in the Javadoc APIs, or by a thorough example, but ultimately it's boilerplate code that rarely, if ever, needs changes to its behavior.

Back to the example. The JTree drop handler code is more complex, but only to determine what should happen when dropping on any given location. Non-leaf nodes accept drops, but leaf nodes refuse drops, and the spaces between leaf nodes accept drops.

Some DropHandler features:
  • Automatically get the copy action when it's the only one allowed
  • Automatically disable user actions which are not allowed
  • Some parts of the drop target allow drops, some don't
  • Paint any drop target indication you please, regardless of the target component (no subclassing of components)
Usually, in order to get non-trivial drop target painting, you have to override one of the target component's paint methods and invoke paintImmediately within the drop handler. That's avoided by use of a decorator, which uses the existing component hierarchy and layout to paint over the target component. The Marquee class uses a dashed stroke to draw a rectangle. A timer regularly increases the phase and triggers a repaint, which gives the "marching ants" effect.

Unfortunately, you still have to make the component implement Autoscrolls, although you could probably call the methods yourself from within the drop handler. It'd be nice if the D&D code which calls into the Autoscrolls interface instead looked for a client property. That functionality could probably be implemented in the DropHandler.

Next step: hook this up to the list and tree animators.

Tuesday, June 27, 2006

Dead Simple Drags

Here is a demo of usage of the Drag & Drop Library, which provides D&D support on top of Java's basic D&D in much the way Xt provided toolkit functionality on top of X11.





Here is the code to add drag handling to a JLabel:
final JLabel label = new JLabel("Drag Me");
label.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
new DragHandler(label, DnDConstants.ACTION_COPY) {
protected Transferable getTransferable(DragGestureEvent e) {
return new StringSelection(label.getText());
}
protected Icon getDragIcon(DragGestureEvent e, Point offset) {
return new ComponentIcon(label, true);
}
};


The only thing absolutely required by the API is getTransferable. However, overloading getDragIcon will automatically give you a nice ghosted drag image, regardless of whether your platform supports one natively (the ComponentIcon used to render the JLabel is just a simple class which renders its given component as an icon).

Drag and Drop has been in Java since version 1.2 when a functional but very complex set of interfaces was introduced. Compare the previous DragHandler interface for dragging with the following interfaces, which must be implemented carefully to get consistent results (note also the conspicuous absence of any methods which explicitly indicate the data to be dragged):

// DragGestureListener
void dragGestureRecognized(DragGestureEvent);
// DragSourceListener
void dragEnter(DragSourceDragEvent);
void dragOver(DragSourceDragEvent);
void dropActionChanged(DragSourceDragEvent);
void dragExit(DragSourceEvent);
void dragDropEnd(DragSourceDropEvent);
//DragSourceMotionListener
void dragMouseMoved(DragSourceDragEvent);
Not a trivial thing to get right, especially without an in-depth understanding of how the system works. Not something I'd expect of every developer. Unfortunately, there is no abstract implementation to provide a base level of functionality or useful example (I'm sure everyone has at least looked at Rockhopper or JavaWorld for functional examples). These examples provide working implementations, but not one that makes extension or reuse clear and simple. Sun has tried to make D&D simpler by introducing the TransferHandler and setting up default drag and drop handlers on Swing components. I consider the TransferHandler a mistaken attempt to merge two operations that just happened to have a little functionality in common. Copy and drag both need to produce a Transferable, and Paste and drop both need to absorb a Transferable, but the edit actions operate in a sufficiently different manner that merging the two doesn't really buy you much in practice.

Copy/paste actions are fairly trivial to construct in the first place, and the clipboard transfer is a few lines of boilerplate.

Drag and drop, on the other hand, is a lot more lines of boilerplate, some of which can introduce subtle differences in behavior if you don't get it just right.

Back to the library. An example on a JTree is a bit more involved, but only because you now need to choose which part of the JTree is going to be dragged. For illustrative purposes, I've disabled dragging of non-leaf nodes, and enabled dragging of the entire tree if you drag outside of any rows. I futz with the ComponentIcon to draw either the whole tree or just one row, as needed. You could conceivably construct a drag from a multiple selection by painting the whole tree but adding an appropriate clipping mask when painting the icon.

final JTree tree = new JTree();
// Turn off selection of rows by dragging
tree.setDragEnabled(true);
// Turn off built-in swing drag handling
tree.setTransferHandler(null);
new DragHandler(tree, DnDConstants.ACTION_COPY) {
protected boolean canDrag(DragGestureEvent e) {
Point where = e.getDragOrigin();
int row = tree.getRowForLocation(where.x, where.y);
if (row != -1) {
TreePath path = tree.getPathForRow(row);
return tree.getModel().isLeaf(path.getLastPathComponent());
}
return true;
}
protected Transferable getTransferable(DragGestureEvent e) {
Point where = e.getDragOrigin();
final int row = tree.getRowForLocation(where.x, where.y);
if (row == -1) {
return new StringSelection("full tree");
}
Object value = tree.getPathForRow(row).getLastPathComponent();
return new StringSelection(String.valueOf(value));
}
protected Icon getDragIcon(DragGestureEvent e, Point offset) {
Point where = e.getDragOrigin();
final int row = tree.getRowForLocation(where.x, where.y);
if (row != -1) {
Rectangle r = tree.getRowBounds(row);
offset.setLocation(r.x, r.y);
}
return new ComponentIcon(tree, true) {
public void paintIcon(Component c, Graphics g, int x, int y) {
g = g.create();
if (row != -1) {
Rectangle r = tree.getRowBounds(row);
g.translate(-r.x, -r.y);
g.setClip(new Rectangle(x+r.x, y+r.y, r.width, r.height));
super.paintIcon(c, g, x, y);
}
else {
super.paintIcon(c, g, x, y);
}
g.dispose();
}
};
}
};

Again, this implementation doesn't have to worry about any of the mechanics of drag and drop. The implementation decides whether an item can be dragged, what the dragged item looks like, and what is the appropriate Transferable. This API hides all the boilerplate, but you can still get access to the basic Java D&D API if you need to, if for some reason you need to augment or override dragOver or dragGestureRecognized.

Next installment will demonstrate the DropHandler, which facilitates decorating your drop target as well as accepting or rejecting incoming data.

Source.

Monday, June 26, 2006

Decorator Update

This fixes some display glitches that showed up on X11-based systems. The decorators are also properly clipped (or not) if they extend beyond the target component bounds. This showed up with the marquee, which would get clipped by a scroll pane that was not an ancestor.




Source

Thursday, June 15, 2006

Navigating Large Spaces

This example demonstrates one method of providing a navigational element for traversing a large, scrolled component. Like Apple's Expose, it's entirely out of the way until you need it.




This implementation combines several effects. First, a ComponentIcon class renders any given target component as an icon. Second, a ScaledIcon is used to resize the ComponentIcon to fit within the desired space. The Panner composes the two icons and paints a border, indicates the visible sub-rectangle, and responds to navigational input (mouse events). Finally the PannerHandler handles showing, hiding, and resizing the panner in response to user input.

ComponentIcon
This is pretty simple. The main thing to watch out for is that you need to turn off double buffering on the target component, or the rendering ends up having odd side effects on the rest of the display. The underlying component's "isShowing" method must return true, otherwise Swing will short-circuit its painting (there are other methods for getting a component to paint which is not currently in the visible hierarchy).

ScaledIcon
This class takes any other icon, scales and translates the Graphics object so that the delegate icon fits within its allotted size, then delegates to the other Icon to paint. Aspect ratio is preserved by default, but that can be turned off if you want to stretch to fit the available space.

Panner
This is a lightweight component with optional transparency (so that you can optionally position it over the panned component and have the underlying component show through). It passes through the "preserveAspectRatio" property to the ScaledIcon.

PannerHandler
This class takes care of the details of deciding where and when to show the panner. It also sets the size appropriately (the example uses a percent value against the size of the panned component, and a fixed offset from the UL corner). Essentially this is the class responsible for configuring all the other classes and hooking them together. It installs a ComponentListener to update the panner size and position whenever the panned component is resized or moves (i.e. is scrolled).

It's possible to attach the panner itself to the panned component (so you get a thumbnail in a corner of the pane) or have it appear in its own window. The panner size can span the entire visible viewport or some fraction thereof. You can also indicate which corner of the visible viewport to use as anchor if the panner is smaller than the visible viewport.

Friday, May 26, 2006

Context-sensitive Edit Menu

Most applications have an "Edit" menu where our friends "Cut", "Copy", and "Paste" hange out. In any non-trivial application, there are certainly more than one thing to which those operations might apply. How can we hook up these friendly edit operations without having to know beforehand everything that might be Cut or every component that might accept a Paste?


Permission is required in order to access the system clipboard.

Let's take a simple example, which displays two JLists. The JList supports a copy action (via its LAF TransferHandler, if you're curious). You can retrieve the action from its ActionMap. The ActionMap is supported by all JComponents, and is basically a pool of all Actions that the component supports. How best to make Edit->Copy (or any other menu item, for that matter) invoke the corresponding action from the appropriate JList? Note that handling a keyboard shortcut is already taken care of by the JList's InputMap, which maps keystrokes to actions in the ActionMap.

First, let's define context to be what component has focus. This may not always be true, but given that keyboard input goes where the focus is, it's reasonable to assume that focus indicates the proper current context.

If all we wanted to do was support mapping a keyboard shortcut to an action, we'd have nothing to do, but we'd like to actually make Edit->Copy do the same thing as that keyboard shortcut. A quick and dirty hack might be to have the menu item generate a fake keystroke sent to the currently focused component, but that only works if the target action has a keystroke associated with it. But that's kind of indirect and doesn't smell very good.

What if the Edit->Copy menu item reflected a sort of delegating action, whose actionPerformed logic simply called some other action. Whenever the context (i.e. focus) changes, we check the focused component's ActionMap for a copy action. Whatever we find, we set as the delegate of the delegating action. If there is no copy action, then the delegating action sets itself disabled.

public class DelegatingAction {
public DelegatingAction(String name) { super(name); }
public void setDelegate(Action a) {
delegate = a;
setEnabled(delegate != null);
}
public void actionPerformed(ActionEvent e) {
if (delegate != null) { delegate.actionPerformed(e); }
}
}


To actually look up the delegates, we can store one or more keys which can be used to identify the actions in the target components. For custom-defined actions, you can set your action keys to whatever is appropriate. In order to handle Swing built-in edit actions, we have to account for a few variations. TransferHandler uses hard-coded strings "cut", "copy" and "paste", while text components use a few string constants defined in DefaultEditorKit, which are different from the TransferHandler strings (e.g. "copy-to-clipboard"). Since these variants exist, we need to check for all of them, since they all correspond to the same Edit menu items.

To know when to update the delegate's mapping, we can install a property change listener to the keyboard focus manager (available in 1.4+ JREs). We only want to look at permanent focus changes, so listen to "permanentFocusOwner".

There's an unexpected glitch caused by the fact that the TransferHandler edit actions are singletons shared across all transfer handlers. As such, they expect to deduce the target of the edit operatoin from the ActionEvent source. In order to work around this, the delegating action must also keep track of the current component context so that it can reformat the ActionEvent to have the proper source.

The singleton implementation also means that the actions' enabled state doesn't reflect whether the target component can actually process the action. Copy should be disabled if nothing is selected, and paste disabled if the component is not editable. Leave that to a future article...

In practice I use a system that's a little more general (it can auto-populate dynamic menus and submenus, and listens for changes on a per-menubar basis instead of per-action, which makes GC a little easier).

Source is here.

Thursday, May 25, 2006

Animated Tree Update

I've fixed a few bugs and enabled horizontal drags to change the depth in the hierarchy where the drop target is ambiguous. Children now slide out from under the parent on expansion, rather than over the parent.



Other things that might be nice to do:

  • Handle drags from outside the component
  • Auto-expand collapsed folders
  • Animate canceled drops
  • Smooth horizontal transitions

Tuesday, May 23, 2006

My Drag Image is Better than Yours


While tinkering with getting suitable ghosted drag images to show up on w32 and linux, I thought I'd whip out the old component decorator yet again, not just to display the drag image, but to display it over any java window that might be receiving a drop. The effect isn't required on OSX (since it already knows how to display a drag image anywhere on the display), but in all cases the same drag handler method is used for generating the actual drag image.



I got started in this drag/drop business because I needed to display different animations and/or decorations under the cursor on a tree table. You know, highlight the folder if you can drop in the folder, show an insertion line to insert between two things. The AbstractComponentDecorator was born to handle the highlighting/decoration without having to subclass or otherwise modify the underlying drop target.

The built-in swing drag and drop makes some things easier, but also hides some of the power of raw DnD. Its drop target highlighting (temporarily changing the selection) is just a programming shortcut, not a real design for marking a drop target area. the TransferHandler system used for Swing DnD solves a particular problem in a specific way, but for practical purposes, is not extensible. It does a nice job of consolidating some drag/drop and clipboard support, but it is really hard to change its behavior.

Fundamentally, you don't really need to specify much to define a drag and drop operation:

  • Indicate the types of actions supported (based on a drag gesture and drag location)
  • Identify a Transferable (data to be dragged) based on a drag gesture and drag location
  • Provide a drag image/image offset for feedback while dragging
  • Indicate the types of actions and data (DataFlavor) supported for drop, possibly refined by actual drop target location
  • Provide drop target feedback based on current drop target location and effective action


The base implementation should handle the following, allowing modification of the default behavior if necessary:

  • Detect drag gesture and user-requested action, if any
  • Provide a drag image under the cursor for feedback
  • Determine the effective action based on the user, source, and target
  • Set/update cursor to standard platform cursors based on the effective action
  • Invoke methods to update the state as the effective action changes, handle drop, etc.


Basic DnD leaves you to perform all of these tasks, while Swing DnD performs most of them but doesn't allow you to override the behavior. I wrote a pair of classes to hide the complexity but leave the base DnD functionality available if needed. The simplicity is that based on a drag location, you just provide a Transferable and optionally an Icon to represent the item being dragged (which can be the whole component or some small part of it). The supported drag actions are indicated in the constructor. The primary methods to be defined on the DragHandler follow:

protected boolean canDrag(DragGestureEvent e);

/** Override to provide an appropriate {@link Transferable}.
*/
protected abstract Transferable getTransferable(DragGestureEvent e);

/** Override this to provide a custom image.
* @param offset set this to be the offset from the drag source origin
* to the image origin. If unchanged, the image is assumed to have the
* same origin as the drag source.
*/
protected Icon getDragIcon(DragGestureEvent e, Point offset);



For the DropHandler, just a few methods are required. Painting the drop target is optional and does nothing if not overridden, but that allows you to paint insertion lines, tint the target, or anything else that might be appropriate (decorators are useful for this purpose):

protected abstract boolean isSupported(List flavors);
/** Update the appearance of the target component. Normally the decoration
* should be painted if the event is an instance of
* {@link DropTargetDragEvent} and cleared otherwise.
*/
protected void paintDropTarget(DropTargetEvent e, int action);
/** Indicate whether the drop is acceptable. */
protected boolean canDrop(DropTargetDropEvent e, int action);
/** Handle an incoming drop. */
protected void drop(DropTargetDropEvent e, int action);


I also needed more control and feedback over user modifiers. Basic DnD simply performs a logical AND of the source, user, and drop target actions. In our app, we support moves, copies, and links. If the user is requesting a link by holding a modifier, you most definitely don't want to move or copy just because link is unsupported but move or copy is (which is the default drag/drop behavior, BTW). In addition, if the user doesn't use any modifiers, the application should be free to select the most appropriate drop operation based on the context. The cursors are automatically set to whichever appropriate for the platform, although you can still easily change that behavior if necessary.

Finally, drag image ghosting has been done to death (see Java Tip 114), but I wanted to have the drag image appear not just in the drag source, but every possible (java) drop target. I managed to get pretty close, probably as close as you can without resorting to native code (see the demo).

Source here. The main class was derived from the JUnit tests for these classes, so it looks a bit crufty.

Sunday, May 07, 2006

Smooth Tree Drop Animation


Here's an updated tree drop animation demo. The base class can be applied to any tree, and if you're using a DefaultTreeModel with MutableTreeNodes, you don't even have to subclass. Web Start demo:

This starts to get complicated, what with having multiple possible targets and behavior for a dragged node, so I started a JUnit test case around the main class. Note that none of the tests actually requires displaying the UI. I usually keep around a testReveal() method though, since often it's nice to do exploratory testing, or simply to get a feel for the component's response.

The horizontal drop location now reflects the appropriate indentation level based on the target parent node. Still needs to track horizontal drag motion, since currently if you're at the end of a few nested nodes, you can't insert at the end of the parent node of your choice.

I started to implement spring-loaded non-leaf nodes (so when you drag over a non-leaf it auto-expands), but the actual implementation has some details to consider, such as whether to collapse auto-expanded nodes and if so, non-disruptively. If you collapse anything above where the user is dragging, the whole tree shifts upward, effectively dragging the tree under the cursor.

A nice side effect of handling expansions is that now when you expand a tree node, the children sort of slide out of the parent in a pleasing manner.

Source for smooth tree drop animation and tests.

Friday, May 05, 2006

Of Swing Frameworks and threaded Actions

I've been trying to come up with an implementation that properly encapsulates the most common cases of triggering, tracking, and responding to the results of a long-running operation from a Swing UI. If the JSR for a Swing app framework produces nothing else, it should address this issue, given its ubiquitousness with AWT's single-thread model.

Basically, you have this:
  • User trigger (button, menu, drop)
  • Collect information from the UI
  • Switch to another thread
  • Perform processing
  • Update the UI (success or failure)
Ideally, I'd like to just write straight code:

public void actionPerformed(ActionEvent e) {
Object[] o = list.getSelectedItems();
try {
Object result = processObjects(o);
list.setSelectedItem(result);
}
catch(CanceledException e) {
// ignore the results
}
catch(Exception e) {
list.setSelectedItem(null);
status.setText("Error: " + e.getMessage());
}
}



Which makes it very clear what's going on. However, as anyone who has used Swing for more than five minutes is aware, this makes your UI freeze for the duration of the operation.

I'd prefer an idiom that looks as close as possible to the original code, perhaps an "executor" block:

public void actionPerformed(ActionEvent e) {
Object[] o = list.getSelectedItems();
try {
Object result = null;
Handle handle = execute-off-edt {
result = processObjects(o);
}
list.setSelectedItem(result);
}
catch(CanceledException e) {
// ignore the results
}
catch(Exception e) {
list.setSelectedItem(null);
status.setText("Error: " + e.getMessage());
}
}

This idiom would let me transparently collect inputs and pass them to a threaded invocation without a lot of messy assignments (which do absolutely nothing to communicate what the code is trying to do). The handle would give me some sort of control over the spawned thread. And when the thing is done or fails, I don't have to do messy assignments to get its results. It's the sort of thing that Java's anonymous inner classes overcome; it's syntactic sugar, but it sure as hell simplifies how the code looks.

Spinplays some neat tricks with interfaces and proxies to let you use something close to this idiom (as do FoxTrot and SwingWorker, albeit less elegantly), but it has some unfortunate restrictions . For instance, if you start a Spin invocation from a mouse press event handler, the mouse release event is likely to be processed during your long-running method, and the rest of the mouse press handling will happen after the mouse release handling.

The reason the idiom above can'tbe used as is, is that in order for the event dispatch processing to continue, the current method needs to return immediately. This is the Spin issue. If you don't return, then you may have event handlers that haven't run yet, and end up running out of turn. I doubt that part of AWT is going to change, so I have to look at variations of the idiom instead.

So I've been playing around with a code pattern that handles the before/during/after pattern, including thread-hopping, cancelation and failure. It's probably not universal, since there are some situations it fits much better than others. I've put it in the form of a javax.swing.Action, since that's a pretty common method of triggering actions in Swing.

abstract class AsynchronousAction {
public void actionPerformed(final ActionEvent e) {
preInvoke(e);
// could use Executor here; implement the before/during/after pattern
// so you don't have to reimplement it on every action
new Thread() {
public void run() {
Throwable failure = null;
try {
invoke(e);
}
catch(Throwable e) {
failure = e;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
postInvoke(failure);
}
});
}
}.start();
}
/** Change/update UI state here, collect input */
protected abstract void preInvoke(ActionEvent e);
/** Invoked from a non-UI thread. */
protected abstract void invoke(ActionEvent e);
/** Invoked when everything happens normally. */
protected abstract void postInvoke();
protected void handleFailure(Throwable failure) { }
protected abstract void postInvoke(Throwable failure) {
if (failure == null) postInvoke();
else {
handleFailure(failure);
}
}
}


I've left out the "cancel" functionality (and automatic disabling while the action is active) for clarity (using thread local variables, the spawned thread can check whether it should bother continuing or reporting its results).

So this works pretty well for, say, a "navigate" method which loads some data from a remote source and displays it. I can cancel the current one and invoke it with a different target. Any canceled action never reaches the postInvoke() method, so I don't have to explicitly add and remove listeners to yet another listener interface. It gets a bit clunky when there's a lot of data to pass to the invoke() method. Where you'd like to just pass parameters, you have to store in member data, which introduces synchronization problems when dealing with multiple invocations.

Monday, May 01, 2006

And now on a tree...


Here's a quick edit of the smooth list drop applied to a JTree. It doesn't actually handle the drop - it still needs to implement changes to the tree model.

It looks best with a LAF that displays vertical lines, since the lines get stretched as space is made for the insert (OSX doesn't draw connector lines :().

Web Start.

(There was a race condition in the animator in the first posting which would sometimes lock up; thanks to ploiku for the stack trace).

A full implementation for the tree would need to have a few abstract methods defined:

boolean canMove(TreePath src);
boolean canMove(TreePath src, TreePath newParent, int index);
void move(TreePath src, TreePath newParent, int index);


You'd probably want to collapse a non-leaf when dragging it, allow drops onto collapsed nodes, auto-expand collapsed nodes after a delay, and probably a few other things I haven't thought of. Too much for one afternoon, drat.

Sunday, April 30, 2006

Smooth JList Drop Target Animation


Web Start and source.

I ran across this nice effect for indicating a drop target in a JList. The ghosted image of an item dragged from the list appears to push aside other entries as it moves through the list of items. A very simple method results in very smooth animation that can span several cells at once. The basic idea is to maintain a list of current positions for all cells, and gradually adjust those positions to match what their final positions would be with a proposed insertion. The animation consists of moving half the distance from the current to the final position (rounding up the final pixel difference). The problem with the sample implementation is that it requires tweaks to both the list model and its LAF UI.

I thought it would be nice to be able to apply this effect to any list without having to replace the LAF UI. Since lists render themselves cell by cell anyway, it shouldn't be that hard to just trick the list into painting each cell where we want it, instead of in its default location.

I focused on just handling an internal drag, but the API on the smoother could easily be tweaked to accept native drags from external sources.

First, to separate the functionality from user triggers, I decided to put the mouse event handling into the demo code. I've found that in UI testing, it's almost always beneficial to be able to drive bits of the UI from direct programmatic methods (would that Swing followed this pattern, so it would be easier to test). The mouse listener decides when to start, update, and end the drag, and calls in to the smoother's corresponding methods.

The smoother itself is a decorator which performs the painting of individual cells by having the original list paint itself fully and then clipping out the unwanted stuff. This could be optimized by just asking for the rendering components, but that potentially omits additional decoration provided by the LAF. So I locate the cell I want within the full list as painted by the LAF, and copy that image to its "floating" location.

    // Paint the background for the insertion point
Rectangle b = getDecorationBounds();
g.setColor(list.getBackground());
g.fillRect(b.x, b.y, b.width, b.height);
for (int i=0;i < list.getModel().getSize();i++) {
if (i == draggedIndex)
continue;
Rectangle r = getCurrentCellBounds(i);
Graphics g2 = g.create(r.x, r.y, r.width, r.height);
Rectangle r2 = list.getCellBounds(i, i);
((Graphics2D)g2).translate(0, -r2.y);
list.paint(g2);
}

The Graphics object is set up to paint only in the desired area, then translated such that the JList will paint the desired cell into that area. We skip the index being dragged, since we want to handle that separately.

To draw the ghosted image, I simply used another decorator that uses the JList to render the dragged item. As the insertion location is updated by the mouse listener, the ghost image adjusts its vertical drawing position (being careful to clamp the vertical bounds to the start and end of the list).

The insertion point could use something more fancy if needed, like using the cell renderer or explicitly painting the rectangle. Just leaving the background exposed works well in this case, though.

A timer task periodically moves each cell's current location toward its final location, and triggers a repaint if any of the locations actually changes. This should be optimized to track a smaller range of cells (like only those visible).

The basic calculation of a cell's desired position simply calculates its normal position, then adjusts for any preceding dragged item (to be removed) and any preceding insertion point (to be inserted). The current locations start off with no dragged item or insertion point, and gradually float to their final positions based on a dragged item and possibly changing insertion point. Note that if you rapidly drag across several items, they all will float smoothly even as their destination changes.

Friday, April 28, 2006

Matisse First Impressions

My compliments to the Matisse crew (and a hat tip to those who have walked this path before). This is truly what a layout editor should look like. Thanks for examining prior art and learning from it. I now have a few projects that now have a netbeans project folder and extra .form files.

That said, here are a few bumps I've run into so far...

Custom Component Creation
Rather than inline code in the XML form, initComponents should either take arguments or simply rely on field contents. Putting the code in XML makes it infeasible to automatically refactor. For example, I have a filename text field that provides a browsing action synched to the editability/enabled state of the text field. So to create the button, I need

new JButton(field.getBrowseAction())

I'd rather just create the component myself prior to initComponents(), and have initComponents() know that that field has already been initialized. Perhaps split between initializing and doing the layout?

Changing Component Names
If you edit the member field name for a component, you don't get a corresponding refactor of the code.

Alternate Components in Layout
I'm trying to figure out the best way to swap out two different panels. One has just a password, one has a filename and a password. I'd like to swap between the two and have the layout still work, but the layout assumes that the component hierarchy is static. I'll probably just set up a container panel with the appropriate resizing attributes, then add/remove the alternates as needed.

Strings
I should be able to substitute a string lookup for a hard-coded string, whether in labels or text fields or whatever. Granted, I can override whatever happens in initComponents, but then I'm maintaining the same string twice - once in my string/properties tables, and again in the UI designer.

Cross-Panel alignment
Sometimes I'd like to operate on a number of components as a group (resize the right edge, for instance) and this doesn't seem possible. It'd be nice if the alignment lines could be inferred and then themselves could be draggable. Putting things into sub-panels has the unfortunate side-effect that you can only align with other things in that panel.

Grid Size/Spacing
I'm surprised this one got through. I'm stuck with 10 pixel margins that I can't change. So my choices are 10 or 0 pixels. Ugh. There's a preference for Grid X/Grid Y, but it doesn't seem to have any effect.

Monday, April 17, 2006

Decorator/Overpainter Update

I've updated the decorator to handle most LAF backgrounds. You can now modify the background on just about anything that relies on a parent to paint its background.

The selection marquee is also no longer clipped to its parent component.



Now you might say "I can just paint on my glass pane" and you'd be right except that:
* Your glass pane eats all user input events once it's visible unless you explicitly redirect them
* You glass pane doesn't know anything about what's under it
* It always paints *above* everything else

The AbstractComponentDecorator is a bit more flexible in that you can target all or part of a specific component or a specific area of your window.

Saturday, April 08, 2006

Decorating/Overpainting Swing Components


Have you ever wanted to change the way an existing component appears, just a little, without having to go and rewrite it or subclass it?

The image at right demonstrates several implementations of an abstract base class which puts an arbitrary decoration over an existing component. This technique allows you to do any of the following:

  • Put labels on a scrolled component which stay fixed relative to the scroll pane rather than the scrolled component (useful for labeling horizontal lines on a panned display).
  • Put an icon badge anywhere on a component, such as a stop sign over invalid text in a text field
  • Put tooltips on a component that you didn't write (like adding annotations to someone else's work).
  • Dim the entire painted image to gray, or fade it into the background color to make the component look disabled.
  • Change the appearance of component's background (gradients, stripes, or polka-dots).
  • Apply animation effects to the component.
  • Marching ants/Rubber band selection rectangle (xor is so Seventies).
  • Highlight all or part of an existing component to indicate the effective target of a drop operation (changing the selection isn't always the best solution).

to see a few of these decorations in action.

Note that the code for each of the following decorations consists of the painting logic and optionally positioning logic, which is all you should be concerned about.

The implementation for drawing a badge:
class Warning extends AbstractComponentDecorator {
    private final int SIZE = 16;
    public Warning(JTextField f) {
        super(f);
    }
    /** Position the badge at the right-most edge. */
    public Rectangle getDecorationBounds() {
        Rectangle r = super.getDecorationBounds();
        Insets insets = getComponent().getInsets();
        r.x += r.width - SIZE - 1;
        r.y += (r.height - SIZE) / 2;
        if (insets != null) {
            r.x -= insets.right;
        }
        return r;
    }
    public void paint(Graphics graphics) {
        Rectangle r = getDecorationBounds();
        Graphics2D g = (Graphics2D)graphics;
        GeneralPath triangle = new GeneralPath();
        triangle.moveTo(r.x + SIZE/2, r.y);
        triangle.lineTo(r.x + SIZE-1, r.y + SIZE-1);
        triangle.lineTo(r.x, r.y + SIZE-1);
        triangle.closePath();
        g.setColor(Color.yellow);
        g.fill(triangle);
        g.setColor(Color.black);
        g.draw(triangle);
        g.drawLine(r.x + SIZE/2, r.y + 3, r.x + SIZE/2, r.y + SIZE*3/4 - 2);
        g.drawLine(r.x + SIZE/2, r.y + SIZE*3/4+1, r.x + SIZE/2, r.y + SIZE - 4);
    }
}
The implementation for dimming:

class Dimmer extends AbstractComponentDecorator {
    public Dimmer(JComponent target) {
        super(target);
    }
    /** Paint using a transparent version of the bg color. */
    public void paint(Graphics g) {
        Color bg = getComponent().getBackground();
        g.setColor(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 200));
        Rectangle r = getDecorationBounds();
        g.fillRect(r.x, r.y, r.width, r.height);
    }
}
The implementation for semi-scrolling labels:

   private static final int SIZE = 1000;
   private static final int BLOCK = 20;
   private static final int LINE = 20;

   private static class Labeler extends AbstractComponentDecorator {
       public Labeler(JComponent target) {
           super(target);
       }
       /** Ensure the label stays at the left-most visible
        * edge of the component.
        */
       public Rectangle getDecorationBounds() {
           Rectangle r = super.getDecorationBounds();
           Rectangle visible = getComponent().getVisibleRect();
           if (r.x < visible.x)
              r.x = visible.x;
           return r;
       }
       public void paint(Graphics g) {
           Rectangle r = getDecorationBounds();
           for (int i=0;i < SIZE;i+= LINE) {
               g.drawString("label " + (i/LINE + 1),
                            r.x, r.y + i + g.getFontMetrics().getAscent() + 2);
           }
       }
   } 


This class started out as a way to highlight different drop targets in a tree or table, and a method for the Costello editor to highlight areas of a component to be captured as an image (without actually setting the target component's background). I've subsequently used it for other decorations as noted above.

The whole idea would be trivial if Swing provided a hook into a component's paint method. There is no such explicit hook, and no implicit ones either without getting into a lot of complexity (perhaps by writing your own repaint manager). Since writing a repaint manager seems totally orthogonal to decorating a single component, we take a different tack.

My first implementation was to simply add a sibling or child component into the hierarchy on top of the first (and indeed, that implementation is still in use by the Costello editor). Unfortunately, the normal component hierarchy doesn't really consider z ordering, so the results are not always consistent. You might get things painted properly 90% of the time, but occasionally have your decoration occluded by a scroll pane, or the fact that there's an extra component in the hierarchy causes the layout manager to occasionally freak out. So it's okay for a proof of concept, but kinda lame in practice.

There is, however, a Swing component that knows about z ordering. Most Swing components will reside somewhere below a JLayeredPane (see the javadoc for javax.swing.RootPaneContainer for details). This little component has the capability of painting things in layers, which is just the capability we're looking for. The JLayeredPane even has predefined layers and was explicitly designed for stacking components in a known Z order (it also has a sub-layer ordering referred to as "position", but this doesn't seem to work).

So the basic idea is that for any decoration, we establish a painting component as a child of the JLayeredPane which is positioned in a layer above the decorated component. The Swing painting mechanism will automatically ensure that our decoration gets painted whenever the decorated component is painted, and that the decoration happens after the target component is painted.

A few details to note:

  • The decorator must maintain proper position and size with respect to the target component. If the decoration is in the lower right corner of the component, it should stay there if the component grows.
  • The decorator must clip its painting according to how much of the target component is visible. If the component is partially obscured within a scroll pane, the decorator should be clipped in a similar fashion.
  • The decorator needs to track the target component's visibility. If the component is on a tabbed pane and a different tab is displayed, the decoration needs to be hidden.
  • Painting under the decorated component is a bit more tricky. The current implementation for decorating component backgrounds assumes the current LAF doesn't change how ComponentUI paints the default background (gtk/synth probably won't work well). I haven't really looked into this much.
  • It's possible to composite with the existing graphics (so you could, for instance, paint only where there's already a blue pixel), but this capability varies by platform and VM version.


Full source for the demo and AbstractComponentDecorator class.