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.