1 /* TransferHandler.java --
2    Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing;
40 
41 import java.awt.Toolkit;
42 import java.awt.datatransfer.Clipboard;
43 import java.awt.datatransfer.DataFlavor;
44 import java.awt.datatransfer.Transferable;
45 import java.awt.datatransfer.UnsupportedFlavorException;
46 import java.awt.dnd.DragGestureEvent;
47 import java.awt.dnd.DragGestureListener;
48 import java.awt.dnd.DragGestureRecognizer;
49 import java.awt.dnd.DragSource;
50 import java.awt.dnd.DragSourceContext;
51 import java.awt.dnd.DragSourceDragEvent;
52 import java.awt.dnd.DragSourceDropEvent;
53 import java.awt.dnd.DragSourceEvent;
54 import java.awt.dnd.DragSourceListener;
55 import java.awt.event.ActionEvent;
56 import java.awt.event.InputEvent;
57 import java.awt.event.MouseEvent;
58 import java.beans.BeanInfo;
59 import java.beans.IntrospectionException;
60 import java.beans.Introspector;
61 import java.beans.PropertyDescriptor;
62 import java.io.IOException;
63 import java.io.Serializable;
64 import java.lang.reflect.Method;
65 
66 public class TransferHandler implements Serializable
67 {
68 
69   /**
70    * An implementation of {@link Transferable} that can be used to export
71    * data from a component's property.
72    */
73   private static class PropertyTransferable
74     implements Transferable
75   {
76     /**
77      * The component from which we export.
78      */
79     private JComponent component;
80 
81     /**
82      * The property descriptor of the property that we handle.
83      */
84     private PropertyDescriptor property;
85 
86     /**
87      * Creates a new PropertyTransferable.
88      *
89      * @param c the component from which we export
90      * @param prop the property from which we export
91      */
PropertyTransferable(JComponent c, PropertyDescriptor prop)92     PropertyTransferable(JComponent c, PropertyDescriptor prop)
93     {
94       component = c;
95       property = prop;
96     }
97 
98     /**
99      * Returns the data flavors supported by the Transferable.
100      *
101      * @return the data flavors supported by the Transferable
102      */
getTransferDataFlavors()103     public DataFlavor[] getTransferDataFlavors()
104     {
105       DataFlavor[] flavors;
106       Class propClass = property.getPropertyType();
107       String mime = DataFlavor.javaJVMLocalObjectMimeType + "; class="
108                     + propClass.getName();
109       try
110         {
111           DataFlavor flavor = new DataFlavor(mime);
112           flavors = new DataFlavor[]{ flavor };
113         }
114       catch (ClassNotFoundException ex)
115         {
116           flavors = new DataFlavor[0];
117         }
118       return flavors;
119     }
120 
121     /**
122      * Returns <code>true</code> when the specified data flavor is supported,
123      * <code>false</code> otherwise.
124      *
125      * @return <code>true</code> when the specified data flavor is supported,
126      *         <code>false</code> otherwise
127      */
isDataFlavorSupported(DataFlavor flavor)128     public boolean isDataFlavorSupported(DataFlavor flavor)
129     {
130       Class propClass = property.getPropertyType();
131       return flavor.getPrimaryType().equals("application")
132         && flavor.getSubType().equals("x-java-jvm-local-objectref")
133         && propClass.isAssignableFrom(flavor.getRepresentationClass());
134     }
135 
136     /**
137      * Returns the actual transfer data.
138      *
139      * @param flavor the data flavor
140      *
141      * @return the actual transfer data
142      */
getTransferData(DataFlavor flavor)143     public Object getTransferData(DataFlavor flavor)
144       throws UnsupportedFlavorException, IOException
145     {
146       if (isDataFlavorSupported(flavor))
147         {
148           Method getter = property.getReadMethod();
149           Object o;
150           try
151             {
152               o = getter.invoke(component);
153               return o;
154             }
155           catch (Exception ex)
156             {
157               throw new IOException("Property read failed: "
158                                     + property.getName());
159             }
160         }
161       else
162         throw new UnsupportedFlavorException(flavor);
163     }
164   }
165 
166   static class TransferAction extends AbstractAction
167   {
168     private String command;
169 
TransferAction(String command)170     public TransferAction(String command)
171     {
172       super(command);
173       this.command = command;
174     }
175 
actionPerformed(ActionEvent event)176     public void actionPerformed(ActionEvent event)
177     {
178       JComponent component = (JComponent) event.getSource();
179       TransferHandler transferHandler = component.getTransferHandler();
180       Clipboard clipboard = getClipboard(component);
181 
182       if (clipboard == null)
183         {
184           // Access denied!
185           Toolkit.getDefaultToolkit().beep();
186           return;
187         }
188 
189       if (command.equals(COMMAND_COPY))
190         transferHandler.exportToClipboard(component, clipboard, COPY);
191       else if (command.equals(COMMAND_CUT))
192         transferHandler.exportToClipboard(component, clipboard, MOVE);
193       else if (command.equals(COMMAND_PASTE))
194         {
195           Transferable transferable = clipboard.getContents(null);
196 
197           if (transferable != null)
198             transferHandler.importData(component, transferable);
199         }
200     }
201 
202     /**
203      * Get the system cliboard or null if the caller isn't allowed to
204      * access the system clipboard.
205      *
206      * @param component a component, used to get the toolkit.
207      * @return the clipboard
208      */
getClipboard(JComponent component)209     private static Clipboard getClipboard(JComponent component)
210     {
211       try
212         {
213           return component.getToolkit().getSystemClipboard();
214         }
215       catch (SecurityException se)
216         {
217           return null;
218         }
219     }
220   }
221 
222   private static class SwingDragGestureRecognizer extends DragGestureRecognizer
223   {
224 
SwingDragGestureRecognizer(DragGestureListener dgl)225     protected SwingDragGestureRecognizer(DragGestureListener dgl)
226     {
227       super(DragSource.getDefaultDragSource(), null, NONE, dgl);
228     }
229 
gesture(JComponent c, MouseEvent e, int src, int drag)230     void gesture(JComponent c, MouseEvent e, int src, int drag)
231     {
232       setComponent(c);
233       setSourceActions(src);
234       appendEvent(e);
235       fireDragGestureRecognized(drag, e.getPoint());
236     }
237 
registerListeners()238     protected void registerListeners()
239     {
240       // Nothing to do here.
241     }
242 
unregisterListeners()243     protected void unregisterListeners()
244     {
245       // Nothing to do here.
246     }
247 
248   }
249 
250   private static class SwingDragHandler
251     implements DragGestureListener, DragSourceListener
252   {
253 
254     private boolean autoscrolls;
255 
dragGestureRecognized(DragGestureEvent e)256     public void dragGestureRecognized(DragGestureEvent e)
257     {
258       JComponent c = (JComponent) e.getComponent();
259       TransferHandler th = c.getTransferHandler();
260       Transferable t = th.createTransferable(c);
261       if (t != null)
262         {
263           autoscrolls = c.getAutoscrolls();
264           c.setAutoscrolls(false);
265           try
266             {
267               e.startDrag(null, t, this);
268               return;
269             }
270           finally
271             {
272               c.setAutoscrolls(autoscrolls);
273             }
274         }
275       th.exportDone(c, t, NONE);
276     }
277 
dragDropEnd(DragSourceDropEvent e)278     public void dragDropEnd(DragSourceDropEvent e)
279     {
280       DragSourceContext ctx = e.getDragSourceContext();
281       JComponent c = (JComponent) ctx.getComponent();
282       TransferHandler th = c.getTransferHandler();
283       if (e.getDropSuccess())
284         {
285           th.exportDone(c, ctx.getTransferable(), e.getDropAction());
286         }
287       else
288         {
289           th.exportDone(c, ctx.getTransferable(), e.getDropAction());
290         }
291       c.setAutoscrolls(autoscrolls);
292     }
293 
dragEnter(DragSourceDragEvent e)294     public void dragEnter(DragSourceDragEvent e)
295     {
296       // Nothing to do here.
297     }
298 
dragExit(DragSourceEvent e)299     public void dragExit(DragSourceEvent e)
300     {
301       // Nothing to do here.
302     }
303 
dragOver(DragSourceDragEvent e)304     public void dragOver(DragSourceDragEvent e)
305     {
306       // Nothing to do here.
307     }
308 
dropActionChanged(DragSourceDragEvent e)309     public void dropActionChanged(DragSourceDragEvent e)
310     {
311       // Nothing to do here.
312     }
313 
314   }
315 
316   private static final long serialVersionUID = -967749805571669910L;
317 
318   private static final String COMMAND_COPY = "copy";
319   private static final String COMMAND_CUT = "cut";
320   private static final String COMMAND_PASTE = "paste";
321 
322   public static final int NONE = 0;
323   public static final int COPY = 1;
324   public static final int MOVE = 2;
325   public static final int COPY_OR_MOVE = 3;
326 
327   private static Action copyAction = new TransferAction(COMMAND_COPY);
328   private static Action cutAction = new TransferAction(COMMAND_CUT);
329   private static Action pasteAction = new TransferAction(COMMAND_PASTE);
330 
331   private int sourceActions;
332   private Icon visualRepresentation;
333 
334   /**
335    * The name of the property into/from which this TransferHandler
336    * imports/exports.
337    */
338   private String propertyName;
339 
340   /**
341    * The DragGestureRecognizer for Swing.
342    */
343   private SwingDragGestureRecognizer recognizer;
344 
getCopyAction()345   public static Action getCopyAction()
346   {
347     return copyAction;
348   }
349 
getCutAction()350   public static Action getCutAction()
351   {
352     return cutAction;
353   }
354 
getPasteAction()355   public static Action getPasteAction()
356   {
357     return pasteAction;
358   }
359 
TransferHandler()360   protected TransferHandler()
361   {
362     this.sourceActions = NONE;
363   }
364 
TransferHandler(String property)365   public TransferHandler(String property)
366   {
367     propertyName = property;
368     this.sourceActions = property != null ? COPY : NONE;
369   }
370 
371   /**
372    * Returns <code>true</code> if the data in this TransferHandler can be
373    * imported into the specified component. This will be the case when:
374    * <ul>
375    *   <li>The component has a readable and writable property with the property
376    *   name specified in the TransferHandler constructor.</li>
377    *   <li>There is a dataflavor with a mime type of
378    *     <code>application/x-java-jvm-local-object-ref</code>.</li>
379    *   <li>The dataflavor's representation class matches the class of the
380    *    property in the component.</li>
381    * </li>
382    *
383    * @param c the component to check
384    * @param flavors the possible data flavors
385    *
386    * @return <code>true</code> if the data in this TransferHandler can be
387    *         imported into the specified component, <code>false</code>
388    *         otherwise
389    */
canImport(JComponent c, DataFlavor[] flavors)390   public boolean canImport(JComponent c, DataFlavor[] flavors)
391   {
392     PropertyDescriptor propDesc = getPropertyDescriptor(c);
393     boolean canImport = false;
394     if (propDesc != null)
395       {
396         // Check if the property is writable. The readable check is already
397         // done in getPropertyDescriptor().
398         Method writer = propDesc.getWriteMethod();
399         if (writer != null)
400           {
401             Class[] params = writer.getParameterTypes();
402             if (params.length == 1)
403               {
404                 // Number of parameters ok, now check mime type and
405                 // representation class.
406                 DataFlavor flavor = getPropertyDataFlavor(params[0], flavors);
407                 if (flavor != null)
408                   canImport = true;
409               }
410           }
411       }
412     return canImport;
413   }
414 
415   /**
416    * Creates a {@link Transferable} that can be used to export data
417    * from the specified component.
418    *
419    * This method returns <code>null</code> when the specified component
420    * doesn't have a readable property that matches the property name
421    * specified in the <code>TransferHandler</code> constructor.
422    *
423    * @param c the component to create a transferable for
424    *
425    * @return a {@link Transferable} that can be used to export data
426    *         from the specified component, or null if the component doesn't
427    *         have a readable property like the transfer handler
428    */
createTransferable(JComponent c)429   protected Transferable createTransferable(JComponent c)
430   {
431     Transferable transferable = null;
432     if (propertyName != null)
433       {
434         PropertyDescriptor prop = getPropertyDescriptor(c);
435         if (prop != null)
436           transferable = new PropertyTransferable(c, prop);
437       }
438     return transferable;
439   }
440 
exportAsDrag(JComponent c, InputEvent e, int action)441   public void exportAsDrag(JComponent c, InputEvent e, int action)
442   {
443     int src = getSourceActions(c);
444     int drag = src & action;
445     if (! (e instanceof MouseEvent))
446       {
447         drag = NONE;
448       }
449     if (drag != NONE)
450       {
451         if (recognizer == null)
452           {
453             SwingDragHandler ds = new SwingDragHandler();
454             recognizer = new SwingDragGestureRecognizer(ds);
455           }
456         recognizer.gesture(c, (MouseEvent) e, src, drag);
457       }
458     else
459       {
460         exportDone(c, null, NONE);
461       }
462   }
463 
464   /**
465    * This method is invoked after data has been exported.
466    * Subclasses should implement this method to remove the data that has been
467    * transferred when the action was <code>MOVE</code>.
468    *
469    * The default implementation does nothing because MOVE is not supported.
470    *
471    * @param c the source component
472    * @param data the data that has been transferred or <code>null</code>
473    *        when the action is NONE
474    * @param action the action that has been performed
475    */
exportDone(JComponent c, Transferable data, int action)476   protected void exportDone(JComponent c, Transferable data, int action)
477   {
478     // Nothing to do in the default implementation.
479   }
480 
481   /**
482    * Exports the property of the component <code>c</code> that was
483    * specified for this TransferHandler to the clipboard, performing
484    * the specified action.
485    *
486    * This will check if the action is allowed by calling
487    * {@link #getSourceActions(JComponent)}. If the action is not allowed,
488    * then no export is performed.
489    *
490    * In either case the method {@link #exportDone} will be called with
491    * the action that has been performed, or {@link #NONE} if the action
492    * was not allowed or could otherwise not be completed.
493    * Any IllegalStateException that is thrown by the Clipboard due to
494    * beeing unavailable will be propagated through this method.
495    *
496    * @param c the component from which to export
497    * @param clip the clipboard to which the data will be exported
498    * @param action the action to perform
499    *
500    * @throws IllegalStateException when the clipboard is not available
501    */
exportToClipboard(JComponent c, Clipboard clip, int action)502   public void exportToClipboard(JComponent c, Clipboard clip, int action)
503     throws IllegalStateException
504   {
505     action &= getSourceActions(c);
506     Transferable transferable = createTransferable(c);
507     if (transferable != null && action != NONE)
508       {
509         try
510           {
511             clip.setContents(transferable, null);
512             exportDone(c, transferable, action);
513           }
514         catch (IllegalStateException ex)
515           {
516             exportDone(c, transferable, NONE);
517             throw ex;
518           }
519       }
520     else
521       exportDone(c, null, NONE);
522   }
523 
getSourceActions(JComponent c)524   public int getSourceActions(JComponent c)
525   {
526     return sourceActions;
527   }
528 
getVisualRepresentation(Transferable t)529   public Icon getVisualRepresentation(Transferable t)
530   {
531     return visualRepresentation;
532   }
533 
534   /**
535    * Imports the transfer data represented by <code>t</code> into the specified
536    * component <code>c</code> by setting the property of this TransferHandler
537    * on that component. If this succeeds, this method returns
538    * <code>true</code>, otherwise <code>false</code>.
539    *
540    *
541    * @param c the component to import into
542    * @param t the transfer data to import
543    *
544    * @return <code>true</code> if the transfer succeeds, <code>false</code>
545    *         otherwise
546    */
importData(JComponent c, Transferable t)547   public boolean importData(JComponent c, Transferable t)
548   {
549     boolean ok = false;
550     PropertyDescriptor prop = getPropertyDescriptor(c);
551     if (prop != null)
552       {
553         Method writer = prop.getWriteMethod();
554         if (writer != null)
555           {
556             Class[] params = writer.getParameterTypes();
557             if (params.length == 1)
558               {
559                 DataFlavor flavor = getPropertyDataFlavor(params[0],
560                                                    t.getTransferDataFlavors());
561                 if (flavor != null)
562                   {
563                     try
564                       {
565                         Object value = t.getTransferData(flavor);
566                         writer.invoke(c, new Object[]{ value });
567                         ok = true;
568                       }
569                     catch (Exception ex)
570                       {
571                         // If anything goes wrong here, do nothing and return
572                         // false;
573                       }
574                   }
575               }
576           }
577       }
578     return ok;
579   }
580 
581   /**
582    * Returns the property descriptor for the property of this TransferHandler
583    * in the specified component, or <code>null</code> if no such property
584    * exists in the component. This method only returns properties that are
585    * at least readable (that is, it has a public no-arg getter method).
586    *
587    * @param c the component to check
588    *
589    * @return the property descriptor for the property of this TransferHandler
590    *         in the specified component, or <code>null</code> if no such
591    *         property exists in the component
592    */
getPropertyDescriptor(JComponent c)593   private PropertyDescriptor getPropertyDescriptor(JComponent c)
594   {
595     PropertyDescriptor prop = null;
596     if (propertyName != null)
597       {
598         Class clazz = c.getClass();
599         BeanInfo beanInfo;
600         try
601           {
602             beanInfo = Introspector.getBeanInfo(clazz);
603           }
604         catch (IntrospectionException ex)
605           {
606             beanInfo = null;
607           }
608         if (beanInfo != null)
609           {
610             PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
611             for (int i = 0; i < props.length && prop == null; i++)
612               {
613                 PropertyDescriptor desc = props[i];
614                 if (desc.getName().equals(propertyName))
615                   {
616                     Method reader = desc.getReadMethod();
617                     if (reader != null)
618                       {
619                         Class[] params = reader.getParameterTypes();
620                         if (params == null || params.length == 0)
621                           prop = desc;
622                       }
623                   }
624               }
625           }
626       }
627     return prop;
628   }
629 
630   /**
631    * Searches <code>flavors</code> to find a suitable data flavor that
632    * has the mime type application/x-java-jvm-local-objectref and a
633    * representation class that is the same as the specified <code>clazz</code>.
634    * When no such data flavor is found, this returns <code>null</code>.
635    *
636    * @param clazz the representation class required for the data flavor
637    * @param flavors the possible data flavors
638    *
639    * @return the suitable data flavor or null if none is found
640    */
getPropertyDataFlavor(Class clazz, DataFlavor[] flavors)641   private DataFlavor getPropertyDataFlavor(Class clazz, DataFlavor[] flavors)
642   {
643     DataFlavor found = null;
644     for (int i = 0; i < flavors.length && found == null; i++)
645       {
646         DataFlavor flavor = flavors[i];
647         if (flavor.getPrimaryType().equals("application")
648             && flavor.getSubType().equals("x-java-jvm-local-objectref")
649             && clazz.isAssignableFrom(flavor.getRepresentationClass()))
650           found = flavor;
651       }
652     return found;
653   }
654 }
655