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, null); 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 }