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