diff -r 836adbf7a2cd -r 3317bb8137f4 jdk/src/java.desktop/share/classes/javax/swing/TransferHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/javax/swing/TransferHandler.java Sun Aug 17 15:54:13 2014 +0100 @@ -0,0 +1,1796 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +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 javax.swing.text.JTextComponent; + +import sun.reflect.misc.MethodUtil; +import sun.swing.SwingUtilities2; +import sun.awt.AppContext; +import sun.swing.*; +import sun.awt.SunToolkit; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import java.security.AccessControlContext; +import java.security.ProtectionDomain; +import sun.misc.SharedSecrets; +import sun.misc.JavaSecurityAccess; + +import sun.awt.AWTAccessor; + +/** + * 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 + * @author Shannon Hickey + * @since 1.4 + */ +@SuppressWarnings("serial") +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. + * + * @see java.awt.dnd.DnDConstants#ACTION_LINK + * @since 1.6 + */ + public static final int LINK = DnDConstants.ACTION_LINK; + + /** + * An interface to tag things with a {@code getTransferHandler} method. + */ + interface HasGetTransferHandler { + + /** Returns the {@code TransferHandler}. + * + * @return The {@code TransferHandler} or {@code null} + */ + public TransferHandler getTransferHandler(); + } + + /** + * Represents a location where dropped data should be inserted. + * This is a base class that only encapsulates a point. + * Components supporting drop may provide subclasses of this + * containing more information. + *

