/*
* @(#)TransferHandler.java 1.30 05/08/26
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.swing;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.beans.*;
import java.lang.reflect.*;
import java.io.*;
import java.util.TooManyListenersException;
import javax.swing.plaf.UIResource;
import javax.swing.event.*;
import com.sun.java.swing.SwingUtilities2;
import sun.reflect.misc.MethodUtil;
/**
* This class is used to handle the transfer of a Transferable
* to and from Swing components. The Transferable
is used to
* represent data that is exchanged via a cut, copy, or paste
* to/from a clipboard. It is also used in drag-and-drop operations
* to represent a drag from a component, and a drop to a component.
* Swing provides functionality that automatically supports cut, copy,
* and paste keyboard bindings that use the functionality provided by
* an implementation of this class. Swing also provides functionality
* that automatically supports drag and drop that uses the functionality
* provided by an implementation of this class. The Swing developer can
* concentrate on specifying the semantics of a transfer primarily by setting
* the transferHandler
property on a Swing component.
*
* This class is implemented to provide a default behavior of transferring
* a component property simply by specifying the name of the property in
* the constructor. For example, to transfer the foreground color from
* one component to another either via the clipboard or a drag and drop operation
* a TransferHandler
can be constructed with the string "foreground". The
* built in support will use the color returned by getForeground
as the source
* of the transfer, and setForeground
for the target of a transfer.
*
* Please see
*
* How to Use Drag and Drop and Data Transfer,
* a section in The Java Tutorial, for more information.
*
*
* @author Timothy Prinzing
* @version 1.30 08/26/05
* @since 1.4
*/
public class TransferHandler implements Serializable {
/**
* An int
representing no transfer action.
*/
public static final int NONE = DnDConstants.ACTION_NONE;
/**
* An int
representing a "copy" transfer action.
* This value is used when data is copied to a clipboard
* or copied elsewhere in a drag and drop operation.
*/
public static final int COPY = DnDConstants.ACTION_COPY;
/**
* An int
representing a "move" transfer action.
* This value is used when data is moved to a clipboard (i.e. a cut)
* or moved elsewhere in a drag and drop operation.
*/
public static final int MOVE = DnDConstants.ACTION_MOVE;
/**
* An int
representing a source action capability of either
* "copy" or "move".
*/
public static final int COPY_OR_MOVE = DnDConstants.ACTION_COPY_OR_MOVE;
/**
* An int
representing a "link" transfer action.
* This value is used to specify that data should be linked in a drag
* and drop operation.
*/
private static final int LINK = DnDConstants.ACTION_LINK;
/**
* Returns an Action
that behaves like a 'cut' operation.
* That is, this will invoke exportToClipboard
with
* a MOVE
argument on the TransferHandler
* associated with the JComponent
that is the source of
* the ActionEvent
.
*
* @return cut Action
*/
public static Action getCutAction() {
return cutAction;
}
/**
* Returns an Action
that behaves like a 'copy' operation.
* That is, this will invoke exportToClipboard
with
* a COPY
argument on the TransferHandler
* associated with the JComponent
that is the source of
* the ActionEvent
.
*
* @return cut Action
*/
public static Action getCopyAction() {
return copyAction;
}
/**
* Returns an Action
that behaves like a 'paste' operation.
* That is, this will invoke importData
on the
* TransferHandler
* associated with the JComponent
that is the source of
* the ActionEvent
.
*
* @return cut Action
*/
public static Action getPasteAction() {
return pasteAction;
}
/**
* Constructs a transfer handler that can transfer a Java Bean property
* from one component to another via the clipboard or a drag and drop
* operation.
*
* @param property the name of the property to transfer; this can
* be null
if there is no property associated with the transfer
* handler (a subclass that performs some other kind of transfer, for example)
*/
public TransferHandler(String property) {
propertyName = property;
}
/**
* Convenience constructor for subclasses.
*/
protected TransferHandler() {
this(null);
}
/**
* Causes the Swing drag support to be initiated. This is called by
* the various UI implementations in the javax.swing.plaf.basic
* package if the dragEnabled property is set on the component.
* This can be called by custom UI
* implementations to use the Swing drag support. This method can also be called
* by a Swing extension written as a subclass of JComponent
* to take advantage of the Swing drag support.
*
* The transfer will not necessarily have been completed at the
* return of this call (i.e. the call does not block waiting for the drop).
* The transfer will take place through the Swing implementation of the
* java.awt.dnd
mechanism, requiring no further effort
* from the developer. The exportDone
method will be called
* when the transfer has completed.
*
* @param comp the component holding the data to be transferred; this
* argument is provided to enable sharing of TransferHandler
s by
* multiple components
* @param e the event that triggered the transfer
* @param action the transfer action initially requested; this should
* be a value of either COPY
or MOVE
;
* the value may be changed during the course of the drag operation
*/
public void exportAsDrag(JComponent comp, InputEvent e, int action) {
int srcActions = getSourceActions(comp);
int dragAction = srcActions & action;
if (! (e instanceof MouseEvent)) {
// only mouse events supported for drag operations
dragAction = NONE;
}
if (dragAction != NONE && !GraphicsEnvironment.isHeadless()) {
if (recognizer == null) {
recognizer = new SwingDragGestureRecognizer(new DragHandler());
}
recognizer.gestured(comp, (MouseEvent)e, srcActions, dragAction);
} else {
exportDone(comp, null, NONE);
}
}
/**
* Causes a transfer from the given component to the
* given clipboard. This method is called by the default cut and
* copy actions registered in a component's action map.
*
* The transfer will take place using the java.awt.datatransfer
* mechanism, requiring no further effort from the developer. Any data
* transfer will be complete and the exportDone
* method will be called with the action that occurred, before this method
* returns. Should the clipboard be unavailable when attempting to place
* data on it, the IllegalStateException
thrown by
* {@link Clipboard#setContents(Transferable, ClipboardOwner)} will
* be propogated through this method. However,
* exportDone
will first be called with an action
* of NONE
for consistency.
*
* @param comp the component holding the data to be transferred; this
* argument is provided to enable sharing of TransferHandler
s by
* multiple components
* @param clip the clipboard to transfer the data into
* @param action the transfer action requested; this should
* be a value of either COPY
or MOVE
;
* the operation performed is the intersection of the transfer
* capabilities given by getSourceActions and the requested action;
* the intersection may result in an action of NONE
* if the requested action isn't supported
* @throws IllegalStateException if the clipboard is currently unavailable
* @see Clipboard#setContents(Transferable, ClipboardOwner)
*/
public void exportToClipboard(JComponent comp, Clipboard clip, int action)
throws IllegalStateException {
int clipboardAction = getSourceActions(comp) & action;
if (clipboardAction != NONE) {
Transferable t = createTransferable(comp);
if (t != null) {
try {
clip.setContents(t, null);
exportDone(comp, t, clipboardAction);
return;
} catch (IllegalStateException ise) {
exportDone(comp, t, NONE);
throw ise;
}
}
}
exportDone(comp, null, NONE);
}
/**
* Causes a transfer to a component from a clipboard or a
* DND drop operation. The Transferable
represents
* the data to be imported into the component.
*
* @param comp the component to receive the transfer; this
* argument is provided to enable sharing of TransferHandler
s
* by multiple components
* @param t the data to import
* @return true if the data was inserted into the component, false otherwise
*/
public boolean importData(JComponent comp, Transferable t) {
PropertyDescriptor prop = getPropertyDescriptor(comp);
if (prop != null) {
Method writer = prop.getWriteMethod();
if (writer == null) {
// read-only property. ignore
return false;
}
Class[] params = writer.getParameterTypes();
if (params.length != 1) {
// zero or more than one argument, ignore
return false;
}
DataFlavor flavor = getPropertyDataFlavor(params[0], t.getTransferDataFlavors());
if (flavor != null) {
try {
Object value = t.getTransferData(flavor);
Object[] args = { value };
MethodUtil.invoke(writer, comp, args);
return true;
} catch (Exception ex) {
System.err.println("Invocation failed");
// invocation code
}
}
}
return false;
}
/**
* Indicates whether a component would accept an import of the given
* set of data flavors prior to actually attempting to import it.
*
* @param comp the component to receive the transfer; this
* argument is provided to enable sharing of TransferHandlers
* by multiple components
* @param transferFlavors the data formats available
* @return true if the data can be inserted into the component, false otherwise
*/
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
PropertyDescriptor prop = getPropertyDescriptor(comp);
if (prop != null) {
Method writer = prop.getWriteMethod();
if (writer == null) {
// read-only property. ignore
return false;
}
Class[] params = writer.getParameterTypes();
if (params.length != 1) {
// zero or more than one argument, ignore
return false;
}
DataFlavor flavor = getPropertyDataFlavor(params[0], transferFlavors);
if (flavor != null) {
return true;
}
}
return false;
}
/**
* Returns the type of transfer actions supported by the source.
* Some models are not mutable, so a transfer operation of COPY
* only should be advertised in that case.
*
* @param c the component holding the data to be transferred; this
* argument is provided to enable sharing of TransferHandler
s
* by multiple components.
* @return COPY
if the transfer property can be found,
* otherwise returns NONE
; a return value of
* of NONE
disables any transfers out of the component
*/
public int getSourceActions(JComponent c) {
PropertyDescriptor prop = getPropertyDescriptor(c);
if (prop != null) {
return COPY;
}
return NONE;
}
/**
* Returns an object that establishes the look of a transfer. This is
* useful for both providing feedback while performing a drag operation and for
* representing the transfer in a clipboard implementation that has a visual
* appearance. The implementation of the Icon
interface should
* not alter the graphics clip or alpha level.
* The icon implementation need not be rectangular or paint all of the
* bounding rectangle and logic that calls the icons paint method should
* not assume the all bits are painted. null
is a valid return value
* for this method and indicates there is no visual representation provided.
* In that case, the calling logic is free to represent the
* transferable however it wants.
*
* The default Swing logic will not do an alpha blended drag animation if
* the return is null
.
*
* @param t the data to be transferred; this value is expected to have been
* created by the createTransferable
method
* @return null
, indicating
* there is no default visual representation
*/
public Icon getVisualRepresentation(Transferable t) {
return null;
}
/**
* Creates a Transferable
to use as the source for
* a data transfer. Returns the representation of the data to
* be transferred, or null
if the component's
* property is null
*
* @param c the component holding the data to be transferred; this
* argument is provided to enable sharing of TransferHandler
s
* by multiple components
* @return the representation of the data to be transferred, or
* null
if the property associated with c
* is null
*
*/
protected Transferable createTransferable(JComponent c) {
PropertyDescriptor property = getPropertyDescriptor(c);
if (property != null) {
return new PropertyTransferable(property, c);
}
return null;
}
/**
* Invoked after data has been exported. This method should remove
* the data that was transferred if the action was MOVE
.
*
* This method is implemented to do nothing since MOVE
* is not a supported action of this implementation
* (getSourceActions
does not include MOVE
).
*
* @param source the component that was the source of the data
* @param data The data that was transferred or possibly null
* if the action is NONE
.
* @param action the actual action that was performed
*/
protected void exportDone(JComponent source, Transferable data, int action) {
}
/**
* Fetches the property descriptor for the property assigned to this transfer
* handler on the given component (transfer handler may be shared). This
* returns null
if the property descriptor can't be found
* or there is an error attempting to fetch the property descriptor.
*/
private PropertyDescriptor getPropertyDescriptor(JComponent comp) {
if (propertyName == null) {
return null;
}
Class k = comp.getClass();
BeanInfo bi;
try {
bi = Introspector.getBeanInfo(k);
} catch (IntrospectionException ex) {
return null;
}
PropertyDescriptor props[] = bi.getPropertyDescriptors();
for (int i=0; i < props.length; i++) {
if (propertyName.equals(props[i].getName())) {
Method reader = props[i].getReadMethod();
if (reader != null) {
Class[] params = reader.getParameterTypes();
if (params == null || params.length == 0) {
// found the desired descriptor
return props[i];
}
}
}
}
return null;
}
/**
* Fetches the data flavor from the array of possible flavors that
* has data of the type represented by property type. Null is
* returned if there is no match.
*/
private DataFlavor getPropertyDataFlavor(Class k, DataFlavor[] flavors) {
for(int i = 0; i < flavors.length; i++) {
DataFlavor flavor = flavors[i];
if ("application".equals(flavor.getPrimaryType()) &&
"x-java-jvm-local-objectref".equals(flavor.getSubType()) &&
k.isAssignableFrom(flavor.getRepresentationClass())) {
return flavor;
}
}
return null;
}
private String propertyName;
private static SwingDragGestureRecognizer recognizer = null;
private static DropTargetListener dropLinkage = null;
private static DropTargetListener getDropTargetListener() {
if (dropLinkage == null) {
dropLinkage = new DropHandler();
}
return dropLinkage;
}
static class PropertyTransferable implements Transferable {
PropertyTransferable(PropertyDescriptor p, JComponent c) {
property = p;
component = c;
}
// --- Transferable methods ----------------------------------------------
/**
* Returns an array of DataFlavor
objects indicating the flavors the data
* can be provided in. The array should be ordered according to preference
* for providing the data (from most richly descriptive to least descriptive).
* @return an array of data flavors in which this data can be transferred
*/
public DataFlavor[] getTransferDataFlavors() {
DataFlavor[] flavors = new DataFlavor[1];
Class propertyType = property.getPropertyType();
String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=" + propertyType.getName();
try {
flavors[0] = new DataFlavor(mimeType);
} catch (ClassNotFoundException cnfe) {
flavors = new DataFlavor[0];
}
return flavors;
}
/**
* Returns whether the specified data flavor is supported for
* this object.
* @param flavor the requested flavor for the data
* @return true if this DataFlavor
is supported,
* otherwise false
*/
public boolean isDataFlavorSupported(DataFlavor flavor) {
Class propertyType = property.getPropertyType();
if ("application".equals(flavor.getPrimaryType()) &&
"x-java-jvm-local-objectref".equals(flavor.getSubType()) &&
flavor.getRepresentationClass().isAssignableFrom(propertyType)) {
return true;
}
return false;
}
/**
* Returns an object which represents the data to be transferred. The class
* of the object returned is defined by the representation class of the flavor.
*
* @param flavor the requested flavor for the data
* @see DataFlavor#getRepresentationClass
* @exception IOException if the data is no longer available
* in the requested flavor.
* @exception UnsupportedFlavorException if the requested data flavor is
* not supported.
*/
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (! isDataFlavorSupported(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
Method reader = property.getReadMethod();
Object value = null;
try {
value = MethodUtil.invoke(reader, component, null);
} catch (Exception ex) {
throw new IOException("Property read failed: " + property.getName());
}
return value;
}
JComponent component;
PropertyDescriptor property;
}
/**
* This is the default drop target for drag and drop operations if
* one isn't provided by the developer. DropTarget
* only supports one DropTargetListener
and doesn't
* function properly if it isn't set.
* This class sets the one listener as the linkage of drop handling
* to the TransferHandler
, and adds support for
* additional listeners which some of the ComponentUI
* implementations install to manipulate a drop insertion location.
*/
static class SwingDropTarget extends DropTarget implements UIResource {
SwingDropTarget(JComponent c) {
super();
setComponent(c);
try {
super.addDropTargetListener(getDropTargetListener());
} catch (TooManyListenersException tmle) {}
}
public void addDropTargetListener(DropTargetListener dtl) throws TooManyListenersException {
// Since the super class only supports one DropTargetListener,
// and we add one from the constructor, we always add to the
// extended list.
if (listenerList == null) {
listenerList = new EventListenerList();
}
listenerList.add(DropTargetListener.class, dtl);
}
public void removeDropTargetListener(DropTargetListener dtl) {
if (listenerList != null) {
listenerList.remove(DropTargetListener.class, dtl);
}
}
// --- DropTargetListener methods (multicast) --------------------------
public void dragEnter(DropTargetDragEvent e) {
super.dragEnter(e);
if (listenerList != null) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==DropTargetListener.class) {
((DropTargetListener)listeners[i+1]).dragEnter(e);
}
}
}
}
public void dragOver(DropTargetDragEvent e) {
super.dragOver(e);
if (listenerList != null) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==DropTargetListener.class) {
((DropTargetListener)listeners[i+1]).dragOver(e);
}
}
}
}
public void dragExit(DropTargetEvent e) {
super.dragExit(e);
if (listenerList != null) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==DropTargetListener.class) {
((DropTargetListener)listeners[i+1]).dragExit(e);
}
}
}
}
public void drop(DropTargetDropEvent e) {
super.drop(e);
if (listenerList != null) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==DropTargetListener.class) {
((DropTargetListener)listeners[i+1]).drop(e);
}
}
}
}
public void dropActionChanged(DropTargetDragEvent e) {
super.dropActionChanged(e);
if (listenerList != null) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==DropTargetListener.class) {
((DropTargetListener)listeners[i+1]).dropActionChanged(e);
}
}
}
}
private EventListenerList listenerList;
}
private static class DropHandler implements DropTargetListener, Serializable {
private boolean canImport;
private boolean actionSupported(int action) {
return (action & (COPY_OR_MOVE | LINK)) != NONE;
}
// --- DropTargetListener methods -----------------------------------
public void dragEnter(DropTargetDragEvent e) {
DataFlavor[] flavors = e.getCurrentDataFlavors();
JComponent c = (JComponent)e.getDropTargetContext().getComponent();
TransferHandler importer = c.getTransferHandler();
if (importer != null && importer.canImport(c, flavors)) {
canImport = true;
} else {
canImport = false;
}
int dropAction = e.getDropAction();
if (canImport && actionSupported(dropAction)) {
e.acceptDrag(dropAction);
} else {
e.rejectDrag();
}
}
public void dragOver(DropTargetDragEvent e) {
int dropAction = e.getDropAction();
if (canImport && actionSupported(dropAction)) {
e.acceptDrag(dropAction);
} else {
e.rejectDrag();
}
}
public void dragExit(DropTargetEvent e) {
}
public void drop(DropTargetDropEvent e) {
int dropAction = e.getDropAction();
JComponent c = (JComponent)e.getDropTargetContext().getComponent();
TransferHandler importer = c.getTransferHandler();
if (canImport && importer != null && actionSupported(dropAction)) {
e.acceptDrop(dropAction);
try {
Transferable t = e.getTransferable();
e.dropComplete(importer.importData(c, t));
} catch (RuntimeException re) {
e.dropComplete(false);
}
} else {
e.rejectDrop();
}
}
public void dropActionChanged(DropTargetDragEvent e) {
int dropAction = e.getDropAction();
if (canImport && actionSupported(dropAction)) {
e.acceptDrag(dropAction);
} else {
e.rejectDrag();
}
}
}
/**
* This is the default drag handler for drag and drop operations that
* use the TransferHandler
.
*/
private static class DragHandler implements DragGestureListener, DragSourceListener {
private boolean scrolls;
// --- DragGestureListener methods -----------------------------------
/**
* a Drag gesture has been recognized
*/
public void dragGestureRecognized(DragGestureEvent dge) {
JComponent c = (JComponent) dge.getComponent();
TransferHandler th = c.getTransferHandler();
Transferable t = th.createTransferable(c);
if (t != null) {
scrolls = c.getAutoscrolls();
c.setAutoscrolls(false);
try {
dge.startDrag(null, t, this);
return;
} catch (RuntimeException re) {
c.setAutoscrolls(scrolls);
}
}
th.exportDone(c, t, NONE);
}
// --- DragSourceListener methods -----------------------------------
/**
* as the hotspot enters a platform dependent drop site
*/
public void dragEnter(DragSourceDragEvent dsde) {
}
/**
* as the hotspot moves over a platform dependent drop site
*/
public void dragOver(DragSourceDragEvent dsde) {
}
/**
* as the hotspot exits a platform dependent drop site
*/
public void dragExit(DragSourceEvent dsde) {
}
/**
* as the operation completes
*/
public void dragDropEnd(DragSourceDropEvent dsde) {
DragSourceContext dsc = dsde.getDragSourceContext();
JComponent c = (JComponent)dsc.getComponent();
if (dsde.getDropSuccess()) {
c.getTransferHandler().exportDone(c, dsc.getTransferable(), dsde.getDropAction());
} else {
c.getTransferHandler().exportDone(c, dsc.getTransferable(), NONE);
}
c.setAutoscrolls(scrolls);
}
public void dropActionChanged(DragSourceDragEvent dsde) {
}
}
private static class SwingDragGestureRecognizer extends DragGestureRecognizer {
SwingDragGestureRecognizer(DragGestureListener dgl) {
super(DragSource.getDefaultDragSource(), null, NONE, dgl);
}
void gestured(JComponent c, MouseEvent e, int srcActions, int action) {
setComponent(c);
setSourceActions(srcActions);
appendEvent(e);
fireDragGestureRecognized(action, e.getPoint());
}
/**
* register this DragGestureRecognizer's Listeners with the Component
*/
protected void registerListeners() {
}
/**
* unregister this DragGestureRecognizer's Listeners with the Component
*
* subclasses must override this method
*/
protected void unregisterListeners() {
}
}
static final Action cutAction = new TransferAction("cut");
static final Action copyAction = new TransferAction("copy");
static final Action pasteAction = new TransferAction("paste");
static class TransferAction extends AbstractAction implements UIResource {
TransferAction(String name) {
super(name);
}
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src instanceof JComponent) {
JComponent c = (JComponent) src;
TransferHandler th = c.getTransferHandler();
Clipboard clipboard = getClipboard(c);
String name = (String) getValue(Action.NAME);
Transferable trans = null;
// any of these calls may throw IllegalStateException
try {
if ((clipboard != null) && (th != null) && (name != null)) {
if ("cut".equals(name)) {
th.exportToClipboard(c, clipboard, MOVE);
} else if ("copy".equals(name)) {
th.exportToClipboard(c, clipboard, COPY);
} else if ("paste".equals(name)) {
trans = clipboard.getContents(null);
}
}
} catch (IllegalStateException ise) {
// clipboard was unavailable
UIManager.getLookAndFeel().provideErrorFeedback(c);
return;
}
// this is a paste action, import data into the component
if (trans != null) {
th.importData(c, trans);
}
}
}
/**
* Returns the clipboard to use for cut/copy/paste.
*/
private Clipboard getClipboard(JComponent c) {
if (SwingUtilities2.canAccessSystemClipboard()) {
return c.getToolkit().getSystemClipboard();
}
Clipboard clipboard = (Clipboard)sun.awt.AppContext.getAppContext().
get(SandboxClipboardKey);
if (clipboard == null) {
clipboard = new Clipboard("Sandboxed Component Clipboard");
sun.awt.AppContext.getAppContext().put(SandboxClipboardKey,
clipboard);
}
return clipboard;
}
/**
* Key used in app context to lookup Clipboard to use if access to
* System clipboard is denied.
*/
private static Object SandboxClipboardKey = new Object();
}
}