/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE 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.
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;
/**
* Creates a new DropTarget given the <code>Component</code>
* to associate itself with, an <code>int</code> representing
* the default acceptable action(s) to
* support, a <code>DropTargetListener</code>
* to handle event processing, a <code>boolean</code> indicating
* if the <code>DropTarget</code> is currently accepting drops, and
* a <code>FlavorMap</code> to use (or null for the default <CODE>FlavorMap</CODE>).
* <P>
* The Component will receive drops only if it is enabled.
* @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
* @param ops The default acceptable actions for this <code>DropTarget</code>
* @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
* @param act Is the <code>DropTarget</code> accepting drops.
* @param fm The <code>FlavorMap</code> to use, or null for the default <CODE>FlavorMap</CODE>
* @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;
} else {
flavorMap =
SystemFlavorMap.
getDefaultFlavorMap();
}
}
/**
* Creates a <code>DropTarget</code> given the <code>Component</code>
* to associate itself with, an <code>int</code> representing
* the default acceptable action(s)
* to support, a <code>DropTargetListener</code>
* to handle event processing, and a <code>boolean</code> indicating
* if the <code>DropTarget</code> is currently accepting drops.
* <P>
* The Component will receive drops only if it is enabled.
* @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
* @param ops The default acceptable actions for this <code>DropTarget</code>
* @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
* @param act Is the <code>DropTarget</code> 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 <code>DropTarget</code>.
* @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 <code>DropTarget</code> given the <code>Component</code>
* to associate itself with, and the <code>DropTargetListener</code>
* to handle event processing.
* <P>
* The Component will receive drops only if it is enabled.
* @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
* @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
* @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 <code>DropTarget</code> given the <code>Component</code>
* to associate itself with, an <code>int</code> representing
* the default acceptable action(s) to support, and a
* <code>DropTargetListener</code> to handle event processing.
* <P>
* The Component will receive drops only if it is enabled.
* @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
* @param ops The default acceptable actions for this <code>DropTarget</code>
* @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
* @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:
* <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) { // undo the change
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));
}
/*
* Called by DropTargetContext.setTargetActions()
* with appropriate synchronization.
*/
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");
}
}
/**
* Calls <code>dragEnter</code> on the registered
* <code>DropTargetListener</code> and passes it
* the specified <code>DropTargetDragEvent</code>.
* Has no effect if this <code>DropTarget</code>
* is not active.
*
* @param dtde the <code>DropTargetDragEvent</code>
*
* @throws NullPointerException if this <code>DropTarget</code>
* is active and <code>dtde</code> is <code>null</code>
*
* @see #isActive
*/
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());
}
/**
* Calls <code>dragOver</code> on the registered
* <code>DropTargetListener</code> and passes it
* the specified <code>DropTargetDragEvent</code>.
* Has no effect if this <code>DropTarget</code>
* is not active.
*
* @param dtde the <code>DropTargetDragEvent</code>
*
* @throws NullPointerException if this <code>DropTarget</code>
* is active and <code>dtde</code> is <code>null</code>
*
* @see #isActive
*/
public synchronized void
dragOver(
DropTargetDragEvent dtde) {
if (!
active) return;
if (
dtListener != null &&
active)
dtListener.
dragOver(
dtde);
updateAutoscroll(
dtde.
getLocation());
}
/**
* Calls <code>dropActionChanged</code> on the registered
* <code>DropTargetListener</code> and passes it
* the specified <code>DropTargetDragEvent</code>.
* Has no effect if this <code>DropTarget</code>
* is not active.
*
* @param dtde the <code>DropTargetDragEvent</code>
*
* @throws NullPointerException if this <code>DropTarget</code>
* is active and <code>dtde</code> is <code>null</code>
*
* @see #isActive
*/
public synchronized void
dropActionChanged(
DropTargetDragEvent dtde) {
if (!
active) return;
if (
dtListener != null)
dtListener.
dropActionChanged(
dtde);
updateAutoscroll(
dtde.
getLocation());
}
/**
* Calls <code>dragExit</code> on the registered
* <code>DropTargetListener</code> and passes it
* the specified <code>DropTargetEvent</code>.
* Has no effect if this <code>DropTarget</code>
* is not active.
* <p>
* 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 <code>DropTargetEvent</code>
*
* @see #isActive
*/
public synchronized void
dragExit(
DropTargetEvent dte) {
isDraggingInside = false;
if (!
active) return;
if (
dtListener != null &&
active)
dtListener.
dragExit(
dte);
clearAutoscroll();
}
/**
* Calls <code>drop</code> on the registered
* <code>DropTargetListener</code> and passes it
* the specified <code>DropTargetDropEvent</code>
* if this <code>DropTarget</code> is active.
*
* @param dtde the <code>DropTargetDropEvent</code>
*
* @throws NullPointerException if <code>dtde</code> is null
* and at least one of the following is true: this
* <code>DropTarget</code> is not active, or there is
* no a <code>DropTargetListener</code> registered.
*
* @see #isActive
*/
public synchronized void
drop(
DropTargetDropEvent dtde) {
isDraggingInside = false;
clearAutoscroll();
if (
dtListener != null &&
active)
dtListener.
drop(
dtde);
else { // we should'nt get here ...
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);
}
/**
* Serializes this <code>DropTarget</code>. Performs default serialization,
* and then writes out this object's <code>DropTargetListener</code> if and
* only if it can be serialized. If not, <code>null</code> is written
* instead.
*
* @serialData The default serializable fields, in alphabetical order,
* followed by either a <code>DropTargetListener</code>
* instance, or <code>null</code>.
* @since 1.4
*/
private void
writeObject(
ObjectOutputStream s) throws
IOException {
s.
defaultWriteObject();
s.
writeObject(
SerializationTester.
test(
dtListener)
?
dtListener : null);
}
/**
* Deserializes this <code>DropTarget</code>. This method first performs
* default deserialization for all non-<code>transient</code> fields. An
* attempt is then made to deserialize this object's
* <code>DropTargetListener</code> as well. This is first attempted by
* deserializing the field <code>dtListener</code>, because, in releases
* prior to 1.4, a non-<code>transient</code> field of this name stored the
* <code>DropTargetListener</code>. 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 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);
// 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
* <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;
}
}
/**
* 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;
/**
* <code>true</code> 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;
/*
* If the dragging is currently inside this drop target
*/
private transient boolean
isDraggingInside;
}