diff --git a/projects/samskivert/src/java/com/samskivert/swing/dnd/DnDManager.java b/projects/samskivert/src/java/com/samskivert/swing/dnd/DnDManager.java new file mode 100644 index 00000000..ab3cfc35 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/swing/dnd/DnDManager.java @@ -0,0 +1,229 @@ +// +// $Id: DnDManager.java,v 1.1 2002/08/20 02:49:19 ray Exp $ + +package com.samskivert.swing.dnd; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Image; +import java.awt.Point; +import java.awt.Toolkit; + +import java.awt.event.AWTEventListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; + +import javax.swing.JComponent; + +import javax.swing.event.AncestorEvent; + +import com.samskivert.swing.event.AncestorAdapter; + +/** + * A custom Drag and Drop manager for use within a single JVM. Does what we + * need it to do and no more. + */ +public class DnDManager + implements MouseMotionListener, AWTEventListener +{ + /** + * Add the specified component as a source of drags, with the DragSource + * controller. + */ + public static void addDragSource (DragSource source, JComponent comp) + { + singleton.addSource(source, comp); + } + + /** + * Add the specified component as a drop target. + */ + public static void addDropTarget (DropTarget target, JComponent comp) + { + singleton.addTarget(target, comp); + } + + /** + * Create a custom cursor out of the specified image. + */ + public static Cursor createImageCursor (Image img) + { + // TODO: check colors/size + Toolkit tk = Toolkit.getDefaultToolkit(); + return tk.createCustomCursor(img, + new Point(img.getWidth(null) / 2, img.getHeight(null) / 2), + "samskivertDnDCursor"); + } + + /** + * Restrict construction. + */ + private DnDManager () + { + } + + /** + * Add a dragsource. + */ + protected void addSource (DragSource source, JComponent comp) + { + _draggers.put(comp, source); + comp.addAncestorListener(_remover); + comp.addMouseMotionListener(this); + } + + /** + * Add a droptarget. + */ + protected void addTarget (DropTarget target, JComponent comp) + { + _droppers.put(comp, target); + comp.addAncestorListener(_remover); + } + + // documentation inherited from interface MouseMotionListener + public void mouseMoved (MouseEvent me) + { + // who cares. + } + + // documentation inherited from interface MouseMotionListener + public void mouseDragged (MouseEvent me) + { + // make sure a drag hasn't already started. + if (_sourceComp != null) { + return; + } + + _sourceComp = me.getComponent(); + _source = (DragSource) _draggers.get(_sourceComp); + + // make sure the source wants to start a drag. + if (!_source.startDrag(_cursors, _data)) { + // if not, reset our start conditions and bail + reset(); + return; + } + + // use standard cursors if custom ones not specified + if (_cursors[0] == null) { + _cursors[0] = java.awt.dnd.DragSource.DefaultMoveDrop; + } + if (_cursors[1] == null) { + _cursors[1] = java.awt.dnd.DragSource.DefaultMoveNoDrop; + } + + // install a listener so we know everywhere that the mouse enters + Toolkit.getDefaultToolkit().addAWTEventListener(this, + AWTEvent.MOUSE_EVENT_MASK); + + // and start out with the no-drop cursor + _lastComp = _sourceComp; + _lastComp.setCursor(_cursors[1]); + } + + // documentation inherited from interface AWTEventListener + public void eventDispatched (AWTEvent event) + { + int id = event.getID(); + if (id == MouseEvent.MOUSE_ENTERED) { + Component oldcomp = _lastComp; + _lastComp = ((MouseEvent) event).getComponent(); + _lastTarget = findAppropriateTarget(_lastComp); + _lastComp.setCursor(_cursors[(_lastTarget == null) ? 1 : 0]); + if (_lastComp != oldcomp) { + oldcomp.setCursor(null); + } + + } else if (id == MouseEvent.MOUSE_RELEASED) { + // since the release comes with a component of the source, + // we use the last enter... + if (_lastTarget != null) { + _lastTarget.dropCompleted(_data[0]); + _source.dragCompleted(_lastTarget); + } + + if (_lastComp != null) { + _lastComp.setCursor(null); + } + Toolkit.getDefaultToolkit().removeAWTEventListener(this); + reset(); + } + } + + /** + * Find the lowest accepting parental target to this component. + */ + protected DropTarget findAppropriateTarget (Component comp) + { + Component parent; + DropTarget target; + while (true) { + // here we sneakily prevent dropping on the source + target = (comp == _sourceComp) ? null + : (DropTarget) _droppers.get(comp); + if ((target != null) && target.checkDrop(_source, _data[0])) { + return target; + } + parent = comp.getParent(); + if (parent == null) { + return null; + } + comp = parent; + } + } + + /** + * Reset dnd to a starting state. + */ + protected void reset () + { + _source = null; + _sourceComp = null; + _lastComp = null; + _lastTarget = null; + _data[0] = null; + _cursors[0] = null; + _cursors[1] = null; + } + + /** A handy helper that removes components when they're no longer in + * the hierarchy. */ + protected AncestorAdapter _remover = new AncestorAdapter() { + public void ancestorRemoved (AncestorEvent ae) + { + JComponent comp = ae.getComponent(); + _draggers.remove(comp); + _droppers.remove(comp); + } + }; + + /** Our DropTargets, indexed by associated Component. */ + protected HashMap _droppers = new HashMap(); + + /** Our DragSources, indexed by associated component. */ + protected HashMap _draggers = new HashMap(); + + /** The original and last component that the mouse was in during a drag. */ + protected Component _sourceComp, _lastComp; + + /** The source of a drag. */ + protected DragSource _source; + + /** The last target, or null if no last target. */ + protected DropTarget _lastTarget; + + /** The accept/reject cursors. */ + protected Cursor[] _cursors = new Cursor[2]; + + /** The data to be passed in the drop. */ + protected Object[] _data = new Object[1]; + + /** A single manager for the entire JVM. */ + protected static final DnDManager singleton = new DnDManager(); +} diff --git a/projects/samskivert/src/java/com/samskivert/swing/dnd/DragSource.java b/projects/samskivert/src/java/com/samskivert/swing/dnd/DragSource.java new file mode 100644 index 00000000..2c5911f6 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/swing/dnd/DragSource.java @@ -0,0 +1,31 @@ +// +// $Id: DragSource.java,v 1.1 2002/08/20 02:49:19 ray Exp $ + +package com.samskivert.swing.dnd; + +import java.awt.Cursor; + +/** + * A drag source is associated with a component and represents a valid + * place to start a drag and drop operation. + */ +public interface DragSource +{ + /** + * Called by the DnDManager to verify that a drag may begin at this source. + * + * @param cursors a two-element array- the first element should contain + * the cursor to use for valid drops, the second for invalid drops. Either + * or both may be left blank to use the default DnD cursors. + * @param data a single-element array that should be filled in with + * the data that will be sent to a DropTarget if there is a successful + * drop. + * @return true if the drag may begin. + */ + public boolean startDrag (Cursor[] cursors, Object[] data); + + /** + * A callback to let the source know that the drop completed successfully. + */ + public void dragCompleted (DropTarget target); +} diff --git a/projects/samskivert/src/java/com/samskivert/swing/dnd/DropTarget.java b/projects/samskivert/src/java/com/samskivert/swing/dnd/DropTarget.java new file mode 100644 index 00000000..82832d31 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/swing/dnd/DropTarget.java @@ -0,0 +1,21 @@ +// +// $Id: DropTarget.java,v 1.1 2002/08/20 02:49:19 ray Exp $ + +package com.samskivert.swing.dnd; + +/** + * A drop target is associated with a component and represents a valid + * place to end a drag and drop. + */ +public interface DropTarget +{ + /** + * Return true if the specified drop should be allowed on this target. + */ + public boolean checkDrop (DragSource source, Object peekdata); + + /** + * Called when the drop is actually executed. + */ + public void dropCompleted (Object data); +}