+ * Developers typically shouldn't create instances of, or extend, this + * class. Instead, these are something provided by the DnD + * implementation by TransferSupport instances and by + * components with a getDropLocation() method. + * + * @see javax.swing.TransferHandler.TransferSupport#getDropLocation + * @since 1.6 + */ + public static class DropLocation { + private final Point dropPoint; + + /** + * Constructs a drop location for the given point. + * + * @param dropPoint the drop point, representing the mouse's + * current location within the component. + * @throws IllegalArgumentException if the point + * is null + */ + protected DropLocation(Point dropPoint) { + if (dropPoint == null) { + throw new IllegalArgumentException("Point cannot be null"); + } + + this.dropPoint = new Point(dropPoint); + } + + /** + * Returns the drop point, representing the mouse's + * current location within the component. + * + * @return the drop point. + */ + public final Point getDropPoint() { + return new Point(dropPoint); + } + + /** + * Returns a string representation of this drop location. + * This method is intended to be used for debugging purposes, + * and the content and format of the returned string may vary + * between implementations. + * + * @return a string representation of this drop location + */ + public String toString() { + return getClass().getName() + "[dropPoint=" + dropPoint + "]"; + } + }; + + /** + * This class encapsulates all relevant details of a clipboard + * or drag and drop transfer, and also allows for customizing + * aspects of the drag and drop experience. + *

+ * The main purpose of this class is to provide the information + * needed by a developer to determine the suitability of a + * transfer or to import the data contained within. But it also + * doubles as a controller for customizing properties during drag + * and drop, such as whether or not to show the drop location, + * and which drop action to use. + *

+ * Developers typically need not create instances of this + * class. Instead, they are something provided by the DnD + * implementation to certain methods in TransferHandler. + * + * @see #canImport(TransferHandler.TransferSupport) + * @see #importData(TransferHandler.TransferSupport) + * @since 1.6 + */ + public final static class TransferSupport { + private boolean isDrop; + private Component component; + + private boolean showDropLocationIsSet; + private boolean showDropLocation; + + private int dropAction = -1; + + /** + * The source is a {@code DropTargetDragEvent} or + * {@code DropTargetDropEvent} for drops, + * and a {@code Transferable} otherwise + */ + private Object source; + + private DropLocation dropLocation; + + /** + * Create a TransferSupport with isDrop() + * true for the given component, event, and index. + * + * @param component the target component + * @param event a DropTargetEvent + */ + private TransferSupport(Component component, + DropTargetEvent event) { + + isDrop = true; + setDNDVariables(component, event); + } + + /** + * Create a TransferSupport with isDrop() + * false for the given component and + * Transferable. + * + * @param component the target component + * @param transferable the transferable + * @throws NullPointerException if either parameter + * is null + */ + public TransferSupport(Component component, Transferable transferable) { + if (component == null) { + throw new NullPointerException("component is null"); + } + + if (transferable == null) { + throw new NullPointerException("transferable is null"); + } + + isDrop = false; + this.component = component; + this.source = transferable; + } + + /** + * Allows for a single instance to be reused during DnD. + * + * @param component the target component + * @param event a DropTargetEvent + */ + private void setDNDVariables(Component component, + DropTargetEvent event) { + + assert isDrop; + + this.component = component; + this.source = event; + dropLocation = null; + dropAction = -1; + showDropLocationIsSet = false; + + if (source == null) { + return; + } + + assert source instanceof DropTargetDragEvent || + source instanceof DropTargetDropEvent; + + Point p = source instanceof DropTargetDragEvent + ? ((DropTargetDragEvent)source).getLocation() + : ((DropTargetDropEvent)source).getLocation(); + + if (SunToolkit.isInstanceOf(component, "javax.swing.text.JTextComponent")) { + dropLocation = SwingAccessor.getJTextComponentAccessor(). + dropLocationForPoint((JTextComponent)component, p); + } else if (component instanceof JComponent) { + dropLocation = ((JComponent)component).dropLocationForPoint(p); + } + + /* + * The drop location may be null at this point if the component + * doesn't return custom drop locations. In this case, a point-only + * drop location will be created lazily when requested. + */ + } + + /** + * Returns whether or not this TransferSupport + * represents a drop operation. + * + * @return true if this is a drop operation, + * false otherwise. + */ + public boolean isDrop() { + return isDrop; + } + + /** + * Returns the target component of this transfer. + * + * @return the target component + */ + public Component getComponent() { + return component; + } + + /** + * Checks that this is a drop and throws an + * {@code IllegalStateException} if it isn't. + * + * @throws IllegalStateException if {@code isDrop} is false. + */ + private void assureIsDrop() { + if (!isDrop) { + throw new IllegalStateException("Not a drop"); + } + } + + /** + * Returns the current (non-{@code null}) drop location for the component, + * when this {@code TransferSupport} represents a drop. + *

+ * Note: For components with built-in drop support, this location + * will be a subclass of {@code DropLocation} of the same type + * returned by that component's {@code getDropLocation} method. + *

+ * This method is only for use with drag and drop transfers. + * Calling it when {@code isDrop()} is {@code false} results + * in an {@code IllegalStateException}. + * + * @return the drop location + * @throws IllegalStateException if this is not a drop + * @see #isDrop() + */ + public DropLocation getDropLocation() { + assureIsDrop(); + + if (dropLocation == null) { + /* + * component didn't give us a custom drop location, + * so lazily create a point-only location + */ + Point p = source instanceof DropTargetDragEvent + ? ((DropTargetDragEvent)source).getLocation() + : ((DropTargetDropEvent)source).getLocation(); + + dropLocation = new DropLocation(p); + } + + return dropLocation; + } + + /** + * Sets whether or not the drop location should be visually indicated + * for the transfer - which must represent a drop. This is applicable to + * those components that automatically + * show the drop location when appropriate during a drag and drop + * operation). By default, the drop location is shown only when the + * {@code TransferHandler} has said it can accept the import represented + * by this {@code TransferSupport}. With this method you can force the + * drop location to always be shown, or always not be shown. + *

+ * This method is only for use with drag and drop transfers. + * Calling it when {@code isDrop()} is {@code false} results + * in an {@code IllegalStateException}. + * + * @param showDropLocation whether or not to indicate the drop location + * @throws IllegalStateException if this is not a drop + * @see #isDrop() + */ + public void setShowDropLocation(boolean showDropLocation) { + assureIsDrop(); + + this.showDropLocation = showDropLocation; + this.showDropLocationIsSet = true; + } + + /** + * Sets the drop action for the transfer - which must represent a drop + * - to the given action, + * instead of the default user drop action. The action must be + * supported by the source's drop actions, and must be one + * of {@code COPY}, {@code MOVE} or {@code LINK}. + *

+ * This method is only for use with drag and drop transfers. + * Calling it when {@code isDrop()} is {@code false} results + * in an {@code IllegalStateException}. + * + * @param dropAction the drop action + * @throws IllegalStateException if this is not a drop + * @throws IllegalArgumentException if an invalid action is specified + * @see #getDropAction + * @see #getUserDropAction + * @see #getSourceDropActions + * @see #isDrop() + */ + public void setDropAction(int dropAction) { + assureIsDrop(); + + int action = dropAction & getSourceDropActions(); + + if (!(action == COPY || action == MOVE || action == LINK)) { + throw new IllegalArgumentException("unsupported drop action: " + dropAction); + } + + this.dropAction = dropAction; + } + + /** + * Returns the action chosen for the drop, when this + * {@code TransferSupport} represents a drop. + *

+ * Unless explicitly chosen by way of {@code setDropAction}, + * this returns the user drop action provided by + * {@code getUserDropAction}. + *

+ * You may wish to query this in {@code TransferHandler}'s + * {@code importData} method to customize processing based + * on the action. + *

+ * This method is only for use with drag and drop transfers. + * Calling it when {@code isDrop()} is {@code false} results + * in an {@code IllegalStateException}. + * + * @return the action chosen for the drop + * @throws IllegalStateException if this is not a drop + * @see #setDropAction + * @see #getUserDropAction + * @see #isDrop() + */ + public int getDropAction() { + return dropAction == -1 ? getUserDropAction() : dropAction; + } + + /** + * Returns the user drop action for the drop, when this + * {@code TransferSupport} represents a drop. + *

+ * The user drop action is chosen for a drop as described in the + * documentation for {@link java.awt.dnd.DropTargetDragEvent} and + * {@link java.awt.dnd.DropTargetDropEvent}. A different action + * may be chosen as the drop action by way of the {@code setDropAction} + * method. + *

+ * You may wish to query this in {@code TransferHandler}'s + * {@code canImport} method when determining the suitability of a + * drop or when deciding on a drop action to explicitly choose. + *

+ * This method is only for use with drag and drop transfers. + * Calling it when {@code isDrop()} is {@code false} results + * in an {@code IllegalStateException}. + * + * @return the user drop action + * @throws IllegalStateException if this is not a drop + * @see #setDropAction + * @see #getDropAction + * @see #isDrop() + */ + public int getUserDropAction() { + assureIsDrop(); + + return (source instanceof DropTargetDragEvent) + ? ((DropTargetDragEvent)source).getDropAction() + : ((DropTargetDropEvent)source).getDropAction(); + } + + /** + * Returns the drag source's supported drop actions, when this + * {@code TransferSupport} represents a drop. + *

+ * The source actions represent the set of actions supported by the + * source of this transfer, and are represented as some bitwise-OR + * combination of {@code COPY}, {@code MOVE} and {@code LINK}. + * You may wish to query this in {@code TransferHandler}'s + * {@code canImport} method when determining the suitability of a drop + * or when deciding on a drop action to explicitly choose. To determine + * if a particular action is supported by the source, bitwise-AND + * the action with the source drop actions, and then compare the result + * against the original action. For example: + *

+         * boolean copySupported = (COPY & getSourceDropActions()) == COPY;
+         * 
+ *

+ * This method is only for use with drag and drop transfers. + * Calling it when {@code isDrop()} is {@code false} results + * in an {@code IllegalStateException}. + * + * @return the drag source's supported drop actions + * @throws IllegalStateException if this is not a drop + * @see #isDrop() + */ + public int getSourceDropActions() { + assureIsDrop(); + + return (source instanceof DropTargetDragEvent) + ? ((DropTargetDragEvent)source).getSourceActions() + : ((DropTargetDropEvent)source).getSourceActions(); + } + + /** + * Returns the data flavors for this transfer. + * + * @return the data flavors for this transfer + */ + public DataFlavor[] getDataFlavors() { + if (isDrop) { + if (source instanceof DropTargetDragEvent) { + return ((DropTargetDragEvent)source).getCurrentDataFlavors(); + } else { + return ((DropTargetDropEvent)source).getCurrentDataFlavors(); + } + } + + return ((Transferable)source).getTransferDataFlavors(); + } + + /** + * Returns whether or not the given data flavor is supported. + * + * @param df the DataFlavor to test + * @return whether or not the given flavor is supported. + */ + public boolean isDataFlavorSupported(DataFlavor df) { + if (isDrop) { + if (source instanceof DropTargetDragEvent) { + return ((DropTargetDragEvent)source).isDataFlavorSupported(df); + } else { + return ((DropTargetDropEvent)source).isDataFlavorSupported(df); + } + } + + return ((Transferable)source).isDataFlavorSupported(df); + } + + /** + * Returns the Transferable associated with this transfer. + *

+ * Note: Unless it is necessary to fetch the Transferable + * directly, use one of the other methods on this class to inquire about + * the transfer. This may perform better than fetching the + * Transferable and asking it directly. + * + * @return the Transferable associated with this transfer + */ + public Transferable getTransferable() { + if (isDrop) { + if (source instanceof DropTargetDragEvent) { + return ((DropTargetDragEvent)source).getTransferable(); + } else { + return ((DropTargetDropEvent)source).getTransferable(); + } + } + + return (Transferable)source; + } + } + + + /** + * Returns an {@code Action} that performs cut operations to the + * clipboard. When performed, this action operates on the {@code JComponent} + * source of the {@code ActionEvent} by invoking {@code exportToClipboard}, + * with a {@code MOVE} action, on the component's {@code TransferHandler}. + * + * @return an {@code Action} for performing cuts to the clipboard + */ + public static Action getCutAction() { + return cutAction; + } + + /** + * Returns an {@code Action} that performs copy operations to the + * clipboard. When performed, this action operates on the {@code JComponent} + * source of the {@code ActionEvent} by invoking {@code exportToClipboard}, + * with a {@code COPY} action, on the component's {@code TransferHandler}. + * + * @return an {@code Action} for performing copies to the clipboard + */ + public static Action getCopyAction() { + return copyAction; + } + + /** + * Returns an {@code Action} that performs paste operations from the + * clipboard. When performed, this action operates on the {@code JComponent} + * source of the {@code ActionEvent} by invoking {@code importData}, + * with the clipboard contents, on the component's {@code TransferHandler}. + * + * @return an {@code Action} for performing pastes from the clipboard + */ + 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); + } + + + /** + * image for the {@code startDrag} method + * + * @see java.awt.dnd.DragGestureEvent#startDrag(Cursor dragCursor, Image dragImage, Point imageOffset, Transferable transferable, DragSourceListener dsl) + */ + private Image dragImage; + + /** + * anchor offset for the {@code startDrag} method + * + * @see java.awt.dnd.DragGestureEvent#startDrag(Cursor dragCursor, Image dragImage, Point imageOffset, Transferable transferable, DragSourceListener dsl) + */ + private Point dragImageOffset; + + /** + * Sets the drag image parameter. The image has to be prepared + * for rendering by the moment of the call. The image is stored + * by reference because of some performance reasons. + * + * @param img an image to drag + */ + public void setDragImage(Image img) { + dragImage = img; + } + + /** + * Returns the drag image. If there is no image to drag, + * the returned value is {@code null}. + * + * @return the reference to the drag image + */ + public Image getDragImage() { + return dragImage; + } + + /** + * Sets an anchor offset for the image to drag. + * It can not be {@code null}. + * + * @param p a {@code Point} object that corresponds + * to coordinates of an anchor offset of the image + * relative to the upper left corner of the image + */ + public void setDragImageOffset(Point p) { + dragImageOffset = new Point(p); + } + + /** + * Returns an anchor offset for the image to drag. + * + * @return a {@code Point} object that corresponds + * to coordinates of an anchor offset of the image + * relative to the upper left corner of the image. + * The point {@code (0,0)} returns by default. + */ + public Point getDragImageOffset() { + if (dragImageOffset == null) { + return new Point(0,0); + } + return new Point(dragImageOffset); + } + + /** + * 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; + * provided to enable sharing of TransferHandlers + * @param e the event that triggered the transfer + * @param action the transfer action initially requested; + * either {@code COPY}, {@code MOVE} or {@code LINK}; + * the DnD system may change the action used during the + * course of the drag operation + */ + public void exportAsDrag(JComponent comp, InputEvent e, int action) { + int srcActions = getSourceActions(comp); + + // only mouse events supported for drag operations + if (!(e instanceof MouseEvent) + // only support known actions + || !(action == COPY || action == MOVE || action == LINK) + // only support valid source actions + || (srcActions & action) == 0) { + + action = NONE; + } + + if (action != NONE && !GraphicsEnvironment.isHeadless()) { + if (recognizer == null) { + recognizer = new SwingDragGestureRecognizer(new DragHandler()); + } + recognizer.gestured(comp, (MouseEvent)e, srcActions, action); + } 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 propagated 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; + * provided to enable sharing of TransferHandlers + * @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 { + + if ((action == COPY || action == MOVE) + && (getSourceActions(comp) & action) != 0) { + + Transferable t = createTransferable(comp); + if (t != null) { + try { + clip.setContents(t, null); + exportDone(comp, t, action); + return; + } catch (IllegalStateException ise) { + exportDone(comp, t, NONE); + throw ise; + } + } + } + + exportDone(comp, null, NONE); + } + + /** + * Causes a transfer to occur from a clipboard or a drag and + * drop operation. The Transferable to be + * imported and the component to transfer to are contained + * within the TransferSupport. + *

+ * While the drag and drop implementation calls {@code canImport} + * to determine the suitability of a transfer before calling this + * method, the implementation of paste does not. As such, it cannot + * be assumed that the transfer is acceptable upon a call to + * this method for paste. It is recommended that {@code canImport} be + * explicitly called to cover this case. + *

+ * Note: The TransferSupport object passed to this method + * is only valid for the duration of the method call. It is undefined + * what values it may contain after this method returns. + * + * @param support the object containing the details of + * the transfer, not null. + * @return true if the data was inserted into the component, + * false otherwise + * @throws NullPointerException if support is {@code null} + * @see #canImport(TransferHandler.TransferSupport) + * @since 1.6 + */ + public boolean importData(TransferSupport support) { + return support.getComponent() instanceof JComponent + ? importData((JComponent)support.getComponent(), support.getTransferable()) + : false; + } + + /** + * 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. + *

+ * Note: Swing now calls the newer version of importData + * that takes a TransferSupport, which in turn calls this + * method (if the component in the {@code TransferSupport} is a + * {@code JComponent}). Developers are encouraged to call and override the + * newer version as it provides more information (and is the only + * version that supports use with a {@code TransferHandler} set directly + * on a {@code JFrame} or other non-{@code JComponent}). + * + * @param comp the component to receive the transfer; + * provided to enable sharing of TransferHandlers + * @param t the data to import + * @return true if the data was inserted into the component, false otherwise + * @see #importData(TransferHandler.TransferSupport) + */ + 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; + } + + /** + * This method is called repeatedly during a drag and drop operation + * to allow the developer to configure properties of, and to return + * the acceptability of transfers; with a return value of {@code true} + * indicating that the transfer represented by the given + * {@code TransferSupport} (which contains all of the details of the + * transfer) is acceptable at the current time, and a value of {@code false} + * rejecting the transfer. + *

+ * For those components that automatically display a drop location during + * drag and drop, accepting the transfer, by default, tells them to show + * the drop location. This can be changed by calling + * {@code setShowDropLocation} on the {@code TransferSupport}. + *

+ * By default, when the transfer is accepted, the chosen drop action is that + * picked by the user via their drag gesture. The developer can override + * this and choose a different action, from the supported source + * actions, by calling {@code setDropAction} on the {@code TransferSupport}. + *

+ * On every call to {@code canImport}, the {@code TransferSupport} contains + * fresh state. As such, any properties set on it must be set on every + * call. Upon a drop, {@code canImport} is called one final time before + * calling into {@code importData}. Any state set on the + * {@code TransferSupport} during that last call will be available in + * {@code importData}. + *

+ * This method is not called internally in response to paste operations. + * As such, it is recommended that implementations of {@code importData} + * explicitly call this method for such cases and that this method + * be prepared to return the suitability of paste operations as well. + *

+ * Note: The TransferSupport object passed to this method + * is only valid for the duration of the method call. It is undefined + * what values it may contain after this method returns. + * + * @param support the object containing the details of + * the transfer, not null. + * @return true if the import can happen, + * false otherwise + * @throws NullPointerException if support is {@code null} + * @see #importData(TransferHandler.TransferSupport) + * @see javax.swing.TransferHandler.TransferSupport#setShowDropLocation + * @see javax.swing.TransferHandler.TransferSupport#setDropAction + * @since 1.6 + */ + public boolean canImport(TransferSupport support) { + return support.getComponent() instanceof JComponent + ? canImport((JComponent)support.getComponent(), support.getDataFlavors()) + : false; + } + + /** + * Indicates whether a component will accept an import of the given + * set of data flavors prior to actually attempting to import it. + *

+ * Note: Swing now calls the newer version of canImport + * that takes a TransferSupport, which in turn calls this + * method (only if the component in the {@code TransferSupport} is a + * {@code JComponent}). Developers are encouraged to call and override the + * newer version as it provides more information (and is the only + * version that supports use with a {@code TransferHandler} set directly + * on a {@code JFrame} or other non-{@code JComponent}). + * + * @param comp the component to receive the transfer; + * provided to enable sharing of TransferHandlers + * @param transferFlavors the data formats available + * @return true if the data can be inserted into the component, false otherwise + * @see #canImport(TransferHandler.TransferSupport) + */ + 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; + * any bitwise-OR combination of {@code COPY}, {@code MOVE} + * and {@code LINK}. + *

+ * Some models are not mutable, so a transfer operation of {@code MOVE} + * should not be advertised in that case. Returning {@code NONE} + * disables transfers from the component. + * + * @param c the component holding the data to be transferred; + * provided to enable sharing of TransferHandlers + * @return {@code COPY} if the transfer property can be found, + * otherwise returns NONE + */ + 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; + * provided to enable sharing of TransferHandlers + * @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 getDropTargetListener() { + synchronized(DropHandler.class) { + DropHandler handler = + (DropHandler)AppContext.getAppContext().get(DropHandler.class); + + if (handler == null) { + handler = new DropHandler(); + AppContext.getAppContext().put(DropHandler.class, handler); + } + + return handler; + } + } + + 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, (Object[])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(Component c) { + super(c, COPY_OR_MOVE | LINK, null); + try { + // addDropTargetListener is overridden + // we specifically need to add to the superclass + 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); + } + } + } + if (!isActive()) { + // If the Drop target is inactive the dragExit will not be dispatched to the dtListener, + // so make sure that we clean up the dtListener anyway. + DropTargetListener dtListener = getDropTargetListener(); + if (dtListener != null && dtListener instanceof DropHandler) { + ((DropHandler)dtListener).cleanup(false); + } + } + } + + 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, + ActionListener { + + private Timer timer; + private Point lastPosition; + private Rectangle outer = new Rectangle(); + private Rectangle inner = new Rectangle(); + private int hysteresis = 10; + + private Component component; + private Object state; + private TransferSupport support = + new TransferSupport(null, (DropTargetEvent)null); + + private static final int AUTOSCROLL_INSET = 10; + + /** + * Update the geometry of the autoscroll region. The geometry is + * maintained as a pair of rectangles. The region can cause + * a scroll if the pointer sits inside it for the duration of the + * timer. The region that causes the timer countdown is the area + * between the two rectangles. + *

+ * This is implemented to use the visible area of the component + * as the outer rectangle, and the insets are fixed at 10. Should + * the component be smaller than a total of 20 in any direction, + * autoscroll will not occur in that direction. + */ + private void updateAutoscrollRegion(JComponent c) { + // compute the outer + Rectangle visible = c.getVisibleRect(); + outer.setBounds(visible.x, visible.y, visible.width, visible.height); + + // compute the insets + Insets i = new Insets(0, 0, 0, 0); + if (c instanceof Scrollable) { + int minSize = 2 * AUTOSCROLL_INSET; + + if (visible.width >= minSize) { + i.left = i.right = AUTOSCROLL_INSET; + } + + if (visible.height >= minSize) { + i.top = i.bottom = AUTOSCROLL_INSET; + } + } + + // set the inner from the insets + inner.setBounds(visible.x + i.left, + visible.y + i.top, + visible.width - (i.left + i.right), + visible.height - (i.top + i.bottom)); + } + + /** + * Perform an autoscroll operation. This is implemented to scroll by the + * unit increment of the Scrollable using scrollRectToVisible. If the + * cursor is in a corner of the autoscroll region, more than one axis will + * scroll. + */ + private void autoscroll(JComponent c, Point pos) { + if (c instanceof Scrollable) { + Scrollable s = (Scrollable) c; + if (pos.y < inner.y) { + // scroll upward + int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1); + Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy); + c.scrollRectToVisible(r); + } else if (pos.y > (inner.y + inner.height)) { + // scroll downard + int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1); + Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy); + c.scrollRectToVisible(r); + } + + if (pos.x < inner.x) { + // scroll left + int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1); + Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height); + c.scrollRectToVisible(r); + } else if (pos.x > (inner.x + inner.width)) { + // scroll right + int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1); + Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height); + c.scrollRectToVisible(r); + } + } + } + + /** + * Initializes the internal properties if they haven't been already + * inited. This is done lazily to avoid loading of desktop properties. + */ + private void initPropertiesIfNecessary() { + if (timer == null) { + Toolkit t = Toolkit.getDefaultToolkit(); + Integer prop; + + prop = (Integer) + t.getDesktopProperty("DnD.Autoscroll.interval"); + + timer = new Timer(prop == null ? 100 : prop.intValue(), this); + + prop = (Integer) + t.getDesktopProperty("DnD.Autoscroll.initialDelay"); + + timer.setInitialDelay(prop == null ? 100 : prop.intValue()); + + prop = (Integer) + t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis"); + + if (prop != null) { + hysteresis = prop.intValue(); + } + } + } + + /** + * The timer fired, perform autoscroll if the pointer is within the + * autoscroll region. + *

+ * @param e the ActionEvent + */ + public void actionPerformed(ActionEvent e) { + updateAutoscrollRegion((JComponent)component); + if (outer.contains(lastPosition) && !inner.contains(lastPosition)) { + autoscroll((JComponent)component, lastPosition); + } + } + + // --- DropTargetListener methods ----------------------------------- + + private void setComponentDropLocation(TransferSupport support, + boolean forDrop) { + + DropLocation dropLocation = (support == null) + ? null + : support.getDropLocation(); + + if (SunToolkit.isInstanceOf(component, "javax.swing.text.JTextComponent")) { + state = SwingAccessor.getJTextComponentAccessor(). + setDropLocation((JTextComponent)component, dropLocation, state, forDrop); + } else if (component instanceof JComponent) { + state = ((JComponent)component).setDropLocation(dropLocation, state, forDrop); + } + } + + private void handleDrag(DropTargetDragEvent e) { + TransferHandler importer = + ((HasGetTransferHandler)component).getTransferHandler(); + + if (importer == null) { + e.rejectDrag(); + setComponentDropLocation(null, false); + return; + } + + support.setDNDVariables(component, e); + boolean canImport = importer.canImport(support); + + if (canImport) { + e.acceptDrag(support.getDropAction()); + } else { + e.rejectDrag(); + } + + boolean showLocation = support.showDropLocationIsSet ? + support.showDropLocation : + canImport; + + setComponentDropLocation(showLocation ? support : null, false); + } + + public void dragEnter(DropTargetDragEvent e) { + state = null; + component = e.getDropTargetContext().getComponent(); + + handleDrag(e); + + if (component instanceof JComponent) { + lastPosition = e.getLocation(); + updateAutoscrollRegion((JComponent)component); + initPropertiesIfNecessary(); + } + } + + public void dragOver(DropTargetDragEvent e) { + handleDrag(e); + + if (!(component instanceof JComponent)) { + return; + } + + Point p = e.getLocation(); + + if (Math.abs(p.x - lastPosition.x) > hysteresis + || Math.abs(p.y - lastPosition.y) > hysteresis) { + // no autoscroll + if (timer.isRunning()) timer.stop(); + } else { + if (!timer.isRunning()) timer.start(); + } + + lastPosition = p; + } + + public void dragExit(DropTargetEvent e) { + cleanup(false); + } + + public void drop(DropTargetDropEvent e) { + TransferHandler importer = + ((HasGetTransferHandler)component).getTransferHandler(); + + if (importer == null) { + e.rejectDrop(); + cleanup(false); + return; + } + + support.setDNDVariables(component, e); + boolean canImport = importer.canImport(support); + + if (canImport) { + e.acceptDrop(support.getDropAction()); + + boolean showLocation = support.showDropLocationIsSet ? + support.showDropLocation : + canImport; + + setComponentDropLocation(showLocation ? support : null, false); + + boolean success; + + try { + success = importer.importData(support); + } catch (RuntimeException re) { + success = false; + } + + e.dropComplete(success); + cleanup(success); + } else { + e.rejectDrop(); + cleanup(false); + } + } + + public void dropActionChanged(DropTargetDragEvent e) { + /* + * Work-around for Linux bug where dropActionChanged + * is called before dragEnter. + */ + if (component == null) { + return; + } + + handleDrag(e); + } + + private void cleanup(boolean forDrop) { + setComponentDropLocation(null, forDrop); + if (component instanceof JComponent) { + ((JComponent)component).dndDone(); + } + + if (timer != null) { + timer.stop(); + } + + state = null; + component = null; + lastPosition = null; + } + } + + /** + * 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 { + Image im = th.getDragImage(); + if (im == null) { + dge.startDrag(null, t, this); + } else { + dge.startDrag(null, im, th.getDragImageOffset(), 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 UIAction implements UIResource { + + TransferAction(String name) { + super(name); + } + + public boolean isEnabled(Object sender) { + if (sender instanceof JComponent + && ((JComponent)sender).getTransferHandler() == null) { + return false; + } + + return true; + } + + private static final JavaSecurityAccess javaSecurityAccess = + SharedSecrets.getJavaSecurityAccess(); + + public void actionPerformed(final ActionEvent e) { + final Object src = e.getSource(); + + final PrivilegedAction action = new PrivilegedAction() { + public Void run() { + actionPerformedImpl(e); + return null; + } + }; + + final AccessControlContext stack = AccessController.getContext(); + final AccessControlContext srcAcc = AWTAccessor.getComponentAccessor().getAccessControlContext((Component)src); + final AccessControlContext eventAcc = AWTAccessor.getAWTEventAccessor().getAccessControlContext(e); + + if (srcAcc == null) { + javaSecurityAccess.doIntersectionPrivilege(action, stack, eventAcc); + } else { + javaSecurityAccess.doIntersectionPrivilege( + new PrivilegedAction() { + public Void run() { + javaSecurityAccess.doIntersectionPrivilege(action, eventAcc); + return null; + } + }, stack, srcAcc); + } + } + + private void actionPerformedImpl(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(new TransferSupport(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(); + + } + +}