/*
* @(#)DropTarget.java 1.48 03/12/19
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
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.AWTEvent;
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 DropTarget
is associated
* with a Component
when that Component
* wishes
* to accept drops during Drag and Drop operations.
*
* Each
* DropTarget
is associated with a FlavorMap
.
* The default FlavorMap
hereafter designates the
* FlavorMap
returned by SystemFlavorMap.getDefaultFlavorMap()
.
*
* @version 1.48, 12/19/03
* @since 1.2
*/
public class DropTarget implements DropTargetListener, Serializable {
private static final long serialVersionUID = -6283860791671019047L;
/**
* Creates a new DropTarget given the Component
* to associate itself with, an int
representing
* the default acceptable action(s) to
* support, a DropTargetListener
* to handle event processing, a boolean
indicating
* if the DropTarget
is currently accepting drops, and
* a FlavorMap
to use (or null for the default FlavorMap
).
*
* The Component will receive drops only if it is enabled.
* @param c The Component
with which this DropTarget
is associated
* @param ops The default acceptable actions for this DropTarget
* @param dtl The DropTargetListener
for this DropTarget
* @param act Is the DropTarget
accepting drops.
* @param fm The FlavorMap
to use, or null for the default FlavorMap
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true
* @see java.awt.GraphicsEnvironment#isHeadless
*/
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;
}
/**
* Creates a DropTarget
given the Component
* to associate itself with, an int
representing
* the default acceptable action(s)
* to support, a DropTargetListener
* to handle event processing, and a boolean
indicating
* if the DropTarget
is currently accepting drops.
*
* The Component will receive drops only if it is enabled.
* @param c The Component
with which this DropTarget
is associated
* @param ops The default acceptable actions for this DropTarget
* @param dtl The DropTargetListener
for this DropTarget
* @param act Is the DropTarget
accepting drops.
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true
* @see java.awt.GraphicsEnvironment#isHeadless
*/
public DropTarget(Component c, int ops, DropTargetListener dtl,
boolean act)
throws HeadlessException
{
this(c, ops, dtl, act, null);
}
/**
* Creates a DropTarget
.
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true
* @see java.awt.GraphicsEnvironment#isHeadless
*/
public DropTarget() throws HeadlessException {
this(null, DnDConstants.ACTION_COPY_OR_MOVE, null, true, null);
}
/**
* Creates a DropTarget
given the Component
* to associate itself with, and the DropTargetListener
* to handle event processing.
*
* The Component will receive drops only if it is enabled.
* @param c The Component
with which this DropTarget
is associated
* @param dtl The DropTargetListener
for this DropTarget
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true
* @see java.awt.GraphicsEnvironment#isHeadless
*/
public DropTarget(Component c, DropTargetListener dtl)
throws HeadlessException
{
this(c, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true, null);
}
/**
* Creates a DropTarget
given the Component
* to associate itself with, an int
representing
* the default acceptable action(s) to support, and a
* DropTargetListener
to handle event processing.
*
* The Component will receive drops only if it is enabled.
* @param c The Component
with which this DropTarget
is associated
* @param ops The default acceptable actions for this DropTarget
* @param dtl The DropTargetListener
for this DropTarget
* @exception HeadlessException if GraphicsEnvironment.isHeadless()
* returns true
* @see java.awt.GraphicsEnvironment#isHeadless
*/
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:
* component.setDropTarget(droptarget);
* or droptarget.setComponent(component);
*
* The Component will receive drops only if it is enabled.
* @param c The new Component
this DropTarget
* 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) { // undo the change
if (old != null) {
old.setDropTarget(this);
addNotify(oldPeer);
}
}
}
/**
* Gets the Component
associated
* with this DropTarget
.
*
* @return the current Component
*/
public synchronized Component getComponent() {
return component;
}
/**
* Sets the default acceptable actions for this DropTarget
*
* @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));
}
/*
* Called by DropTargetContext.setTargetActions()
* with appropriate synchronization.
*/
void doSetDefaultActions(int ops) {
actions = ops;
}
/**
* Gets an int
representing the
* current action(s) supported by this DropTarget
.
*
* @return the current default actions
*/
public int getDefaultActions() {
return actions;
}
/**
* Sets the DropTarget active if true
,
* inactive if false
.
*
* @param isActive sets the DropTarget
(in)active.
*/
public synchronized void setActive(boolean isActive) {
if (isActive != active) {
active = isActive;
}
if (!active) clearAutoscroll();
}
/**
* Reports whether or not
* this DropTarget
* is currently active (ready to accept drops).
*
* @return true
if active, false
if not
*/
public boolean isActive() {
return active;
}
/**
* Adds a new DropTargetListener
(UNICAST SOURCE).
*
* @param dtl The new DropTargetListener
*
* @throws TooManyListenersException
if a
* DropTargetListener
is already added to this
* DropTarget
.
*/
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 DropTargetListener
(UNICAST SOURCE).
*
* @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");
}
}
/**
* Calls dragEnter
on the registered
* DropTargetListener
and passes it
* the specified DropTargetDragEvent
.
* Has no effect if this DropTarget
* is not active.
*
* @param dtde the DropTargetDragEvent
*
* @throws NullPointerException if this DropTarget
* is active and dtde
is null
*
* @see #isActive
*/
public synchronized void dragEnter(DropTargetDragEvent dtde) {
if (!active) return;
if (dtListener != null) {
dtListener.dragEnter(dtde);
} else
dtde.getDropTargetContext().setTargetActions(DnDConstants.ACTION_NONE);
initializeAutoscrolling(dtde.getLocation());
}
/**
* Calls dragOver
on the registered
* DropTargetListener
and passes it
* the specified DropTargetDragEvent
.
* Has no effect if this DropTarget
* is not active.
*
* @param dtde the DropTargetDragEvent
*
* @throws NullPointerException if this DropTarget
* is active and dtde
is null
*
* @see #isActive
*/
public synchronized void dragOver(DropTargetDragEvent dtde) {
if (!active) return;
if (dtListener != null && active) dtListener.dragOver(dtde);
updateAutoscroll(dtde.getLocation());
}
/**
* Calls dropActionChanged
on the registered
* DropTargetListener
and passes it
* the specified DropTargetDragEvent
.
* Has no effect if this DropTarget
* is not active.
*
* @param dtde the DropTargetDragEvent
*
* @throws NullPointerException if this DropTarget
* is active and dtde
is null
*
* @see #isActive
*/
public synchronized void dropActionChanged(DropTargetDragEvent dtde) {
if (!active) return;
if (dtListener != null) dtListener.dropActionChanged(dtde);
updateAutoscroll(dtde.getLocation());
}
/**
* Calls dragExit
on the registered
* DropTargetListener
and passes it
* the specified DropTargetEvent
.
* Has no effect if this DropTarget
* is not active.
*
* This method itself does not throw any exception
* for null parameter but for exceptions thrown by
* the respective method of the listener.
*
* @param dte the DropTargetEvent
*
* @see #isActive
*/
public synchronized void dragExit(DropTargetEvent dte) {
if (!active) return;
if (dtListener != null && active) dtListener.dragExit(dte);
clearAutoscroll();
}
/**
* Calls drop
on the registered
* DropTargetListener
and passes it
* the specified DropTargetDropEvent
* if this DropTarget
is active.
*
* @param dtde the DropTargetDropEvent
*
* @throws NullPointerException if dtde
is null
* and at least one of the following is true: this
* DropTarget
is not active, or there is
* no a DropTargetListener
registered.
*
* @see #isActive
*/
public synchronized void drop(DropTargetDropEvent dtde) {
clearAutoscroll();
if (dtListener != null && active)
dtListener.drop(dtde);
else { // we should'nt get here ...
dtde.rejectDrop();
}
}
/**
* Gets the FlavorMap
* associated with this DropTarget
.
* If no FlavorMap
has been set for this
* DropTarget
, it is associated with the default
* FlavorMap
.
*
* @return the FlavorMap for this DropTarget
*/
public FlavorMap getFlavorMap() { return flavorMap; }
/**
* Sets the FlavorMap
associated
* with this DropTarget
.
*
* @param fm the new FlavorMap
, 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.
**********************************************************************
*
* @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. ********************************************************************** *
* @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;
}
/**
* Gets the DropTargetContext
associated
* with this DropTarget
.
*
* @return the DropTargetContext
associated with this DropTarget
.
*/
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);
}
/**
* Serializes this DropTarget
. Performs default serialization,
* and then writes out this object's DropTargetListener
if and
* only if it can be serialized. If not, null
is written
* instead.
*
* @serialData The default serializable fields, in alphabetical order,
* followed by either a DropTargetListener
* instance, or null
.
* @since 1.4
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject(SerializationTester.test(dtListener)
? dtListener : null);
}
/**
* Deserializes this DropTarget
. This method first performs
* default deserialization for all non-transient
fields. An
* attempt is then made to deserialize this object's
* DropTargetListener
as well. This is first attempted by
* deserializing the field dtListener
, because, in releases
* prior to 1.4, a non-transient
field of this name stored the
* DropTargetListener
. If this fails, the next object in the
* stream is used instead.
*
* @since 1.4
*/
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 previoulsy 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);
// Pre-1.4 support. 'dtListener' was previously non-transient
try {
dtListener = (DropTargetListener)f.get("dtListener", null);
} catch (IllegalArgumentException e) {
// 1.4-compatible byte stream. 'dtListener' was written explicitly
dtListener = (DropTargetListener)s.readObject();
}
}
/*********************************************************************/
/**
* this protected nested class implements autoscrolling
*/
protected static class DropTargetAutoScroller implements ActionListener {
/**
* construct a DropTargetAutoScroller
*
* @param c the Component
* @param p the Point
*/
protected DropTargetAutoScroller(Component c, Point p) {
super();
component = c;
autoScroll = (Autoscroll)component;
Toolkit t = Toolkit.getDefaultToolkit();
Integer initial = new Integer(100);
Integer interval = new Integer(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
*
* @param newLocn the Point
*/
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
*
* @param e the ActionEvent
*/
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
*
* @param c the Component
* @param p the Point
*/
protected DropTargetAutoScroller createDropTargetAutoScroller(Component c, Point p) {
return new DropTargetAutoScroller(c, p);
}
/**
* initialize autoscrolling
*
* @param p the Point
*/
protected void initializeAutoscrolling(Point p) {
if (component == null || !(component instanceof Autoscroll)) return;
autoScroller = createDropTargetAutoScroller(component, p);
}
/**
* update autoscrolling with current cursor locn
*
* @param dragCursorLocn the Point
*/
protected void updateAutoscroll(Point dragCursorLocn) {
if (autoScroller != null) autoScroller.updateLocation(dragCursorLocn);
}
/**
* clear autoscrolling
*/
protected void clearAutoscroll() {
if (autoScroller != null) {
autoScroller.stop();
autoScroller = null;
}
}
/**
* The DropTargetContext associated with this DropTarget.
*
* @serial
*/
private DropTargetContext dropTargetContext = createDropTargetContext();
/**
* The Component associated with this DropTarget.
*
* @serial
*/
private Component component;
/*
* That Component's Peer
*/
private transient ComponentPeer componentPeer;
/*
* That Component's "native" Peer
*/
private transient ComponentPeer nativePeer;
/**
* Default permissible actions supported by this DropTarget.
*
* @see #setDefaultActions
* @see #getDefaultActions
* @serial
*/
int actions = DnDConstants.ACTION_COPY_OR_MOVE;
/**
* true
if the DropTarget is accepting Drag & Drop operations.
*
* @serial
*/
boolean active = true;
/*
* the auto scrolling object
*/
private transient DropTargetAutoScroller autoScroller;
/*
* The delegate
*/
private transient DropTargetListener dtListener;
/*
* The FlavorMap
*/
private transient FlavorMap flavorMap = SystemFlavorMap.getDefaultFlavorMap();
}