001 /* TransferHandler.java --
002 Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing;
040
041 import java.awt.Toolkit;
042 import java.awt.datatransfer.Clipboard;
043 import java.awt.datatransfer.DataFlavor;
044 import java.awt.datatransfer.Transferable;
045 import java.awt.datatransfer.UnsupportedFlavorException;
046 import java.awt.dnd.DragGestureEvent;
047 import java.awt.dnd.DragGestureListener;
048 import java.awt.dnd.DragGestureRecognizer;
049 import java.awt.dnd.DragSource;
050 import java.awt.dnd.DragSourceContext;
051 import java.awt.dnd.DragSourceDragEvent;
052 import java.awt.dnd.DragSourceDropEvent;
053 import java.awt.dnd.DragSourceEvent;
054 import java.awt.dnd.DragSourceListener;
055 import java.awt.event.ActionEvent;
056 import java.awt.event.InputEvent;
057 import java.awt.event.MouseEvent;
058 import java.beans.BeanInfo;
059 import java.beans.IntrospectionException;
060 import java.beans.Introspector;
061 import java.beans.PropertyDescriptor;
062 import java.io.IOException;
063 import java.io.Serializable;
064 import java.lang.reflect.Method;
065
066 public class TransferHandler implements Serializable
067 {
068
069 /**
070 * An implementation of {@link Transferable} that can be used to export
071 * data from a component's property.
072 */
073 private static class PropertyTransferable
074 implements Transferable
075 {
076 /**
077 * The component from which we export.
078 */
079 private JComponent component;
080
081 /**
082 * The property descriptor of the property that we handle.
083 */
084 private PropertyDescriptor property;
085
086 /**
087 * Creates a new PropertyTransferable.
088 *
089 * @param c the component from which we export
090 * @param prop the property from which we export
091 */
092 PropertyTransferable(JComponent c, PropertyDescriptor prop)
093 {
094 component = c;
095 property = prop;
096 }
097
098 /**
099 * Returns the data flavors supported by the Transferable.
100 *
101 * @return the data flavors supported by the Transferable
102 */
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 */
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 */
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
170 public TransferAction(String command)
171 {
172 super(command);
173 this.command = command;
174 }
175
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 */
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
225 protected SwingDragGestureRecognizer(DragGestureListener dgl)
226 {
227 super(DragSource.getDefaultDragSource(), null, NONE, dgl);
228 }
229
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
238 protected void registerListeners()
239 {
240 // Nothing to do here.
241 }
242
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
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
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
294 public void dragEnter(DragSourceDragEvent e)
295 {
296 // Nothing to do here.
297 }
298
299 public void dragExit(DragSourceEvent e)
300 {
301 // Nothing to do here.
302 }
303
304 public void dragOver(DragSourceDragEvent e)
305 {
306 // Nothing to do here.
307 }
308
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
345 public static Action getCopyAction()
346 {
347 return copyAction;
348 }
349
350 public static Action getCutAction()
351 {
352 return cutAction;
353 }
354
355 public static Action getPasteAction()
356 {
357 return pasteAction;
358 }
359
360 protected TransferHandler()
361 {
362 this.sourceActions = NONE;
363 }
364
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 */
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 */
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
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 */
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 */
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
524 public int getSourceActions(JComponent c)
525 {
526 return sourceActions;
527 }
528
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 */
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 */
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 */
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 }