OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / TransferHandler.java
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 gnu.classpath.NotImplementedException;
42
43 import java.awt.Toolkit;
44 import java.awt.datatransfer.Clipboard;
45 import java.awt.datatransfer.DataFlavor;
46 import java.awt.datatransfer.Transferable;
47 import java.awt.datatransfer.UnsupportedFlavorException;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.InputEvent;
50 import java.beans.BeanInfo;
51 import java.beans.IntrospectionException;
52 import java.beans.Introspector;
53 import java.beans.PropertyDescriptor;
54 import java.io.IOException;
55 import java.io.Serializable;
56 import java.lang.reflect.Method;
57
58 public class TransferHandler implements Serializable
59 {
60
61   /**
62    * An implementation of {@link Transferable} that can be used to export
63    * data from a component's property.
64    */
65   private static class PropertyTransferable
66     implements Transferable
67   {
68     /**
69      * The component from which we export.
70      */
71     private JComponent component;
72
73     /**
74      * The property descriptor of the property that we handle.
75      */
76     private PropertyDescriptor property;
77
78     /**
79      * Creates a new PropertyTransferable.
80      *
81      * @param c the component from which we export
82      * @param prop the property from which we export
83      */
84     PropertyTransferable(JComponent c, PropertyDescriptor prop)
85     {
86       component = c;
87       property = prop;
88     }
89
90     /**
91      * Returns the data flavors supported by the Transferable.
92      *
93      * @return the data flavors supported by the Transferable
94      */
95     public DataFlavor[] getTransferDataFlavors()
96     {
97       DataFlavor[] flavors;
98       Class propClass = property.getPropertyType();
99       String mime = DataFlavor.javaJVMLocalObjectMimeType + "; class="
100                     + propClass.getName();
101       try
102         {
103           DataFlavor flavor = new DataFlavor(mime);
104           flavors = new DataFlavor[]{ flavor };
105         }
106       catch (ClassNotFoundException ex)
107         {
108           flavors = new DataFlavor[0];
109         }
110       return flavors;
111     }
112
113     /**
114      * Returns <code>true</code> when the specified data flavor is supported,
115      * <code>false</code> otherwise.
116      *
117      * @return <code>true</code> when the specified data flavor is supported,
118      *         <code>false</code> otherwise
119      */
120     public boolean isDataFlavorSupported(DataFlavor flavor)
121     {
122       Class propClass = property.getPropertyType();
123       return flavor.getPrimaryType().equals("application")
124         && flavor.getSubType().equals("x-java-jvm-local-objectref")
125         && propClass.isAssignableFrom(flavor.getRepresentationClass());
126     }
127
128     /**
129      * Returns the actual transfer data.
130      *
131      * @param flavor the data flavor
132      *
133      * @return the actual transfer data
134      */
135     public Object getTransferData(DataFlavor flavor)
136       throws UnsupportedFlavorException, IOException
137     {
138       if (isDataFlavorSupported(flavor))
139         {
140           Method getter = property.getReadMethod();
141           Object o;
142           try
143             {
144               o = getter.invoke(component, null);
145               return o;
146             }
147           catch (Exception ex)
148             {
149               throw new IOException("Property read failed: "
150                                     + property.getName());
151             }
152         }
153       else
154         throw new UnsupportedFlavorException(flavor);
155     }
156   }
157
158   static class TransferAction extends AbstractAction
159   {
160     private String command;
161
162     public TransferAction(String command)
163     {
164       super(command);
165       this.command = command;
166     }
167     
168     public void actionPerformed(ActionEvent event)
169     {
170       JComponent component = (JComponent) event.getSource();
171       TransferHandler transferHandler = component.getTransferHandler();
172       Clipboard clipboard = getClipboard(component);
173
174       if (clipboard == null)
175         {
176           // Access denied!
177           Toolkit.getDefaultToolkit().beep();
178           return;
179         }
180
181       if (command.equals(COMMAND_COPY))
182         transferHandler.exportToClipboard(component, clipboard, COPY);
183       else if (command.equals(COMMAND_CUT))
184         transferHandler.exportToClipboard(component, clipboard, MOVE);
185       else if (command.equals(COMMAND_PASTE))
186         {
187           Transferable transferable = clipboard.getContents(null);
188
189           if (transferable != null)
190             transferHandler.importData(component, transferable);
191         }
192     }
193   
194     /**
195      * Get the system cliboard or null if the caller isn't allowed to
196      * access the system clipboard.
197      * 
198      * @param component a component, used to get the toolkit.
199      * @return the clipboard
200      */
201     private static Clipboard getClipboard(JComponent component)
202     {
203       try
204         {
205           return component.getToolkit().getSystemClipboard();
206         }
207       catch (SecurityException se)
208         {
209           return null;
210         }
211     }
212   }
213   
214   private static final long serialVersionUID = -967749805571669910L;
215
216   private static final String COMMAND_COPY = "copy";
217   private static final String COMMAND_CUT = "cut";
218   private static final String COMMAND_PASTE = "paste";
219   
220   public static final int NONE = 0;
221   public static final int COPY = 1;
222   public static final int MOVE = 2;
223   public static final int COPY_OR_MOVE = 3;
224
225   private static Action copyAction = new TransferAction(COMMAND_COPY);
226   private static Action cutAction = new TransferAction(COMMAND_CUT);
227   private static Action pasteAction = new TransferAction(COMMAND_PASTE);
228   
229   private int sourceActions;
230   private Icon visualRepresentation;
231
232   /**
233    * The name of the property into/from which this TransferHandler
234    * imports/exports. 
235    */
236   private String propertyName;
237
238   public static Action getCopyAction()
239   {
240     return copyAction;
241   }
242
243   public static Action getCutAction()
244   {
245     return cutAction;
246   }
247
248   public static Action getPasteAction()
249   {
250     return pasteAction;
251   }
252
253   protected TransferHandler()
254   {
255     this.sourceActions = NONE;
256   }
257
258   public TransferHandler(String property)
259   {
260     propertyName = property;
261     this.sourceActions = property != null ? COPY : NONE;
262   }
263
264   /**
265    * Returns <code>true</code> if the data in this TransferHandler can be
266    * imported into the specified component. This will be the case when:
267    * <ul>
268    *   <li>The component has a readable and writable property with the property
269    *   name specified in the TransferHandler constructor.</li>
270    *   <li>There is a dataflavor with a mime type of
271    *     <code>application/x-java-jvm-local-object-ref</code>.</li>
272    *   <li>The dataflavor's representation class matches the class of the
273    *    property in the component.</li>
274    * </li>
275    *
276    * @param c the component to check
277    * @param flavors the possible data flavors
278    *
279    * @return <code>true</code> if the data in this TransferHandler can be
280    *         imported into the specified component, <code>false</code>
281    *         otherwise
282    */
283   public boolean canImport(JComponent c, DataFlavor[] flavors)
284   {
285     PropertyDescriptor propDesc = getPropertyDescriptor(c);
286     boolean canImport = false;
287     if (propDesc != null)
288       {
289         // Check if the property is writable. The readable check is already
290         // done in getPropertyDescriptor().
291         Method writer = propDesc.getWriteMethod();
292         if (writer != null)
293           {
294             Class[] params = writer.getParameterTypes();
295             if (params.length == 1)
296               {
297                 // Number of parameters ok, now check mime type and
298                 // representation class.
299                 DataFlavor flavor = getPropertyDataFlavor(params[0], flavors);
300                 if (flavor != null)
301                   canImport = true;
302               }
303           }
304       }
305     return canImport;
306   }
307
308   /**
309    * Creates a {@link Transferable} that can be used to export data
310    * from the specified component.
311    *
312    * This method returns <code>null</code> when the specified component
313    * doesn't have a readable property that matches the property name
314    * specified in the <code>TransferHandler</code> constructor.
315    *
316    * @param c the component to create a transferable for
317    *
318    * @return a {@link Transferable} that can be used to export data
319    *         from the specified component, or null if the component doesn't
320    *         have a readable property like the transfer handler
321    */
322   protected Transferable createTransferable(JComponent c) 
323   {
324     Transferable transferable = null;
325     if (propertyName != null)
326       {
327         PropertyDescriptor prop = getPropertyDescriptor(c);
328         if (prop != null)
329           transferable = new PropertyTransferable(c, prop);
330       }
331     return transferable;
332   }
333
334   public void exportAsDrag(JComponent c, InputEvent e, int action) 
335     throws NotImplementedException
336   {
337     // TODO: Implement this properly
338   }
339
340   /**
341    * This method is invoked after data has been exported.
342    * Subclasses should implement this method to remove the data that has been
343    * transferred when the action was <code>MOVE</code>.
344    *
345    * The default implementation does nothing because MOVE is not supported.
346    *
347    * @param c the source component
348    * @param data the data that has been transferred or <code>null</code>
349    *        when the action is NONE
350    * @param action the action that has been performed
351    */
352   protected void exportDone(JComponent c, Transferable data, int action)
353   {
354     // Nothing to do in the default implementation.
355   }
356
357   /**
358    * Exports the property of the component <code>c</code> that was
359    * specified for this TransferHandler to the clipboard, performing
360    * the specified action.
361    *
362    * This will check if the action is allowed by calling
363    * {@link #getSourceActions(JComponent)}. If the action is not allowed,
364    * then no export is performed.
365    *
366    * In either case the method {@link #exportDone} will be called with
367    * the action that has been performed, or {@link #NONE} if the action
368    * was not allowed or could otherwise not be completed.
369    * Any IllegalStateException that is thrown by the Clipboard due to
370    * beeing unavailable will be propagated through this method.
371    *
372    * @param c the component from which to export
373    * @param clip the clipboard to which the data will be exported
374    * @param action the action to perform
375    *
376    * @throws IllegalStateException when the clipboard is not available
377    */
378   public void exportToClipboard(JComponent c, Clipboard clip, int action) 
379     throws IllegalStateException
380   {
381     action &= getSourceActions(c);
382     Transferable transferable = createTransferable(c);
383     if (transferable != null && action != NONE)
384       {
385         try
386           {
387             clip.setContents(transferable, null);
388             exportDone(c, transferable, action);
389           }
390         catch (IllegalStateException ex)
391           {
392             exportDone(c, transferable, NONE);
393             throw ex;
394           }
395       }
396     else
397       exportDone(c, null, NONE);
398   } 
399
400   public int getSourceActions(JComponent c)
401   {
402     return sourceActions;
403   }
404
405   public Icon getVisualRepresentation(Transferable t)
406   {
407     return visualRepresentation;
408   }
409
410   /**
411    * Imports the transfer data represented by <code>t</code> into the specified
412    * component <code>c</code> by setting the property of this TransferHandler
413    * on that component. If this succeeds, this method returns
414    * <code>true</code>, otherwise <code>false</code>.
415    * 
416    *
417    * @param c the component to import into
418    * @param t the transfer data to import
419    *
420    * @return <code>true</code> if the transfer succeeds, <code>false</code>
421    *         otherwise
422    */
423   public boolean importData(JComponent c, Transferable t) 
424   {
425     boolean ok = false;
426     PropertyDescriptor prop = getPropertyDescriptor(c);
427     if (prop != null)
428       {
429         Method writer = prop.getWriteMethod();
430         if (writer != null)
431           {
432             Class[] params = writer.getParameterTypes();
433             if (params.length == 1)
434               {
435                 DataFlavor flavor = getPropertyDataFlavor(params[0],
436                                                    t.getTransferDataFlavors());
437                 if (flavor != null)
438                   {
439                     try
440                       {
441                         Object value = t.getTransferData(flavor);
442                         writer.invoke(c, new Object[]{ value });
443                         ok = true;
444                       }
445                     catch (Exception ex)
446                       {
447                         // If anything goes wrong here, do nothing and return
448                         // false;
449                       }
450                   }
451               }
452           }
453       }
454     return ok;
455   }
456
457   /**
458    * Returns the property descriptor for the property of this TransferHandler
459    * in the specified component, or <code>null</code> if no such property
460    * exists in the component. This method only returns properties that are
461    * at least readable (that is, it has a public no-arg getter method).
462    *
463    * @param c the component to check
464    *
465    * @return the property descriptor for the property of this TransferHandler
466    *         in the specified component, or <code>null</code> if no such
467    *         property exists in the component
468    */
469   private PropertyDescriptor getPropertyDescriptor(JComponent c)
470   {
471     PropertyDescriptor prop = null;
472     if (propertyName != null)
473       {
474         Class clazz = c.getClass();
475         BeanInfo beanInfo;
476         try
477           {
478             beanInfo = Introspector.getBeanInfo(clazz);
479           }
480         catch (IntrospectionException ex)
481           {
482             beanInfo = null;
483           }
484         if (beanInfo != null)
485           {
486             PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
487             for (int i = 0; i < props.length && prop == null; i++)
488               {
489                 PropertyDescriptor desc = props[i];
490                 if (desc.getName().equals(propertyName))
491                   {
492                     Method reader = desc.getReadMethod();
493                     if (reader != null)
494                       {
495                         Class[] params = reader.getParameterTypes();
496                         if (params == null || params.length == 0)
497                           prop = desc;
498                       }
499                   }
500               }
501           }
502       }
503     return prop;
504   }
505
506   /**
507    * Searches <code>flavors</code> to find a suitable data flavor that
508    * has the mime type application/x-java-jvm-local-objectref and a
509    * representation class that is the same as the specified <code>clazz</code>.
510    * When no such data flavor is found, this returns <code>null</code>.
511    *
512    * @param clazz the representation class required for the data flavor
513    * @param flavors the possible data flavors
514    *
515    * @return the suitable data flavor or null if none is found
516    */
517   private DataFlavor getPropertyDataFlavor(Class clazz, DataFlavor[] flavors)
518   {
519     DataFlavor found = null;
520     for (int i = 0; i < flavors.length && found == null; i++)
521       {
522         DataFlavor flavor = flavors[i];
523         if (flavor.getPrimaryType().equals("application")
524             && flavor.getSubType().equals("x-java-jvm-local-objectref")
525             && clazz.isAssignableFrom(flavor.getRepresentationClass()))
526           found = flavor;
527       }
528     return found;
529   }
530 }