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.

4 comments:

Anonymous said...

Hi,

I liked your ghost image implementation along with DragHandler and DropHandler and I wanted to use it in my project. But it didn't work in applets. To enable it for applets I would propose to replace all occurances of

SwingUtilities.getWindowAncestor(component)

with

SwingUtilities.getAncestorOfClass(RootPaneContainer.class, component)


Kind regards,
V

technomage said...

Thanks for the tip

daljeet said...

can you give me the code for an implementation of drag on applets.i would be very grateful for it.

technomage said...

I think the previous comment indicates pretty clearly what you'd need to do, although I vaguely recall putting that into the source code.