| /* TransferHandler.java -- |
| Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing; |
| |
| import java.awt.Toolkit; |
| import java.awt.datatransfer.Clipboard; |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.datatransfer.UnsupportedFlavorException; |
| import java.awt.dnd.DragGestureEvent; |
| import java.awt.dnd.DragGestureListener; |
| import java.awt.dnd.DragGestureRecognizer; |
| import java.awt.dnd.DragSource; |
| import java.awt.dnd.DragSourceContext; |
| import java.awt.dnd.DragSourceDragEvent; |
| import java.awt.dnd.DragSourceDropEvent; |
| import java.awt.dnd.DragSourceEvent; |
| import java.awt.dnd.DragSourceListener; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.InputEvent; |
| import java.awt.event.MouseEvent; |
| import java.beans.BeanInfo; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.lang.reflect.Method; |
| |
| public class TransferHandler implements Serializable |
| { |
| |
| /** |
| * An implementation of {@link Transferable} that can be used to export |
| * data from a component's property. |
| */ |
| private static class PropertyTransferable |
| implements Transferable |
| { |
| /** |
| * The component from which we export. |
| */ |
| private JComponent component; |
| |
| /** |
| * The property descriptor of the property that we handle. |
| */ |
| private PropertyDescriptor property; |
| |
| /** |
| * Creates a new PropertyTransferable. |
| * |
| * @param c the component from which we export |
| * @param prop the property from which we export |
| */ |
| PropertyTransferable(JComponent c, PropertyDescriptor prop) |
| { |
| component = c; |
| property = prop; |
| } |
| |
| /** |
| * Returns the data flavors supported by the Transferable. |
| * |
| * @return the data flavors supported by the Transferable |
| */ |
| public DataFlavor[] getTransferDataFlavors() |
| { |
| DataFlavor[] flavors; |
| Class propClass = property.getPropertyType(); |
| String mime = DataFlavor.javaJVMLocalObjectMimeType + "; class=" |
| + propClass.getName(); |
| try |
| { |
| DataFlavor flavor = new DataFlavor(mime); |
| flavors = new DataFlavor[]{ flavor }; |
| } |
| catch (ClassNotFoundException ex) |
| { |
| flavors = new DataFlavor[0]; |
| } |
| return flavors; |
| } |
| |
| /** |
| * Returns <code>true</code> when the specified data flavor is supported, |
| * <code>false</code> otherwise. |
| * |
| * @return <code>true</code> when the specified data flavor is supported, |
| * <code>false</code> otherwise |
| */ |
| public boolean isDataFlavorSupported(DataFlavor flavor) |
| { |
| Class propClass = property.getPropertyType(); |
| return flavor.getPrimaryType().equals("application") |
| && flavor.getSubType().equals("x-java-jvm-local-objectref") |
| && propClass.isAssignableFrom(flavor.getRepresentationClass()); |
| } |
| |
| /** |
| * Returns the actual transfer data. |
| * |
| * @param flavor the data flavor |
| * |
| * @return the actual transfer data |
| */ |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException, IOException |
| { |
| if (isDataFlavorSupported(flavor)) |
| { |
| Method getter = property.getReadMethod(); |
| Object o; |
| try |
| { |
| o = getter.invoke(component, null); |
| return o; |
| } |
| catch (Exception ex) |
| { |
| throw new IOException("Property read failed: " |
| + property.getName()); |
| } |
| } |
| else |
| throw new UnsupportedFlavorException(flavor); |
| } |
| } |
| |
| static class TransferAction extends AbstractAction |
| { |
| private String command; |
| |
| public TransferAction(String command) |
| { |
| super(command); |
| this.command = command; |
| } |
| |
| public void actionPerformed(ActionEvent event) |
| { |
| JComponent component = (JComponent) event.getSource(); |
| TransferHandler transferHandler = component.getTransferHandler(); |
| Clipboard clipboard = getClipboard(component); |
| |
| if (clipboard == null) |
| { |
| // Access denied! |
| Toolkit.getDefaultToolkit().beep(); |
| return; |
| } |
| |
| if (command.equals(COMMAND_COPY)) |
| transferHandler.exportToClipboard(component, clipboard, COPY); |
| else if (command.equals(COMMAND_CUT)) |
| transferHandler.exportToClipboard(component, clipboard, MOVE); |
| else if (command.equals(COMMAND_PASTE)) |
| { |
| Transferable transferable = clipboard.getContents(null); |
| |
| if (transferable != null) |
| transferHandler.importData(component, transferable); |
| } |
| } |
| |
| /** |
| * Get the system cliboard or null if the caller isn't allowed to |
| * access the system clipboard. |
| * |
| * @param component a component, used to get the toolkit. |
| * @return the clipboard |
| */ |
| private static Clipboard getClipboard(JComponent component) |
| { |
| try |
| { |
| return component.getToolkit().getSystemClipboard(); |
| } |
| catch (SecurityException se) |
| { |
| return null; |
| } |
| } |
| } |
| |
| private static class SwingDragGestureRecognizer extends DragGestureRecognizer |
| { |
| |
| protected SwingDragGestureRecognizer(DragGestureListener dgl) |
| { |
| super(DragSource.getDefaultDragSource(), null, NONE, dgl); |
| } |
| |
| void gesture(JComponent c, MouseEvent e, int src, int drag) |
| { |
| setComponent(c); |
| setSourceActions(src); |
| appendEvent(e); |
| fireDragGestureRecognized(drag, e.getPoint()); |
| } |
| |
| protected void registerListeners() |
| { |
| // Nothing to do here. |
| } |
| |
| protected void unregisterListeners() |
| { |
| // Nothing to do here. |
| } |
| |
| } |
| |
| private static class SwingDragHandler |
| implements DragGestureListener, DragSourceListener |
| { |
| |
| private boolean autoscrolls; |
| |
| public void dragGestureRecognized(DragGestureEvent e) |
| { |
| JComponent c = (JComponent) e.getComponent(); |
| TransferHandler th = c.getTransferHandler(); |
| Transferable t = th.createTransferable(c); |
| if (t != null) |
| { |
| autoscrolls = c.getAutoscrolls(); |
| c.setAutoscrolls(false); |
| try |
| { |
| e.startDrag(null, t, this); |
| return; |
| } |
| finally |
| { |
| c.setAutoscrolls(autoscrolls); |
| } |
| } |
| th.exportDone(c, t, NONE); |
| } |
| |
| public void dragDropEnd(DragSourceDropEvent e) |
| { |
| DragSourceContext ctx = e.getDragSourceContext(); |
| JComponent c = (JComponent) ctx.getComponent(); |
| TransferHandler th = c.getTransferHandler(); |
| if (e.getDropSuccess()) |
| { |
| th.exportDone(c, ctx.getTransferable(), e.getDropAction()); |
| } |
| else |
| { |
| th.exportDone(c, ctx.getTransferable(), e.getDropAction()); |
| } |
| c.setAutoscrolls(autoscrolls); |
| } |
| |
| public void dragEnter(DragSourceDragEvent e) |
| { |
| // Nothing to do here. |
| } |
| |
| public void dragExit(DragSourceEvent e) |
| { |
| // Nothing to do here. |
| } |
| |
| public void dragOver(DragSourceDragEvent e) |
| { |
| // Nothing to do here. |
| } |
| |
| public void dropActionChanged(DragSourceDragEvent e) |
| { |
| // Nothing to do here. |
| } |
| |
| } |
| |
| private static final long serialVersionUID = -967749805571669910L; |
| |
| private static final String COMMAND_COPY = "copy"; |
| private static final String COMMAND_CUT = "cut"; |
| private static final String COMMAND_PASTE = "paste"; |
| |
| public static final int NONE = 0; |
| public static final int COPY = 1; |
| public static final int MOVE = 2; |
| public static final int COPY_OR_MOVE = 3; |
| |
| private static Action copyAction = new TransferAction(COMMAND_COPY); |
| private static Action cutAction = new TransferAction(COMMAND_CUT); |
| private static Action pasteAction = new TransferAction(COMMAND_PASTE); |
| |
| private int sourceActions; |
| private Icon visualRepresentation; |
| |
| /** |
| * The name of the property into/from which this TransferHandler |
| * imports/exports. |
| */ |
| private String propertyName; |
| |
| /** |
| * The DragGestureRecognizer for Swing. |
| */ |
| private SwingDragGestureRecognizer recognizer; |
| |
| public static Action getCopyAction() |
| { |
| return copyAction; |
| } |
| |
| public static Action getCutAction() |
| { |
| return cutAction; |
| } |
| |
| public static Action getPasteAction() |
| { |
| return pasteAction; |
| } |
| |
| protected TransferHandler() |
| { |
| this.sourceActions = NONE; |
| } |
| |
| public TransferHandler(String property) |
| { |
| propertyName = property; |
| this.sourceActions = property != null ? COPY : NONE; |
| } |
| |
| /** |
| * Returns <code>true</code> if the data in this TransferHandler can be |
| * imported into the specified component. This will be the case when: |
| * <ul> |
| * <li>The component has a readable and writable property with the property |
| * name specified in the TransferHandler constructor.</li> |
| * <li>There is a dataflavor with a mime type of |
| * <code>application/x-java-jvm-local-object-ref</code>.</li> |
| * <li>The dataflavor's representation class matches the class of the |
| * property in the component.</li> |
| * </li> |
| * |
| * @param c the component to check |
| * @param flavors the possible data flavors |
| * |
| * @return <code>true</code> if the data in this TransferHandler can be |
| * imported into the specified component, <code>false</code> |
| * otherwise |
| */ |
| public boolean canImport(JComponent c, DataFlavor[] flavors) |
| { |
| PropertyDescriptor propDesc = getPropertyDescriptor(c); |
| boolean canImport = false; |
| if (propDesc != null) |
| { |
| // Check if the property is writable. The readable check is already |
| // done in getPropertyDescriptor(). |
| Method writer = propDesc.getWriteMethod(); |
| if (writer != null) |
| { |
| Class[] params = writer.getParameterTypes(); |
| if (params.length == 1) |
| { |
| // Number of parameters ok, now check mime type and |
| // representation class. |
| DataFlavor flavor = getPropertyDataFlavor(params[0], flavors); |
| if (flavor != null) |
| canImport = true; |
| } |
| } |
| } |
| return canImport; |
| } |
| |
| /** |
| * Creates a {@link Transferable} that can be used to export data |
| * from the specified component. |
| * |
| * This method returns <code>null</code> when the specified component |
| * doesn't have a readable property that matches the property name |
| * specified in the <code>TransferHandler</code> constructor. |
| * |
| * @param c the component to create a transferable for |
| * |
| * @return a {@link Transferable} that can be used to export data |
| * from the specified component, or null if the component doesn't |
| * have a readable property like the transfer handler |
| */ |
| protected Transferable createTransferable(JComponent c) |
| { |
| Transferable transferable = null; |
| if (propertyName != null) |
| { |
| PropertyDescriptor prop = getPropertyDescriptor(c); |
| if (prop != null) |
| transferable = new PropertyTransferable(c, prop); |
| } |
| return transferable; |
| } |
| |
| public void exportAsDrag(JComponent c, InputEvent e, int action) |
| { |
| int src = getSourceActions(c); |
| int drag = src & action; |
| if (! (e instanceof MouseEvent)) |
| { |
| drag = NONE; |
| } |
| if (drag != NONE) |
| { |
| if (recognizer == null) |
| { |
| SwingDragHandler ds = new SwingDragHandler(); |
| recognizer = new SwingDragGestureRecognizer(ds); |
| } |
| recognizer.gesture(c, (MouseEvent) e, src, drag); |
| } |
| else |
| { |
| exportDone(c, null, NONE); |
| } |
| } |
| |
| /** |
| * This method is invoked after data has been exported. |
| * Subclasses should implement this method to remove the data that has been |
| * transferred when the action was <code>MOVE</code>. |
| * |
| * The default implementation does nothing because MOVE is not supported. |
| * |
| * @param c the source component |
| * @param data the data that has been transferred or <code>null</code> |
| * when the action is NONE |
| * @param action the action that has been performed |
| */ |
| protected void exportDone(JComponent c, Transferable data, int action) |
| { |
| // Nothing to do in the default implementation. |
| } |
| |
| /** |
| * Exports the property of the component <code>c</code> that was |
| * specified for this TransferHandler to the clipboard, performing |
| * the specified action. |
| * |
| * This will check if the action is allowed by calling |
| * {@link #getSourceActions(JComponent)}. If the action is not allowed, |
| * then no export is performed. |
| * |
| * In either case the method {@link #exportDone} will be called with |
| * the action that has been performed, or {@link #NONE} if the action |
| * was not allowed or could otherwise not be completed. |
| * Any IllegalStateException that is thrown by the Clipboard due to |
| * beeing unavailable will be propagated through this method. |
| * |
| * @param c the component from which to export |
| * @param clip the clipboard to which the data will be exported |
| * @param action the action to perform |
| * |
| * @throws IllegalStateException when the clipboard is not available |
| */ |
| public void exportToClipboard(JComponent c, Clipboard clip, int action) |
| throws IllegalStateException |
| { |
| action &= getSourceActions(c); |
| Transferable transferable = createTransferable(c); |
| if (transferable != null && action != NONE) |
| { |
| try |
| { |
| clip.setContents(transferable, null); |
| exportDone(c, transferable, action); |
| } |
| catch (IllegalStateException ex) |
| { |
| exportDone(c, transferable, NONE); |
| throw ex; |
| } |
| } |
| else |
| exportDone(c, null, NONE); |
| } |
| |
| public int getSourceActions(JComponent c) |
| { |
| return sourceActions; |
| } |
| |
| public Icon getVisualRepresentation(Transferable t) |
| { |
| return visualRepresentation; |
| } |
| |
| /** |
| * Imports the transfer data represented by <code>t</code> into the specified |
| * component <code>c</code> by setting the property of this TransferHandler |
| * on that component. If this succeeds, this method returns |
| * <code>true</code>, otherwise <code>false</code>. |
| * |
| * |
| * @param c the component to import into |
| * @param t the transfer data to import |
| * |
| * @return <code>true</code> if the transfer succeeds, <code>false</code> |
| * otherwise |
| */ |
| public boolean importData(JComponent c, Transferable t) |
| { |
| boolean ok = false; |
| PropertyDescriptor prop = getPropertyDescriptor(c); |
| if (prop != null) |
| { |
| Method writer = prop.getWriteMethod(); |
| if (writer != null) |
| { |
| Class[] params = writer.getParameterTypes(); |
| if (params.length == 1) |
| { |
| DataFlavor flavor = getPropertyDataFlavor(params[0], |
| t.getTransferDataFlavors()); |
| if (flavor != null) |
| { |
| try |
| { |
| Object value = t.getTransferData(flavor); |
| writer.invoke(c, new Object[]{ value }); |
| ok = true; |
| } |
| catch (Exception ex) |
| { |
| // If anything goes wrong here, do nothing and return |
| // false; |
| } |
| } |
| } |
| } |
| } |
| return ok; |
| } |
| |
| /** |
| * Returns the property descriptor for the property of this TransferHandler |
| * in the specified component, or <code>null</code> if no such property |
| * exists in the component. This method only returns properties that are |
| * at least readable (that is, it has a public no-arg getter method). |
| * |
| * @param c the component to check |
| * |
| * @return the property descriptor for the property of this TransferHandler |
| * in the specified component, or <code>null</code> if no such |
| * property exists in the component |
| */ |
| private PropertyDescriptor getPropertyDescriptor(JComponent c) |
| { |
| PropertyDescriptor prop = null; |
| if (propertyName != null) |
| { |
| Class clazz = c.getClass(); |
| BeanInfo beanInfo; |
| try |
| { |
| beanInfo = Introspector.getBeanInfo(clazz); |
| } |
| catch (IntrospectionException ex) |
| { |
| beanInfo = null; |
| } |
| if (beanInfo != null) |
| { |
| PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); |
| for (int i = 0; i < props.length && prop == null; i++) |
| { |
| PropertyDescriptor desc = props[i]; |
| if (desc.getName().equals(propertyName)) |
| { |
| Method reader = desc.getReadMethod(); |
| if (reader != null) |
| { |
| Class[] params = reader.getParameterTypes(); |
| if (params == null || params.length == 0) |
| prop = desc; |
| } |
| } |
| } |
| } |
| } |
| return prop; |
| } |
| |
| /** |
| * Searches <code>flavors</code> to find a suitable data flavor that |
| * has the mime type application/x-java-jvm-local-objectref and a |
| * representation class that is the same as the specified <code>clazz</code>. |
| * When no such data flavor is found, this returns <code>null</code>. |
| * |
| * @param clazz the representation class required for the data flavor |
| * @param flavors the possible data flavors |
| * |
| * @return the suitable data flavor or null if none is found |
| */ |
| private DataFlavor getPropertyDataFlavor(Class clazz, DataFlavor[] flavors) |
| { |
| DataFlavor found = null; |
| for (int i = 0; i < flavors.length && found == null; i++) |
| { |
| DataFlavor flavor = flavors[i]; |
| if (flavor.getPrimaryType().equals("application") |
| && flavor.getSubType().equals("x-java-jvm-local-objectref") |
| && clazz.isAssignableFrom(flavor.getRepresentationClass())) |
| found = flavor; |
| } |
| return found; |
| } |
| } |