Wednesday, March 28, 2007

Give your application a speech bubble

As a result of an argument about the best way to present meta-information about complex application objects presented to the user, I had to come up with some concrete alternatives.

The problem is that for a given item in the application (in this case an SVN-based revision control client that aggregates and presents large amounts of meta-information about the repository), there may exist information that just doesn't fit in the display. You want to provide the user with a route to the desired information with as few inputs as possible.

Tooltips is one alternative, but aren't always appropriate. While you can set (globally) the delay before appearance and the time before dismissal, dismissing them is implicit. In my application, I need the meta information to be displayed or hidden explicitly, so that rules out tooltips (at least without substantial modification of the tooltip manager).

A dedicated "inspector" window is another alternative, but kind of heavyweight. We definitely don't want a component that can be activated, since that introduces focus issues. I also didn't want to make it appear to the user that there was a generic "container" that kept resizing and moving, as if it were a tool palette that couldn't decide on its purpose. At a minimum the window has to be undecorated and pretty much behave like a tooltip.

What I really wanted was something in between the two. A simple Popup with a unique appearance which could be shown or hidden at will. Apple introduced something like this as Balloon Help years ago, although I think the main problem was that they didn't get the trigger quite right. One reason a lot of expert users don't like tooltips (or balloon help) is that they show up when you don't expect it and you have to wave the mouse to make them go away (usually causing another one to be triggered).

Google maps does a nice job with their balloon tips. The trigger is obvious, and the tip itself provides a close button (and now I notice a maximize button as well).

So I started out with Popups via PopupFactory and wound up with another demonstration of non-rectangular clipping, brought to you by the JNA project. The API is pretty simple:


Component myComponent;
Component myBubble;
Point where; // location relative to myComponent
final Popup popup = BalloonManager.getBalloon(myComponent, myBubble, where.x, where.y);
popup.show();
// Hide the popup on mouse clicks
myBubble.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
popup.hide();
}
});


The balloon is a simple rounded rectangle with a triangle stuck to the bottom, with the corresponding Area used as the window mask.

I originally tried using PopupFactory directly, but it doesn't provide enough control over the intermediate components (the ancestors of the content that you don't really see; there's a pesky medium-weight popup that uses java.awt.Panel, which can't be easily masked). This implementation uses a window for any popup, regardless of whether its fully contained within the parent component.

Drop shadows would be nice (maybe someone wants to contribute some compositing code?).




Tuesday, March 13, 2007

Building w32 JNI DLLs with gcc

After a bit of trial and error, I got JNA's native shared library to build (and run, and run tests) using gcc. Unfortunately the available documentation didn't quite work for me (might be a problem with my cygwin installation, but I wasn't the only one with an issue).

gcc-mingw is a version of cygwin gcc that can compile directly to the w32 api, without any of the cygwin emulation layer.


% gcc -dumpspecs | sed s%dllcrt2%/lib/mingw/dllcrt2%g > specs.new
% gcc -mno-cygwin -D_REENTRANT -D_GNU_SOURCE \
-D__int64="long long" -D_JNI_IMPLEMENTATION \
-o
-shared -Wl,--kill-at -specs specs.new -L /lib/mingw


The -mno-cygwin flag tells the compiler to run in mingw mode, i.e. omit all the cygwin unix compatibility layer and compile directly against the w32 api.

GCC's built-in 64-bit type is long long, so we map the type used in the JNI headers, __int64, to that type. Defining _JNI_IMPLEMENTATION ensures the JNI implementation exports its native method declarations.

Defining _REENTRANT and _GNU_SOURCE cause certain additional declarations of C library functions to be included. If you've never heard of them, most likely you don't need them.

The --kill-at flag to the linker ensures all symbols are exported undecorated, i.e. _my_func instead of _my_func@NN, which is the default when a function is declared as __stdcall. You could also use --add-stdcall-alias, which includes both versions of the symbol.

The GCC specs need tweaking or gcc-mingw doesn't find its initialization code for dlls, dllcrt2.o. We also have to nudge gcc-mingw to find the mingw libraries. Normally this is taken care of automatically by gcc, but for some reason my installation of cygwin gcc wouldn't find them, and no amount of -L or explicit object linkage would fix it.

Anyhow, with that out of the way, you don't have to have Microsoft's tools to build JNA or any other JNI library on w32.

I have noted that GCC apparently handles floating point a little differently, since the JNA tests that use FP return values/arguments are failing with the GCC-build dll.

Tuesday, February 27, 2007

Non-rectangular Windows Update




Updated to include support for OSX and linux.

While OSX doesn't actually use any native code to do the window mask, JNA support is there if it needed to. Source (and per-platform binaries) is available at http://jna.dev.java.net.

Update
Transparent version of the demo is available here.

Friday, February 23, 2007

Non-rectangular Windows

I've been meaning to play around with shaped windows for a while, but didn't relish the thought of walking through the tedium of JNI configurations and builds. Doing it on one platform is bad enough, but on several? No thanks.

So I thought I'd write a little scriptable shared library access stub once and be done with JNI entirely. Well, turns out it's already been done. Several times.

JNative
NLink
JNA

JNative has some interesting features not found in the others, but actually using it is only slightly better than JNI. NLink is currently w32-only, and has a bit of a COM bent. JNA fit most closely with my objectives, already had implementations for w32 and solaris, and was already platform-agnostic. So I took a couple days to hack in some more features (mostly to get an understanding of the codebase), and here is what I got. The following code is what it takes to make a frame take an arbitrary shape.

User32 user32 = User32.INSTANCE;
GDI32 gdi32 = GDI32.INSTANCE;
JFrame frame = new JFrame(getName());
Pointer p = gdi32.CreateRoundRectRgn(0, -150, 300, 300, 300, 300);
int hWnd = user32.FindWindowA(null, getName());
user32.SetWindowRgn(hWnd, p, true);

Looking up the appropriate w32 calls probably took the most time. How are the w32 libraries defined? How is this for trivial:

public interface User32 extends StdCallLibarary {
User32 INSTANCE = (User32)Native.loadLibary("user32", User32.class);
int FindWindowA(String winClass, String title);
void setWindowRgn(int hWnd, Pointer p, boolean redraw);
}
public interface GDI32 extends StdCallLibrary {
GDI32 INSTANCE = (GDI32)Native.loadLibrary("gdi32", GDI32.class);
Pointer CreateRoundRectRgn(int left, int top, int right, int bottom, int w, int h);
}

Now, somebody's probably going to point me to how SWT has had this functionality for years (does it?), but this is nicely abstracted and based on a very small number of classes. I'll be updating the code at jna.dev.java.net (or maybe opening a new location if I can't get the existing project moved to subversion), but for now, check out the demo by clicking on everyone's favorite orange launch button.




Permissions required, because this runs some custom native code.

Oh, BTW, this is windows-only for the moment. I'll do X11 next and anyone's free to send me some code snippets for setting window masks on other platforms. I could also use some help porting to PPC and other platforms (a very small amount of native ASM to push arguments to the stack, not too hard).

Friday, February 16, 2007

JDK 1.6 Applet Madness

I had a few applets on the main page showing some interesting eye candy. Then I upgraded my JDK to 1.6 and they lock up the java plugin (IE or firefox). If anyone has encountered a similar issue, please let me know, I'd really like to fix it.

I've gotten a variety of errors, but I can't seem to come up with an effective workaround or consistent analysis (JDK 1.6 doesn't let you pick a different VM to use for applets).

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.