|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package java.awt.dnd; |
|
|
|
import java.util.TooManyListenersException; |
|
|
|
import java.io.IOException; |
|
import java.io.ObjectInputStream; |
|
import java.io.ObjectOutputStream; |
|
import java.io.Serializable; |
|
|
|
import java.awt.Component; |
|
import java.awt.Dimension; |
|
import java.awt.GraphicsEnvironment; |
|
import java.awt.HeadlessException; |
|
import java.awt.Insets; |
|
import java.awt.Point; |
|
import java.awt.Rectangle; |
|
import java.awt.Toolkit; |
|
import java.awt.event.ActionEvent; |
|
import java.awt.event.ActionListener; |
|
import java.awt.datatransfer.FlavorMap; |
|
import java.awt.datatransfer.SystemFlavorMap; |
|
import javax.swing.Timer; |
|
import java.awt.peer.ComponentPeer; |
|
import java.awt.peer.LightweightPeer; |
|
import java.awt.dnd.peer.DropTargetPeer; |
|
|
|
|
|
/** |
|
* The <code>DropTarget</code> is associated |
|
* with a <code>Component</code> when that <code>Component</code> |
|
* wishes |
|
* to accept drops during Drag and Drop operations. |
|
* <P> |
|
* Each |
|
* <code>DropTarget</code> is associated with a <code>FlavorMap</code>. |
|
* The default <code>FlavorMap</code> hereafter designates the |
|
* <code>FlavorMap</code> returned by <code>SystemFlavorMap.getDefaultFlavorMap()</code>. |
|
* |
|
* @since 1.2 |
|
*/ |
|
|
|
public class DropTarget implements DropTargetListener, Serializable { |
|
|
|
private static final long serialVersionUID = -6283860791671019047L; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DropTarget(Component c, int ops, DropTargetListener dtl, |
|
boolean act, FlavorMap fm) |
|
throws HeadlessException |
|
{ |
|
if (GraphicsEnvironment.isHeadless()) { |
|
throw new HeadlessException(); |
|
} |
|
|
|
component = c; |
|
|
|
setDefaultActions(ops); |
|
|
|
if (dtl != null) try { |
|
addDropTargetListener(dtl); |
|
} catch (TooManyListenersException tmle) { |
|
// do nothing! |
|
} |
|
|
|
if (c != null) { |
|
c.setDropTarget(this); |
|
setActive(act); |
|
} |
|
|
|
if (fm != null) { |
|
flavorMap = fm; |
|
} else { |
|
flavorMap = SystemFlavorMap.getDefaultFlavorMap(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DropTarget(Component c, int ops, DropTargetListener dtl, |
|
boolean act) |
|
throws HeadlessException |
|
{ |
|
this(c, ops, dtl, act, null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DropTarget() throws HeadlessException { |
|
this(null, DnDConstants.ACTION_COPY_OR_MOVE, null, true, null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DropTarget(Component c, DropTargetListener dtl) |
|
throws HeadlessException |
|
{ |
|
this(c, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true, null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DropTarget(Component c, int ops, DropTargetListener dtl) |
|
throws HeadlessException |
|
{ |
|
this(c, ops, dtl, true); |
|
} |
|
|
|
/** |
|
* Note: this interface is required to permit the safe association |
|
* of a DropTarget with a Component in one of two ways, either: |
|
* <code> component.setDropTarget(droptarget); </code> |
|
* or <code> droptarget.setComponent(component); </code> |
|
* <P> |
|
* The Component will receive drops only if it is enabled. |
|
* @param c The new <code>Component</code> this <code>DropTarget</code> |
|
* is to be associated with. |
|
*/ |
|
|
|
public synchronized void setComponent(Component c) { |
|
if (component == c || component != null && component.equals(c)) |
|
return; |
|
|
|
Component old; |
|
ComponentPeer oldPeer = null; |
|
|
|
if ((old = component) != null) { |
|
clearAutoscroll(); |
|
|
|
component = null; |
|
|
|
if (componentPeer != null) { |
|
oldPeer = componentPeer; |
|
removeNotify(componentPeer); |
|
} |
|
|
|
old.setDropTarget(null); |
|
|
|
} |
|
|
|
if ((component = c) != null) try { |
|
c.setDropTarget(this); |
|
} catch (Exception e) { |
|
if (old != null) { |
|
old.setDropTarget(this); |
|
addNotify(oldPeer); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Gets the <code>Component</code> associated |
|
* with this <code>DropTarget</code>. |
|
* <P> |
|
* @return the current <code>Component</code> |
|
*/ |
|
|
|
public synchronized Component getComponent() { |
|
return component; |
|
} |
|
|
|
/** |
|
* Sets the default acceptable actions for this <code>DropTarget</code> |
|
* <P> |
|
* @param ops the default actions |
|
* @see java.awt.dnd.DnDConstants |
|
*/ |
|
|
|
public void setDefaultActions(int ops) { |
|
getDropTargetContext().setTargetActions(ops & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE)); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
void doSetDefaultActions(int ops) { |
|
actions = ops; |
|
} |
|
|
|
/** |
|
* Gets an <code>int</code> representing the |
|
* current action(s) supported by this <code>DropTarget</code>. |
|
* <P> |
|
* @return the current default actions |
|
*/ |
|
|
|
public int getDefaultActions() { |
|
return actions; |
|
} |
|
|
|
/** |
|
* Sets the DropTarget active if <code>true</code>, |
|
* inactive if <code>false</code>. |
|
* <P> |
|
* @param isActive sets the <code>DropTarget</code> (in)active. |
|
*/ |
|
|
|
public synchronized void setActive(boolean isActive) { |
|
if (isActive != active) { |
|
active = isActive; |
|
} |
|
|
|
if (!active) clearAutoscroll(); |
|
} |
|
|
|
/** |
|
* Reports whether or not |
|
* this <code>DropTarget</code> |
|
* is currently active (ready to accept drops). |
|
* <P> |
|
* @return <CODE>true</CODE> if active, <CODE>false</CODE> if not |
|
*/ |
|
|
|
public boolean isActive() { |
|
return active; |
|
} |
|
|
|
/** |
|
* Adds a new <code>DropTargetListener</code> (UNICAST SOURCE). |
|
* <P> |
|
* @param dtl The new <code>DropTargetListener</code> |
|
* <P> |
|
* @throws TooManyListenersException if a |
|
* <code>DropTargetListener</code> is already added to this |
|
* <code>DropTarget</code>. |
|
*/ |
|
|
|
public synchronized void addDropTargetListener(DropTargetListener dtl) throws TooManyListenersException { |
|
if (dtl == null) return; |
|
|
|
if (equals(dtl)) throw new IllegalArgumentException("DropTarget may not be its own Listener"); |
|
|
|
if (dtListener == null) |
|
dtListener = dtl; |
|
else |
|
throw new TooManyListenersException(); |
|
} |
|
|
|
/** |
|
* Removes the current <code>DropTargetListener</code> (UNICAST SOURCE). |
|
* <P> |
|
* @param dtl the DropTargetListener to deregister. |
|
*/ |
|
|
|
public synchronized void removeDropTargetListener(DropTargetListener dtl) { |
|
if (dtl != null && dtListener != null) { |
|
if(dtListener.equals(dtl)) |
|
dtListener = null; |
|
else |
|
throw new IllegalArgumentException("listener mismatch"); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void dragEnter(DropTargetDragEvent dtde) { |
|
isDraggingInside = true; |
|
|
|
if (!active) return; |
|
|
|
if (dtListener != null) { |
|
dtListener.dragEnter(dtde); |
|
} else |
|
dtde.getDropTargetContext().setTargetActions(DnDConstants.ACTION_NONE); |
|
|
|
initializeAutoscrolling(dtde.getLocation()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void dragOver(DropTargetDragEvent dtde) { |
|
if (!active) return; |
|
|
|
if (dtListener != null && active) dtListener.dragOver(dtde); |
|
|
|
updateAutoscroll(dtde.getLocation()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void dropActionChanged(DropTargetDragEvent dtde) { |
|
if (!active) return; |
|
|
|
if (dtListener != null) dtListener.dropActionChanged(dtde); |
|
|
|
updateAutoscroll(dtde.getLocation()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void dragExit(DropTargetEvent dte) { |
|
isDraggingInside = false; |
|
|
|
if (!active) return; |
|
|
|
if (dtListener != null && active) dtListener.dragExit(dte); |
|
|
|
clearAutoscroll(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void drop(DropTargetDropEvent dtde) { |
|
isDraggingInside = false; |
|
|
|
clearAutoscroll(); |
|
|
|
if (dtListener != null && active) |
|
dtListener.drop(dtde); |
|
else { |
|
dtde.rejectDrop(); |
|
} |
|
} |
|
|
|
/** |
|
* Gets the <code>FlavorMap</code> |
|
* associated with this <code>DropTarget</code>. |
|
* If no <code>FlavorMap</code> has been set for this |
|
* <code>DropTarget</code>, it is associated with the default |
|
* <code>FlavorMap</code>. |
|
* <P> |
|
* @return the FlavorMap for this DropTarget |
|
*/ |
|
|
|
public FlavorMap getFlavorMap() { return flavorMap; } |
|
|
|
/** |
|
* Sets the <code>FlavorMap</code> associated |
|
* with this <code>DropTarget</code>. |
|
* <P> |
|
* @param fm the new <code>FlavorMap</code>, or null to |
|
* associate the default FlavorMap with this DropTarget. |
|
*/ |
|
|
|
public void setFlavorMap(FlavorMap fm) { |
|
flavorMap = fm == null ? SystemFlavorMap.getDefaultFlavorMap() : fm; |
|
} |
|
|
|
/** |
|
* Notify the DropTarget that it has been associated with a Component |
|
* |
|
********************************************************************** |
|
* This method is usually called from java.awt.Component.addNotify() of |
|
* the Component associated with this DropTarget to notify the DropTarget |
|
* that a ComponentPeer has been associated with that Component. |
|
* |
|
* Calling this method, other than to notify this DropTarget of the |
|
* association of the ComponentPeer with the Component may result in |
|
* a malfunction of the DnD system. |
|
********************************************************************** |
|
* <P> |
|
* @param peer The Peer of the Component we are associated with! |
|
* |
|
*/ |
|
|
|
public void addNotify(ComponentPeer peer) { |
|
if (peer == componentPeer) return; |
|
|
|
componentPeer = peer; |
|
|
|
for (Component c = component; |
|
c != null && peer instanceof LightweightPeer; c = c.getParent()) { |
|
peer = c.getPeer(); |
|
} |
|
|
|
if (peer instanceof DropTargetPeer) { |
|
nativePeer = peer; |
|
((DropTargetPeer)peer).addDropTarget(this); |
|
} else { |
|
nativePeer = null; |
|
} |
|
} |
|
|
|
/** |
|
* Notify the DropTarget that it has been disassociated from a Component |
|
* |
|
********************************************************************** |
|
* This method is usually called from java.awt.Component.removeNotify() of |
|
* the Component associated with this DropTarget to notify the DropTarget |
|
* that a ComponentPeer has been disassociated with that Component. |
|
* |
|
* Calling this method, other than to notify this DropTarget of the |
|
* disassociation of the ComponentPeer from the Component may result in |
|
* a malfunction of the DnD system. |
|
********************************************************************** |
|
* <P> |
|
* @param peer The Peer of the Component we are being disassociated from! |
|
*/ |
|
|
|
public void removeNotify(ComponentPeer peer) { |
|
if (nativePeer != null) |
|
((DropTargetPeer)nativePeer).removeDropTarget(this); |
|
|
|
componentPeer = nativePeer = null; |
|
|
|
synchronized (this) { |
|
if (isDraggingInside) { |
|
dragExit(new DropTargetEvent(getDropTargetContext())); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Gets the <code>DropTargetContext</code> associated |
|
* with this <code>DropTarget</code>. |
|
* <P> |
|
* @return the <code>DropTargetContext</code> associated with this <code>DropTarget</code>. |
|
*/ |
|
|
|
public DropTargetContext getDropTargetContext() { |
|
return dropTargetContext; |
|
} |
|
|
|
/** |
|
* Creates the DropTargetContext associated with this DropTarget. |
|
* Subclasses may override this method to instantiate their own |
|
* DropTargetContext subclass. |
|
* |
|
* This call is typically *only* called by the platform's |
|
* DropTargetContextPeer as a drag operation encounters this |
|
* DropTarget. Accessing the Context while no Drag is current |
|
* has undefined results. |
|
*/ |
|
|
|
protected DropTargetContext createDropTargetContext() { |
|
return new DropTargetContext(this); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void writeObject(ObjectOutputStream s) throws IOException { |
|
s.defaultWriteObject(); |
|
|
|
s.writeObject(SerializationTester.test(dtListener) |
|
? dtListener : null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void readObject(ObjectInputStream s) |
|
throws ClassNotFoundException, IOException |
|
{ |
|
ObjectInputStream.GetField f = s.readFields(); |
|
|
|
try { |
|
dropTargetContext = |
|
(DropTargetContext)f.get("dropTargetContext", null); |
|
} catch (IllegalArgumentException e) { |
|
// Pre-1.4 support. 'dropTargetContext' was previously transient |
|
} |
|
if (dropTargetContext == null) { |
|
dropTargetContext = createDropTargetContext(); |
|
} |
|
|
|
component = (Component)f.get("component", null); |
|
actions = f.get("actions", DnDConstants.ACTION_COPY_OR_MOVE); |
|
active = f.get("active", true); |
|
|
|
|
|
try { |
|
dtListener = (DropTargetListener)f.get("dtListener", null); |
|
} catch (IllegalArgumentException e) { |
|
|
|
dtListener = (DropTargetListener)s.readObject(); |
|
} |
|
} |
|
|
|
/*********************************************************************/ |
|
|
|
/** |
|
* this protected nested class implements autoscrolling |
|
*/ |
|
|
|
protected static class DropTargetAutoScroller implements ActionListener { |
|
|
|
/** |
|
* construct a DropTargetAutoScroller |
|
* <P> |
|
* @param c the <code>Component</code> |
|
* @param p the <code>Point</code> |
|
*/ |
|
|
|
protected DropTargetAutoScroller(Component c, Point p) { |
|
super(); |
|
|
|
component = c; |
|
autoScroll = (Autoscroll)component; |
|
|
|
Toolkit t = Toolkit.getDefaultToolkit(); |
|
|
|
Integer initial = Integer.valueOf(100); |
|
Integer interval = Integer.valueOf(100); |
|
|
|
try { |
|
initial = (Integer)t.getDesktopProperty("DnD.Autoscroll.initialDelay"); |
|
} catch (Exception e) { |
|
// ignore |
|
} |
|
|
|
try { |
|
interval = (Integer)t.getDesktopProperty("DnD.Autoscroll.interval"); |
|
} catch (Exception e) { |
|
// ignore |
|
} |
|
|
|
timer = new Timer(interval.intValue(), this); |
|
|
|
timer.setCoalesce(true); |
|
timer.setInitialDelay(initial.intValue()); |
|
|
|
locn = p; |
|
prev = p; |
|
|
|
try { |
|
hysteresis = ((Integer)t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis")).intValue(); |
|
} catch (Exception e) { |
|
// ignore |
|
} |
|
|
|
timer.start(); |
|
} |
|
|
|
/** |
|
* update the geometry of the autoscroll region |
|
*/ |
|
|
|
private void updateRegion() { |
|
Insets i = autoScroll.getAutoscrollInsets(); |
|
Dimension size = component.getSize(); |
|
|
|
if (size.width != outer.width || size.height != outer.height) |
|
outer.reshape(0, 0, size.width, size.height); |
|
|
|
if (inner.x != i.left || inner.y != i.top) |
|
inner.setLocation(i.left, i.top); |
|
|
|
int newWidth = size.width - (i.left + i.right); |
|
int newHeight = size.height - (i.top + i.bottom); |
|
|
|
if (newWidth != inner.width || newHeight != inner.height) |
|
inner.setSize(newWidth, newHeight); |
|
|
|
} |
|
|
|
/** |
|
* cause autoscroll to occur |
|
* <P> |
|
* @param newLocn the <code>Point</code> |
|
*/ |
|
|
|
protected synchronized void updateLocation(Point newLocn) { |
|
prev = locn; |
|
locn = newLocn; |
|
|
|
if (Math.abs(locn.x - prev.x) > hysteresis || |
|
Math.abs(locn.y - prev.y) > hysteresis) { |
|
if (timer.isRunning()) timer.stop(); |
|
} else { |
|
if (!timer.isRunning()) timer.start(); |
|
} |
|
} |
|
|
|
/** |
|
* cause autoscrolling to stop |
|
*/ |
|
|
|
protected void stop() { timer.stop(); } |
|
|
|
/** |
|
* cause autoscroll to occur |
|
* <P> |
|
* @param e the <code>ActionEvent</code> |
|
*/ |
|
|
|
public synchronized void actionPerformed(ActionEvent e) { |
|
updateRegion(); |
|
|
|
if (outer.contains(locn) && !inner.contains(locn)) |
|
autoScroll.autoscroll(locn); |
|
} |
|
|
|
/* |
|
* fields |
|
*/ |
|
|
|
private Component component; |
|
private Autoscroll autoScroll; |
|
|
|
private Timer timer; |
|
|
|
private Point locn; |
|
private Point prev; |
|
|
|
private Rectangle outer = new Rectangle(); |
|
private Rectangle inner = new Rectangle(); |
|
|
|
private int hysteresis = 10; |
|
} |
|
|
|
/*********************************************************************/ |
|
|
|
/** |
|
* create an embedded autoscroller |
|
* <P> |
|
* @param c the <code>Component</code> |
|
* @param p the <code>Point</code> |
|
*/ |
|
|
|
protected DropTargetAutoScroller createDropTargetAutoScroller(Component c, Point p) { |
|
return new DropTargetAutoScroller(c, p); |
|
} |
|
|
|
/** |
|
* initialize autoscrolling |
|
* <P> |
|
* @param p the <code>Point</code> |
|
*/ |
|
|
|
protected void initializeAutoscrolling(Point p) { |
|
if (component == null || !(component instanceof Autoscroll)) return; |
|
|
|
autoScroller = createDropTargetAutoScroller(component, p); |
|
} |
|
|
|
/** |
|
* update autoscrolling with current cursor location |
|
* <P> |
|
* @param dragCursorLocn the <code>Point</code> |
|
*/ |
|
|
|
protected void updateAutoscroll(Point dragCursorLocn) { |
|
if (autoScroller != null) autoScroller.updateLocation(dragCursorLocn); |
|
} |
|
|
|
/** |
|
* clear autoscrolling |
|
*/ |
|
|
|
protected void clearAutoscroll() { |
|
if (autoScroller != null) { |
|
autoScroller.stop(); |
|
autoScroller = null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private DropTargetContext dropTargetContext = createDropTargetContext(); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Component component; |
|
|
|
|
|
|
|
*/ |
|
private transient ComponentPeer componentPeer; |
|
|
|
|
|
|
|
*/ |
|
private transient ComponentPeer nativePeer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int actions = DnDConstants.ACTION_COPY_OR_MOVE; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean active = true; |
|
|
|
/* |
|
* the auto scrolling object |
|
*/ |
|
|
|
private transient DropTargetAutoScroller autoScroller; |
|
|
|
/* |
|
* The delegate |
|
*/ |
|
|
|
private transient DropTargetListener dtListener; |
|
|
|
/* |
|
* The FlavorMap |
|
*/ |
|
|
|
private transient FlavorMap flavorMap; |
|
|
|
|
|
|
|
*/ |
|
private transient boolean isDraggingInside; |
|
} |