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.

No comments: