1 /* 2 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing; 26 27 import java.awt.*; 28 import java.awt.event.*; 29 import java.awt.datatransfer.*; 30 import java.awt.dnd.*; 31 import java.awt.peer.ComponentPeer; 32 import java.beans.*; 33 import java.lang.reflect.*; 34 import java.io.*; 35 import java.util.TooManyListenersException; 36 import javax.swing.plaf.UIResource; 37 import javax.swing.event.*; 38 import javax.swing.text.JTextComponent; 39 40 import sun.reflect.misc.MethodUtil; 41 import sun.swing.SwingUtilities2; 42 import sun.awt.AppContext; 43 import sun.swing.*; 44 import sun.awt.SunToolkit; 45 46 import java.security.AccessController; 47 import java.security.PrivilegedAction; 48 49 import java.security.AccessControlContext; 50 import java.security.ProtectionDomain; 51 import sun.misc.SharedSecrets; 52 import sun.misc.JavaSecurityAccess; 53 54 import sun.awt.AWTAccessor; 55 56 /** 57 * This class is used to handle the transfer of a <code>Transferable</code> 58 * to and from Swing components. The <code>Transferable</code> is used to 59 * represent data that is exchanged via a cut, copy, or paste 60 * to/from a clipboard. It is also used in drag-and-drop operations 61 * to represent a drag from a component, and a drop to a component. 62 * Swing provides functionality that automatically supports cut, copy, 63 * and paste keyboard bindings that use the functionality provided by 64 * an implementation of this class. Swing also provides functionality 65 * that automatically supports drag and drop that uses the functionality 66 * provided by an implementation of this class. The Swing developer can 67 * concentrate on specifying the semantics of a transfer primarily by setting 68 * the <code>transferHandler</code> property on a Swing component. 69 * <p> 70 * This class is implemented to provide a default behavior of transferring 71 * a component property simply by specifying the name of the property in 72 * the constructor. For example, to transfer the foreground color from 73 * one component to another either via the clipboard or a drag and drop operation 74 * a <code>TransferHandler</code> can be constructed with the string "foreground". The 75 * built in support will use the color returned by <code>getForeground</code> as the source 76 * of the transfer, and <code>setForeground</code> for the target of a transfer. 77 * <p> 78 * Please see 79 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/dnd.html"> 80 * How to Use Drag and Drop and Data Transfer</a>, 81 * a section in <em>The Java Tutorial</em>, for more information. 82 * 83 * 84 * @author Timothy Prinzing 85 * @author Shannon Hickey 86 * @since 1.4 87 */ 88 @SuppressWarnings("serial") 89 public class TransferHandler implements Serializable { 90 91 /** 92 * An <code>int</code> representing no transfer action. 93 */ 94 public static final int NONE = DnDConstants.ACTION_NONE; 95 96 /** 97 * An <code>int</code> representing a "copy" transfer action. 98 * This value is used when data is copied to a clipboard 99 * or copied elsewhere in a drag and drop operation. 100 */ 101 public static final int COPY = DnDConstants.ACTION_COPY; 102 103 /** 104 * An <code>int</code> representing a "move" transfer action. 105 * This value is used when data is moved to a clipboard (i.e. a cut) 106 * or moved elsewhere in a drag and drop operation. 107 */ 108 public static final int MOVE = DnDConstants.ACTION_MOVE; 109 110 /** 111 * An <code>int</code> representing a source action capability of either 112 * "copy" or "move". 113 */ 114 public static final int COPY_OR_MOVE = DnDConstants.ACTION_COPY_OR_MOVE; 115 116 /** 117 * An <code>int</code> representing a "link" transfer action. 118 * This value is used to specify that data should be linked in a drag 119 * and drop operation. 120 * 121 * @see java.awt.dnd.DnDConstants#ACTION_LINK 122 * @since 1.6 123 */ 124 public static final int LINK = DnDConstants.ACTION_LINK; 125 126 /** 127 * An interface to tag things with a {@code getTransferHandler} method. 128 */ 129 interface HasGetTransferHandler { 130 131 /** Returns the {@code TransferHandler}. 132 * 133 * @return The {@code TransferHandler} or {@code null} 134 */ 135 public TransferHandler getTransferHandler(); 136 } 137 138 /** 139 * Represents a location where dropped data should be inserted. 140 * This is a base class that only encapsulates a point. 141 * Components supporting drop may provide subclasses of this 142 * containing more information. 143 * <p> 144 * Developers typically shouldn't create instances of, or extend, this 145 * class. Instead, these are something provided by the DnD 146 * implementation by <code>TransferSupport</code> instances and by 147 * components with a <code>getDropLocation()</code> method. 148 * 149 * @see javax.swing.TransferHandler.TransferSupport#getDropLocation 150 * @since 1.6 151 */ 152 public static class DropLocation { 153 private final Point dropPoint; 154 155 /** 156 * Constructs a drop location for the given point. 157 * 158 * @param dropPoint the drop point, representing the mouse's 159 * current location within the component. 160 * @throws IllegalArgumentException if the point 161 * is <code>null</code> 162 */ 163 protected DropLocation(Point dropPoint) { 164 if (dropPoint == null) { 165 throw new IllegalArgumentException("Point cannot be null"); 166 } 167 168 this.dropPoint = new Point(dropPoint); 169 } 170 171 /** 172 * Returns the drop point, representing the mouse's 173 * current location within the component. 174 * 175 * @return the drop point. 176 */ 177 public final Point getDropPoint() { 178 return new Point(dropPoint); 179 } 180 181 /** 182 * Returns a string representation of this drop location. 183 * This method is intended to be used for debugging purposes, 184 * and the content and format of the returned string may vary 185 * between implementations. 186 * 187 * @return a string representation of this drop location 188 */ 189 public String toString() { 190 return getClass().getName() + "[dropPoint=" + dropPoint + "]"; 191 } 192 }; 193 194 /** 195 * This class encapsulates all relevant details of a clipboard 196 * or drag and drop transfer, and also allows for customizing 197 * aspects of the drag and drop experience. 198 * <p> 199 * The main purpose of this class is to provide the information 200 * needed by a developer to determine the suitability of a 201 * transfer or to import the data contained within. But it also 202 * doubles as a controller for customizing properties during drag 203 * and drop, such as whether or not to show the drop location, 204 * and which drop action to use. 205 * <p> 206 * Developers typically need not create instances of this 207 * class. Instead, they are something provided by the DnD 208 * implementation to certain methods in <code>TransferHandler</code>. 209 * 210 * @see #canImport(TransferHandler.TransferSupport) 211 * @see #importData(TransferHandler.TransferSupport) 212 * @since 1.6 213 */ 214 public final static class TransferSupport { 215 private boolean isDrop; 216 private Component component; 217 218 private boolean showDropLocationIsSet; 219 private boolean showDropLocation; 220 221 private int dropAction = -1; 222 223 /** 224 * The source is a {@code DropTargetDragEvent} or 225 * {@code DropTargetDropEvent} for drops, 226 * and a {@code Transferable} otherwise 227 */ 228 private Object source; 229 230 private DropLocation dropLocation; 231 232 /** 233 * Create a <code>TransferSupport</code> with <code>isDrop()</code> 234 * <code>true</code> for the given component, event, and index. 235 * 236 * @param component the target component 237 * @param event a <code>DropTargetEvent</code> 238 */ 239 private TransferSupport(Component component, 240 DropTargetEvent event) { 241 242 isDrop = true; 243 setDNDVariables(component, event); 244 } 245 246 /** 247 * Create a <code>TransferSupport</code> with <code>isDrop()</code> 248 * <code>false</code> for the given component and 249 * <code>Transferable</code>. 250 * 251 * @param component the target component 252 * @param transferable the transferable 253 * @throws NullPointerException if either parameter 254 * is <code>null</code> 255 */ 256 public TransferSupport(Component component, Transferable transferable) { 257 if (component == null) { 258 throw new NullPointerException("component is null"); 259 } 260 261 if (transferable == null) { 262 throw new NullPointerException("transferable is null"); 263 } 264 265 isDrop = false; 266 this.component = component; 267 this.source = transferable; 268 } 269 270 /** 271 * Allows for a single instance to be reused during DnD. 272 * 273 * @param component the target component 274 * @param event a <code>DropTargetEvent</code> 275 */ 276 private void setDNDVariables(Component component, 277 DropTargetEvent event) { 278 279 assert isDrop; 280 281 this.component = component; 282 this.source = event; 283 dropLocation = null; 284 dropAction = -1; 285 showDropLocationIsSet = false; 286 287 if (source == null) { 288 return; 289 } 290 291 assert source instanceof DropTargetDragEvent || 292 source instanceof DropTargetDropEvent; 293 294 Point p = source instanceof DropTargetDragEvent 295 ? ((DropTargetDragEvent)source).getLocation() 296 : ((DropTargetDropEvent)source).getLocation(); 297 298 if (SunToolkit.isInstanceOf(component, "javax.swing.text.JTextComponent")) { 299 dropLocation = SwingAccessor.getJTextComponentAccessor(). 300 dropLocationForPoint((JTextComponent)component, p); 301 } else if (component instanceof JComponent) { 302 dropLocation = ((JComponent)component).dropLocationForPoint(p); 303 } 304 305 /* 306 * The drop location may be null at this point if the component 307 * doesn't return custom drop locations. In this case, a point-only 308 * drop location will be created lazily when requested. 309 */ 310 } 311 312 /** 313 * Returns whether or not this <code>TransferSupport</code> 314 * represents a drop operation. 315 * 316 * @return <code>true</code> if this is a drop operation, 317 * <code>false</code> otherwise. 318 */ 319 public boolean isDrop() { 320 return isDrop; 321 } 322 323 /** 324 * Returns the target component of this transfer. 325 * 326 * @return the target component 327 */ 328 public Component getComponent() { 329 return component; 330 } 331 332 /** 333 * Checks that this is a drop and throws an 334 * {@code IllegalStateException} if it isn't. 335 * 336 * @throws IllegalStateException if {@code isDrop} is false. 337 */ 338 private void assureIsDrop() { 339 if (!isDrop) { 340 throw new IllegalStateException("Not a drop"); 341 } 342 } 343 344 /** 345 * Returns the current (non-{@code null}) drop location for the component, 346 * when this {@code TransferSupport} represents a drop. 347 * <p> 348 * Note: For components with built-in drop support, this location 349 * will be a subclass of {@code DropLocation} of the same type 350 * returned by that component's {@code getDropLocation} method. 351 * <p> 352 * This method is only for use with drag and drop transfers. 353 * Calling it when {@code isDrop()} is {@code false} results 354 * in an {@code IllegalStateException}. 355 * 356 * @return the drop location 357 * @throws IllegalStateException if this is not a drop 358 * @see #isDrop() 359 */ 360 public DropLocation getDropLocation() { 361 assureIsDrop(); 362 363 if (dropLocation == null) { 364 /* 365 * component didn't give us a custom drop location, 366 * so lazily create a point-only location 367 */ 368 Point p = source instanceof DropTargetDragEvent 369 ? ((DropTargetDragEvent)source).getLocation() 370 : ((DropTargetDropEvent)source).getLocation(); 371 372 dropLocation = new DropLocation(p); 373 } 374 375 return dropLocation; 376 } 377 378 /** 379 * Sets whether or not the drop location should be visually indicated 380 * for the transfer - which must represent a drop. This is applicable to 381 * those components that automatically 382 * show the drop location when appropriate during a drag and drop 383 * operation). By default, the drop location is shown only when the 384 * {@code TransferHandler} has said it can accept the import represented 385 * by this {@code TransferSupport}. With this method you can force the 386 * drop location to always be shown, or always not be shown. 387 * <p> 388 * This method is only for use with drag and drop transfers. 389 * Calling it when {@code isDrop()} is {@code false} results 390 * in an {@code IllegalStateException}. 391 * 392 * @param showDropLocation whether or not to indicate the drop location 393 * @throws IllegalStateException if this is not a drop 394 * @see #isDrop() 395 */ 396 public void setShowDropLocation(boolean showDropLocation) { 397 assureIsDrop(); 398 399 this.showDropLocation = showDropLocation; 400 this.showDropLocationIsSet = true; 401 } 402 403 /** 404 * Sets the drop action for the transfer - which must represent a drop 405 * - to the given action, 406 * instead of the default user drop action. The action must be 407 * supported by the source's drop actions, and must be one 408 * of {@code COPY}, {@code MOVE} or {@code LINK}. 409 * <p> 410 * This method is only for use with drag and drop transfers. 411 * Calling it when {@code isDrop()} is {@code false} results 412 * in an {@code IllegalStateException}. 413 * 414 * @param dropAction the drop action 415 * @throws IllegalStateException if this is not a drop 416 * @throws IllegalArgumentException if an invalid action is specified 417 * @see #getDropAction 418 * @see #getUserDropAction 419 * @see #getSourceDropActions 420 * @see #isDrop() 421 */ 422 public void setDropAction(int dropAction) { 423 assureIsDrop(); 424 425 int action = dropAction & getSourceDropActions(); 426 427 if (!(action == COPY || action == MOVE || action == LINK)) { 428 throw new IllegalArgumentException("unsupported drop action: " + dropAction); 429 } 430 431 this.dropAction = dropAction; 432 } 433 434 /** 435 * Returns the action chosen for the drop, when this 436 * {@code TransferSupport} represents a drop. 437 * <p> 438 * Unless explicitly chosen by way of {@code setDropAction}, 439 * this returns the user drop action provided by 440 * {@code getUserDropAction}. 441 * <p> 442 * You may wish to query this in {@code TransferHandler}'s 443 * {@code importData} method to customize processing based 444 * on the action. 445 * <p> 446 * This method is only for use with drag and drop transfers. 447 * Calling it when {@code isDrop()} is {@code false} results 448 * in an {@code IllegalStateException}. 449 * 450 * @return the action chosen for the drop 451 * @throws IllegalStateException if this is not a drop 452 * @see #setDropAction 453 * @see #getUserDropAction 454 * @see #isDrop() 455 */ 456 public int getDropAction() { 457 return dropAction == -1 ? getUserDropAction() : dropAction; 458 } 459 460 /** 461 * Returns the user drop action for the drop, when this 462 * {@code TransferSupport} represents a drop. 463 * <p> 464 * The user drop action is chosen for a drop as described in the 465 * documentation for {@link java.awt.dnd.DropTargetDragEvent} and 466 * {@link java.awt.dnd.DropTargetDropEvent}. A different action 467 * may be chosen as the drop action by way of the {@code setDropAction} 468 * method. 469 * <p> 470 * You may wish to query this in {@code TransferHandler}'s 471 * {@code canImport} method when determining the suitability of a 472 * drop or when deciding on a drop action to explicitly choose. 473 * <p> 474 * This method is only for use with drag and drop transfers. 475 * Calling it when {@code isDrop()} is {@code false} results 476 * in an {@code IllegalStateException}. 477 * 478 * @return the user drop action 479 * @throws IllegalStateException if this is not a drop 480 * @see #setDropAction 481 * @see #getDropAction 482 * @see #isDrop() 483 */ 484 public int getUserDropAction() { 485 assureIsDrop(); 486 487 return (source instanceof DropTargetDragEvent) 488 ? ((DropTargetDragEvent)source).getDropAction() 489 : ((DropTargetDropEvent)source).getDropAction(); 490 } 491 492 /** 493 * Returns the drag source's supported drop actions, when this 494 * {@code TransferSupport} represents a drop. 495 * <p> 496 * The source actions represent the set of actions supported by the 497 * source of this transfer, and are represented as some bitwise-OR 498 * combination of {@code COPY}, {@code MOVE} and {@code LINK}. 499 * You may wish to query this in {@code TransferHandler}'s 500 * {@code canImport} method when determining the suitability of a drop 501 * or when deciding on a drop action to explicitly choose. To determine 502 * if a particular action is supported by the source, bitwise-AND 503 * the action with the source drop actions, and then compare the result 504 * against the original action. For example: 505 * <pre> 506 * boolean copySupported = (COPY & getSourceDropActions()) == COPY; 507 * </pre> 508 * <p> 509 * This method is only for use with drag and drop transfers. 510 * Calling it when {@code isDrop()} is {@code false} results 511 * in an {@code IllegalStateException}. 512 * 513 * @return the drag source's supported drop actions 514 * @throws IllegalStateException if this is not a drop 515 * @see #isDrop() 516 */ 517 public int getSourceDropActions() { 518 assureIsDrop(); 519 520 return (source instanceof DropTargetDragEvent) 521 ? ((DropTargetDragEvent)source).getSourceActions() 522 : ((DropTargetDropEvent)source).getSourceActions(); 523 } 524 525 /** 526 * Returns the data flavors for this transfer. 527 * 528 * @return the data flavors for this transfer 529 */ 530 public DataFlavor[] getDataFlavors() { 531 if (isDrop) { 532 if (source instanceof DropTargetDragEvent) { 533 return ((DropTargetDragEvent)source).getCurrentDataFlavors(); 534 } else { 535 return ((DropTargetDropEvent)source).getCurrentDataFlavors(); 536 } 537 } 538 539 return ((Transferable)source).getTransferDataFlavors(); 540 } 541 542 /** 543 * Returns whether or not the given data flavor is supported. 544 * 545 * @param df the <code>DataFlavor</code> to test 546 * @return whether or not the given flavor is supported. 547 */ 548 public boolean isDataFlavorSupported(DataFlavor df) { 549 if (isDrop) { 550 if (source instanceof DropTargetDragEvent) { 551 return ((DropTargetDragEvent)source).isDataFlavorSupported(df); 552 } else { 553 return ((DropTargetDropEvent)source).isDataFlavorSupported(df); 554 } 555 } 556 557 return ((Transferable)source).isDataFlavorSupported(df); 558 } 559 560 /** 561 * Returns the <code>Transferable</code> associated with this transfer. 562 * <p> 563 * Note: Unless it is necessary to fetch the <code>Transferable</code> 564 * directly, use one of the other methods on this class to inquire about 565 * the transfer. This may perform better than fetching the 566 * <code>Transferable</code> and asking it directly. 567 * 568 * @return the <code>Transferable</code> associated with this transfer 569 */ 570 public Transferable getTransferable() { 571 if (isDrop) { 572 if (source instanceof DropTargetDragEvent) { 573 return ((DropTargetDragEvent)source).getTransferable(); 574 } else { 575 return ((DropTargetDropEvent)source).getTransferable(); 576 } 577 } 578 579 return (Transferable)source; 580 } 581 } 582 583 584 /** 585 * Returns an {@code Action} that performs cut operations to the 586 * clipboard. When performed, this action operates on the {@code JComponent} 587 * source of the {@code ActionEvent} by invoking {@code exportToClipboard}, 588 * with a {@code MOVE} action, on the component's {@code TransferHandler}. 589 * 590 * @return an {@code Action} for performing cuts to the clipboard 591 */ 592 public static Action getCutAction() { 593 return cutAction; 594 } 595 596 /** 597 * Returns an {@code Action} that performs copy operations to the 598 * clipboard. When performed, this action operates on the {@code JComponent} 599 * source of the {@code ActionEvent} by invoking {@code exportToClipboard}, 600 * with a {@code COPY} action, on the component's {@code TransferHandler}. 601 * 602 * @return an {@code Action} for performing copies to the clipboard 603 */ 604 public static Action getCopyAction() { 605 return copyAction; 606 } 607 608 /** 609 * Returns an {@code Action} that performs paste operations from the 610 * clipboard. When performed, this action operates on the {@code JComponent} 611 * source of the {@code ActionEvent} by invoking {@code importData}, 612 * with the clipboard contents, on the component's {@code TransferHandler}. 613 * 614 * @return an {@code Action} for performing pastes from the clipboard 615 */ 616 public static Action getPasteAction() { 617 return pasteAction; 618 } 619 620 621 /** 622 * Constructs a transfer handler that can transfer a Java Bean property 623 * from one component to another via the clipboard or a drag and drop 624 * operation. 625 * 626 * @param property the name of the property to transfer; this can 627 * be <code>null</code> if there is no property associated with the transfer 628 * handler (a subclass that performs some other kind of transfer, for example) 629 */ 630 public TransferHandler(String property) { 631 propertyName = property; 632 } 633 634 /** 635 * Convenience constructor for subclasses. 636 */ 637 protected TransferHandler() { 638 this(null); 639 } 640 641 642 /** 643 * image for the {@code startDrag} method 644 * 645 * @see java.awt.dnd.DragGestureEvent#startDrag(Cursor dragCursor, Image dragImage, Point imageOffset, Transferable transferable, DragSourceListener dsl) 646 */ 647 private Image dragImage; 648 649 /** 650 * anchor offset for the {@code startDrag} method 651 * 652 * @see java.awt.dnd.DragGestureEvent#startDrag(Cursor dragCursor, Image dragImage, Point imageOffset, Transferable transferable, DragSourceListener dsl) 653 */ 654 private Point dragImageOffset; 655 656 /** 657 * Sets the drag image parameter. The image has to be prepared 658 * for rendering by the moment of the call. The image is stored 659 * by reference because of some performance reasons. 660 * 661 * @param img an image to drag 662 */ 663 public void setDragImage(Image img) { 664 dragImage = img; 665 } 666 667 /** 668 * Returns the drag image. If there is no image to drag, 669 * the returned value is {@code null}. 670 * 671 * @return the reference to the drag image 672 */ 673 public Image getDragImage() { 674 return dragImage; 675 } 676 677 /** 678 * Sets an anchor offset for the image to drag. 679 * It can not be {@code null}. 680 * 681 * @param p a {@code Point} object that corresponds 682 * to coordinates of an anchor offset of the image 683 * relative to the upper left corner of the image 684 */ 685 public void setDragImageOffset(Point p) { 686 dragImageOffset = new Point(p); 687 } 688 689 /** 690 * Returns an anchor offset for the image to drag. 691 * 692 * @return a {@code Point} object that corresponds 693 * to coordinates of an anchor offset of the image 694 * relative to the upper left corner of the image. 695 * The point {@code (0,0)} returns by default. 696 */ 697 public Point getDragImageOffset() { 698 if (dragImageOffset == null) { 699 return new Point(0,0); 700 } 701 return new Point(dragImageOffset); 702 } 703 704 /** 705 * Causes the Swing drag support to be initiated. This is called by 706 * the various UI implementations in the <code>javax.swing.plaf.basic</code> 707 * package if the dragEnabled property is set on the component. 708 * This can be called by custom UI 709 * implementations to use the Swing drag support. This method can also be called 710 * by a Swing extension written as a subclass of <code>JComponent</code> 711 * to take advantage of the Swing drag support. 712 * <p> 713 * The transfer <em>will not necessarily</em> have been completed at the 714 * return of this call (i.e. the call does not block waiting for the drop). 715 * The transfer will take place through the Swing implementation of the 716 * <code>java.awt.dnd</code> mechanism, requiring no further effort 717 * from the developer. The <code>exportDone</code> method will be called 718 * when the transfer has completed. 719 * 720 * @param comp the component holding the data to be transferred; 721 * provided to enable sharing of <code>TransferHandler</code>s 722 * @param e the event that triggered the transfer 723 * @param action the transfer action initially requested; 724 * either {@code COPY}, {@code MOVE} or {@code LINK}; 725 * the DnD system may change the action used during the 726 * course of the drag operation 727 */ 728 public void exportAsDrag(JComponent comp, InputEvent e, int action) { 729 int srcActions = getSourceActions(comp); 730 731 // only mouse events supported for drag operations 732 if (!(e instanceof MouseEvent) 733 // only support known actions 734 || !(action == COPY || action == MOVE || action == LINK) 735 // only support valid source actions 736 || (srcActions & action) == 0) { 737 738 action = NONE; 739 } 740 741 if (action != NONE && !GraphicsEnvironment.isHeadless()) { 742 if (recognizer == null) { 743 recognizer = new SwingDragGestureRecognizer(new DragHandler()); 744 } 745 recognizer.gestured(comp, (MouseEvent)e, srcActions, action); 746 } else { 747 exportDone(comp, null, NONE); 748 } 749 } 750 751 /** 752 * Causes a transfer from the given component to the 753 * given clipboard. This method is called by the default cut and 754 * copy actions registered in a component's action map. 755 * <p> 756 * The transfer will take place using the <code>java.awt.datatransfer</code> 757 * mechanism, requiring no further effort from the developer. Any data 758 * transfer <em>will</em> be complete and the <code>exportDone</code> 759 * method will be called with the action that occurred, before this method 760 * returns. Should the clipboard be unavailable when attempting to place 761 * data on it, the <code>IllegalStateException</code> thrown by 762 * {@link Clipboard#setContents(Transferable, ClipboardOwner)} will 763 * be propogated through this method. However, 764 * <code>exportDone</code> will first be called with an action 765 * of <code>NONE</code> for consistency. 766 * 767 * @param comp the component holding the data to be transferred; 768 * provided to enable sharing of <code>TransferHandler</code>s 769 * @param clip the clipboard to transfer the data into 770 * @param action the transfer action requested; this should 771 * be a value of either <code>COPY</code> or <code>MOVE</code>; 772 * the operation performed is the intersection of the transfer 773 * capabilities given by getSourceActions and the requested action; 774 * the intersection may result in an action of <code>NONE</code> 775 * if the requested action isn't supported 776 * @throws IllegalStateException if the clipboard is currently unavailable 777 * @see Clipboard#setContents(Transferable, ClipboardOwner) 778 */ 779 public void exportToClipboard(JComponent comp, Clipboard clip, int action) 780 throws IllegalStateException { 781 782 if ((action == COPY || action == MOVE) 783 && (getSourceActions(comp) & action) != 0) { 784 785 Transferable t = createTransferable(comp); 786 if (t != null) { 787 try { 788 clip.setContents(t, null); 789 exportDone(comp, t, action); 790 return; 791 } catch (IllegalStateException ise) { 792 exportDone(comp, t, NONE); 793 throw ise; 794 } 795 } 796 } 797 798 exportDone(comp, null, NONE); 799 } 800 801 /** 802 * Causes a transfer to occur from a clipboard or a drag and 803 * drop operation. The <code>Transferable</code> to be 804 * imported and the component to transfer to are contained 805 * within the <code>TransferSupport</code>. 806 * <p> 807 * While the drag and drop implementation calls {@code canImport} 808 * to determine the suitability of a transfer before calling this 809 * method, the implementation of paste does not. As such, it cannot 810 * be assumed that the transfer is acceptable upon a call to 811 * this method for paste. It is recommended that {@code canImport} be 812 * explicitly called to cover this case. 813 * <p> 814 * Note: The <code>TransferSupport</code> object passed to this method 815 * is only valid for the duration of the method call. It is undefined 816 * what values it may contain after this method returns. 817 * 818 * @param support the object containing the details of 819 * the transfer, not <code>null</code>. 820 * @return true if the data was inserted into the component, 821 * false otherwise 822 * @throws NullPointerException if <code>support</code> is {@code null} 823 * @see #canImport(TransferHandler.TransferSupport) 824 * @since 1.6 825 */ 826 public boolean importData(TransferSupport support) { 827 return support.getComponent() instanceof JComponent 828 ? importData((JComponent)support.getComponent(), support.getTransferable()) 829 : false; 830 } 831 832 /** 833 * Causes a transfer to a component from a clipboard or a 834 * DND drop operation. The <code>Transferable</code> represents 835 * the data to be imported into the component. 836 * <p> 837 * Note: Swing now calls the newer version of <code>importData</code> 838 * that takes a <code>TransferSupport</code>, which in turn calls this 839 * method (if the component in the {@code TransferSupport} is a 840 * {@code JComponent}). Developers are encouraged to call and override the 841 * newer version as it provides more information (and is the only 842 * version that supports use with a {@code TransferHandler} set directly 843 * on a {@code JFrame} or other non-{@code JComponent}). 844 * 845 * @param comp the component to receive the transfer; 846 * provided to enable sharing of <code>TransferHandler</code>s 847 * @param t the data to import 848 * @return true if the data was inserted into the component, false otherwise 849 * @see #importData(TransferHandler.TransferSupport) 850 */ 851 public boolean importData(JComponent comp, Transferable t) { 852 PropertyDescriptor prop = getPropertyDescriptor(comp); 853 if (prop != null) { 854 Method writer = prop.getWriteMethod(); 855 if (writer == null) { 856 // read-only property. ignore 857 return false; 858 } 859 Class<?>[] params = writer.getParameterTypes(); 860 if (params.length != 1) { 861 // zero or more than one argument, ignore 862 return false; 863 } 864 DataFlavor flavor = getPropertyDataFlavor(params[0], t.getTransferDataFlavors()); 865 if (flavor != null) { 866 try { 867 Object value = t.getTransferData(flavor); 868 Object[] args = { value }; 869 MethodUtil.invoke(writer, comp, args); 870 return true; 871 } catch (Exception ex) { 872 System.err.println("Invocation failed"); 873 // invocation code 874 } 875 } 876 } 877 return false; 878 } 879 880 /** 881 * This method is called repeatedly during a drag and drop operation 882 * to allow the developer to configure properties of, and to return 883 * the acceptability of transfers; with a return value of {@code true} 884 * indicating that the transfer represented by the given 885 * {@code TransferSupport} (which contains all of the details of the 886 * transfer) is acceptable at the current time, and a value of {@code false} 887 * rejecting the transfer. 888 * <p> 889 * For those components that automatically display a drop location during 890 * drag and drop, accepting the transfer, by default, tells them to show 891 * the drop location. This can be changed by calling 892 * {@code setShowDropLocation} on the {@code TransferSupport}. 893 * <p> 894 * By default, when the transfer is accepted, the chosen drop action is that 895 * picked by the user via their drag gesture. The developer can override 896 * this and choose a different action, from the supported source 897 * actions, by calling {@code setDropAction} on the {@code TransferSupport}. 898 * <p> 899 * On every call to {@code canImport}, the {@code TransferSupport} contains 900 * fresh state. As such, any properties set on it must be set on every 901 * call. Upon a drop, {@code canImport} is called one final time before 902 * calling into {@code importData}. Any state set on the 903 * {@code TransferSupport} during that last call will be available in 904 * {@code importData}. 905 * <p> 906 * This method is not called internally in response to paste operations. 907 * As such, it is recommended that implementations of {@code importData} 908 * explicitly call this method for such cases and that this method 909 * be prepared to return the suitability of paste operations as well. 910 * <p> 911 * Note: The <code>TransferSupport</code> object passed to this method 912 * is only valid for the duration of the method call. It is undefined 913 * what values it may contain after this method returns. 914 * 915 * @param support the object containing the details of 916 * the transfer, not <code>null</code>. 917 * @return <code>true</code> if the import can happen, 918 * <code>false</code> otherwise 919 * @throws NullPointerException if <code>support</code> is {@code null} 920 * @see #importData(TransferHandler.TransferSupport) 921 * @see javax.swing.TransferHandler.TransferSupport#setShowDropLocation 922 * @see javax.swing.TransferHandler.TransferSupport#setDropAction 923 * @since 1.6 924 */ 925 public boolean canImport(TransferSupport support) { 926 return support.getComponent() instanceof JComponent 927 ? canImport((JComponent)support.getComponent(), support.getDataFlavors()) 928 : false; 929 } 930 931 /** 932 * Indicates whether a component will accept an import of the given 933 * set of data flavors prior to actually attempting to import it. 934 * <p> 935 * Note: Swing now calls the newer version of <code>canImport</code> 936 * that takes a <code>TransferSupport</code>, which in turn calls this 937 * method (only if the component in the {@code TransferSupport} is a 938 * {@code JComponent}). Developers are encouraged to call and override the 939 * newer version as it provides more information (and is the only 940 * version that supports use with a {@code TransferHandler} set directly 941 * on a {@code JFrame} or other non-{@code JComponent}). 942 * 943 * @param comp the component to receive the transfer; 944 * provided to enable sharing of <code>TransferHandler</code>s 945 * @param transferFlavors the data formats available 946 * @return true if the data can be inserted into the component, false otherwise 947 * @see #canImport(TransferHandler.TransferSupport) 948 */ 949 public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { 950 PropertyDescriptor prop = getPropertyDescriptor(comp); 951 if (prop != null) { 952 Method writer = prop.getWriteMethod(); 953 if (writer == null) { 954 // read-only property. ignore 955 return false; 956 } 957 Class<?>[] params = writer.getParameterTypes(); 958 if (params.length != 1) { 959 // zero or more than one argument, ignore 960 return false; 961 } 962 DataFlavor flavor = getPropertyDataFlavor(params[0], transferFlavors); 963 if (flavor != null) { 964 return true; 965 } 966 } 967 return false; 968 } 969 970 /** 971 * Returns the type of transfer actions supported by the source; 972 * any bitwise-OR combination of {@code COPY}, {@code MOVE} 973 * and {@code LINK}. 974 * <p> 975 * Some models are not mutable, so a transfer operation of {@code MOVE} 976 * should not be advertised in that case. Returning {@code NONE} 977 * disables transfers from the component. 978 * 979 * @param c the component holding the data to be transferred; 980 * provided to enable sharing of <code>TransferHandler</code>s 981 * @return {@code COPY} if the transfer property can be found, 982 * otherwise returns <code>NONE</code> 983 */ 984 public int getSourceActions(JComponent c) { 985 PropertyDescriptor prop = getPropertyDescriptor(c); 986 if (prop != null) { 987 return COPY; 988 } 989 return NONE; 990 } 991 992 /** 993 * Returns an object that establishes the look of a transfer. This is 994 * useful for both providing feedback while performing a drag operation and for 995 * representing the transfer in a clipboard implementation that has a visual 996 * appearance. The implementation of the <code>Icon</code> interface should 997 * not alter the graphics clip or alpha level. 998 * The icon implementation need not be rectangular or paint all of the 999 * bounding rectangle and logic that calls the icons paint method should 1000 * not assume the all bits are painted. <code>null</code> is a valid return value 1001 * for this method and indicates there is no visual representation provided. 1002 * In that case, the calling logic is free to represent the 1003 * transferable however it wants. 1004 * <p> 1005 * The default Swing logic will not do an alpha blended drag animation if 1006 * the return is <code>null</code>. 1007 * 1008 * @param t the data to be transferred; this value is expected to have been 1009 * created by the <code>createTransferable</code> method 1010 * @return <code>null</code>, indicating 1011 * there is no default visual representation 1012 */ 1013 public Icon getVisualRepresentation(Transferable t) { 1014 return null; 1015 } 1016 1017 /** 1018 * Creates a <code>Transferable</code> to use as the source for 1019 * a data transfer. Returns the representation of the data to 1020 * be transferred, or <code>null</code> if the component's 1021 * property is <code>null</code> 1022 * 1023 * @param c the component holding the data to be transferred; 1024 * provided to enable sharing of <code>TransferHandler</code>s 1025 * @return the representation of the data to be transferred, or 1026 * <code>null</code> if the property associated with <code>c</code> 1027 * is <code>null</code> 1028 * 1029 */ 1030 protected Transferable createTransferable(JComponent c) { 1031 PropertyDescriptor property = getPropertyDescriptor(c); 1032 if (property != null) { 1033 return new PropertyTransferable(property, c); 1034 } 1035 return null; 1036 } 1037 1038 /** 1039 * Invoked after data has been exported. This method should remove 1040 * the data that was transferred if the action was <code>MOVE</code>. 1041 * <p> 1042 * This method is implemented to do nothing since <code>MOVE</code> 1043 * is not a supported action of this implementation 1044 * (<code>getSourceActions</code> does not include <code>MOVE</code>). 1045 * 1046 * @param source the component that was the source of the data 1047 * @param data The data that was transferred or possibly null 1048 * if the action is <code>NONE</code>. 1049 * @param action the actual action that was performed 1050 */ 1051 protected void exportDone(JComponent source, Transferable data, int action) { 1052 } 1053 1054 /** 1055 * Fetches the property descriptor for the property assigned to this transfer 1056 * handler on the given component (transfer handler may be shared). This 1057 * returns <code>null</code> if the property descriptor can't be found 1058 * or there is an error attempting to fetch the property descriptor. 1059 */ 1060 private PropertyDescriptor getPropertyDescriptor(JComponent comp) { 1061 if (propertyName == null) { 1062 return null; 1063 } 1064 Class<?> k = comp.getClass(); 1065 BeanInfo bi; 1066 try { 1067 bi = Introspector.getBeanInfo(k); 1068 } catch (IntrospectionException ex) { 1069 return null; 1070 } 1071 PropertyDescriptor props[] = bi.getPropertyDescriptors(); 1072 for (int i=0; i < props.length; i++) { 1073 if (propertyName.equals(props[i].getName())) { 1074 Method reader = props[i].getReadMethod(); 1075 1076 if (reader != null) { 1077 Class<?>[] params = reader.getParameterTypes(); 1078 1079 if (params == null || params.length == 0) { 1080 // found the desired descriptor 1081 return props[i]; 1082 } 1083 } 1084 } 1085 } 1086 return null; 1087 } 1088 1089 /** 1090 * Fetches the data flavor from the array of possible flavors that 1091 * has data of the type represented by property type. Null is 1092 * returned if there is no match. 1093 */ 1094 private DataFlavor getPropertyDataFlavor(Class<?> k, DataFlavor[] flavors) { 1095 for(int i = 0; i < flavors.length; i++) { 1096 DataFlavor flavor = flavors[i]; 1097 if ("application".equals(flavor.getPrimaryType()) && 1098 "x-java-jvm-local-objectref".equals(flavor.getSubType()) && 1099 k.isAssignableFrom(flavor.getRepresentationClass())) { 1100 1101 return flavor; 1102 } 1103 } 1104 return null; 1105 } 1106 1107 1108 private String propertyName; 1109 private static SwingDragGestureRecognizer recognizer = null; 1110 1111 private static DropTargetListener getDropTargetListener() { 1112 synchronized(DropHandler.class) { 1113 DropHandler handler = 1114 (DropHandler)AppContext.getAppContext().get(DropHandler.class); 1115 1116 if (handler == null) { 1117 handler = new DropHandler(); 1118 AppContext.getAppContext().put(DropHandler.class, handler); 1119 } 1120 1121 return handler; 1122 } 1123 } 1124 1125 static class PropertyTransferable implements Transferable { 1126 1127 PropertyTransferable(PropertyDescriptor p, JComponent c) { 1128 property = p; 1129 component = c; 1130 } 1131 1132 // --- Transferable methods ---------------------------------------------- 1133 1134 /** 1135 * Returns an array of <code>DataFlavor</code> objects indicating the flavors the data 1136 * can be provided in. The array should be ordered according to preference 1137 * for providing the data (from most richly descriptive to least descriptive). 1138 * @return an array of data flavors in which this data can be transferred 1139 */ 1140 public DataFlavor[] getTransferDataFlavors() { 1141 DataFlavor[] flavors = new DataFlavor[1]; 1142 Class<?> propertyType = property.getPropertyType(); 1143 String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=" + propertyType.getName(); 1144 try { 1145 flavors[0] = new DataFlavor(mimeType); 1146 } catch (ClassNotFoundException cnfe) { 1147 flavors = new DataFlavor[0]; 1148 } 1149 return flavors; 1150 } 1151 1152 /** 1153 * Returns whether the specified data flavor is supported for 1154 * this object. 1155 * @param flavor the requested flavor for the data 1156 * @return true if this <code>DataFlavor</code> is supported, 1157 * otherwise false 1158 */ 1159 public boolean isDataFlavorSupported(DataFlavor flavor) { 1160 Class<?> propertyType = property.getPropertyType(); 1161 if ("application".equals(flavor.getPrimaryType()) && 1162 "x-java-jvm-local-objectref".equals(flavor.getSubType()) && 1163 flavor.getRepresentationClass().isAssignableFrom(propertyType)) { 1164 1165 return true; 1166 } 1167 return false; 1168 } 1169 1170 /** 1171 * Returns an object which represents the data to be transferred. The class 1172 * of the object returned is defined by the representation class of the flavor. 1173 * 1174 * @param flavor the requested flavor for the data 1175 * @see DataFlavor#getRepresentationClass 1176 * @exception IOException if the data is no longer available 1177 * in the requested flavor. 1178 * @exception UnsupportedFlavorException if the requested data flavor is 1179 * not supported. 1180 */ 1181 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { 1182 if (! isDataFlavorSupported(flavor)) { 1183 throw new UnsupportedFlavorException(flavor); 1184 } 1185 Method reader = property.getReadMethod(); 1186 Object value = null; 1187 try { 1188 value = MethodUtil.invoke(reader, component, (Object[])null); 1189 } catch (Exception ex) { 1190 throw new IOException("Property read failed: " + property.getName()); 1191 } 1192 return value; 1193 } 1194 1195 JComponent component; 1196 PropertyDescriptor property; 1197 } 1198 1199 /** 1200 * This is the default drop target for drag and drop operations if 1201 * one isn't provided by the developer. <code>DropTarget</code> 1202 * only supports one <code>DropTargetListener</code> and doesn't 1203 * function properly if it isn't set. 1204 * This class sets the one listener as the linkage of drop handling 1205 * to the <code>TransferHandler</code>, and adds support for 1206 * additional listeners which some of the <code>ComponentUI</code> 1207 * implementations install to manipulate a drop insertion location. 1208 */ 1209 static class SwingDropTarget extends DropTarget implements UIResource { 1210 1211 SwingDropTarget(Component c) { 1212 super(c, COPY_OR_MOVE | LINK, null); 1213 try { 1214 // addDropTargetListener is overridden 1215 // we specifically need to add to the superclass 1216 super.addDropTargetListener(getDropTargetListener()); 1217 } catch (TooManyListenersException tmle) {} 1218 } 1219 1220 public void addDropTargetListener(DropTargetListener dtl) throws TooManyListenersException { 1221 // Since the super class only supports one DropTargetListener, 1222 // and we add one from the constructor, we always add to the 1223 // extended list. 1224 if (listenerList == null) { 1225 listenerList = new EventListenerList(); 1226 } 1227 listenerList.add(DropTargetListener.class, dtl); 1228 } 1229 1230 public void removeDropTargetListener(DropTargetListener dtl) { 1231 if (listenerList != null) { 1232 listenerList.remove(DropTargetListener.class, dtl); 1233 } 1234 } 1235 1236 @Override 1237 public void removeNotify(ComponentPeer peer) { 1238 super.removeNotify(peer); 1239 if (dtListener != null && dtListener instanceof DropHandler) { 1240 ((DropHandler)dtListener).cleanup(false); 1241 } 1242 } 1243 1244 // --- DropTargetListener methods (multicast) -------------------------- 1245 1246 public void dragEnter(DropTargetDragEvent e) { 1247 super.dragEnter(e); 1248 if (listenerList != null) { 1249 Object[] listeners = listenerList.getListenerList(); 1250 for (int i = listeners.length-2; i>=0; i-=2) { 1251 if (listeners[i]==DropTargetListener.class) { 1252 ((DropTargetListener)listeners[i+1]).dragEnter(e); 1253 } 1254 } 1255 } 1256 } 1257 1258 public void dragOver(DropTargetDragEvent e) { 1259 super.dragOver(e); 1260 if (listenerList != null) { 1261 Object[] listeners = listenerList.getListenerList(); 1262 for (int i = listeners.length-2; i>=0; i-=2) { 1263 if (listeners[i]==DropTargetListener.class) { 1264 ((DropTargetListener)listeners[i+1]).dragOver(e); 1265 } 1266 } 1267 } 1268 } 1269 1270 public void dragExit(DropTargetEvent e) { 1271 super.dragExit(e); 1272 if (listenerList != null) { 1273 Object[] listeners = listenerList.getListenerList(); 1274 for (int i = listeners.length-2; i>=0; i-=2) { 1275 if (listeners[i]==DropTargetListener.class) { 1276 ((DropTargetListener)listeners[i+1]).dragExit(e); 1277 } 1278 } 1279 } 1280 } 1281 1282 public void drop(DropTargetDropEvent e) { 1283 super.drop(e); 1284 if (listenerList != null) { 1285 Object[] listeners = listenerList.getListenerList(); 1286 for (int i = listeners.length-2; i>=0; i-=2) { 1287 if (listeners[i]==DropTargetListener.class) { 1288 ((DropTargetListener)listeners[i+1]).drop(e); 1289 } 1290 } 1291 } 1292 } 1293 1294 public void dropActionChanged(DropTargetDragEvent e) { 1295 super.dropActionChanged(e); 1296 if (listenerList != null) { 1297 Object[] listeners = listenerList.getListenerList(); 1298 for (int i = listeners.length-2; i>=0; i-=2) { 1299 if (listeners[i]==DropTargetListener.class) { 1300 ((DropTargetListener)listeners[i+1]).dropActionChanged(e); 1301 } 1302 } 1303 } 1304 } 1305 1306 private EventListenerList listenerList; 1307 } 1308 1309 private static class DropHandler implements DropTargetListener, 1310 Serializable, 1311 ActionListener { 1312 1313 private Timer timer; 1314 private Point lastPosition; 1315 private Rectangle outer = new Rectangle(); 1316 private Rectangle inner = new Rectangle(); 1317 private int hysteresis = 10; 1318 1319 private Component component; 1320 private Object state; 1321 private TransferSupport support = 1322 new TransferSupport(null, (DropTargetEvent)null); 1323 1324 private static final int AUTOSCROLL_INSET = 10; 1325 1326 /** 1327 * Update the geometry of the autoscroll region. The geometry is 1328 * maintained as a pair of rectangles. The region can cause 1329 * a scroll if the pointer sits inside it for the duration of the 1330 * timer. The region that causes the timer countdown is the area 1331 * between the two rectangles. 1332 * <p> 1333 * This is implemented to use the visible area of the component 1334 * as the outer rectangle, and the insets are fixed at 10. Should 1335 * the component be smaller than a total of 20 in any direction, 1336 * autoscroll will not occur in that direction. 1337 */ 1338 private void updateAutoscrollRegion(JComponent c) { 1339 // compute the outer 1340 Rectangle visible = c.getVisibleRect(); 1341 outer.setBounds(visible.x, visible.y, visible.width, visible.height); 1342 1343 // compute the insets 1344 Insets i = new Insets(0, 0, 0, 0); 1345 if (c instanceof Scrollable) { 1346 int minSize = 2 * AUTOSCROLL_INSET; 1347 1348 if (visible.width >= minSize) { 1349 i.left = i.right = AUTOSCROLL_INSET; 1350 } 1351 1352 if (visible.height >= minSize) { 1353 i.top = i.bottom = AUTOSCROLL_INSET; 1354 } 1355 } 1356 1357 // set the inner from the insets 1358 inner.setBounds(visible.x + i.left, 1359 visible.y + i.top, 1360 visible.width - (i.left + i.right), 1361 visible.height - (i.top + i.bottom)); 1362 } 1363 1364 /** 1365 * Perform an autoscroll operation. This is implemented to scroll by the 1366 * unit increment of the Scrollable using scrollRectToVisible. If the 1367 * cursor is in a corner of the autoscroll region, more than one axis will 1368 * scroll. 1369 */ 1370 private void autoscroll(JComponent c, Point pos) { 1371 if (c instanceof Scrollable) { 1372 Scrollable s = (Scrollable) c; 1373 if (pos.y < inner.y) { 1374 // scroll upward 1375 int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1); 1376 Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy); 1377 c.scrollRectToVisible(r); 1378 } else if (pos.y > (inner.y + inner.height)) { 1379 // scroll downard 1380 int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1); 1381 Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy); 1382 c.scrollRectToVisible(r); 1383 } 1384 1385 if (pos.x < inner.x) { 1386 // scroll left 1387 int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1); 1388 Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height); 1389 c.scrollRectToVisible(r); 1390 } else if (pos.x > (inner.x + inner.width)) { 1391 // scroll right 1392 int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1); 1393 Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height); 1394 c.scrollRectToVisible(r); 1395 } 1396 } 1397 } 1398 1399 /** 1400 * Initializes the internal properties if they haven't been already 1401 * inited. This is done lazily to avoid loading of desktop properties. 1402 */ 1403 private void initPropertiesIfNecessary() { 1404 if (timer == null) { 1405 Toolkit t = Toolkit.getDefaultToolkit(); 1406 Integer prop; 1407 1408 prop = (Integer) 1409 t.getDesktopProperty("DnD.Autoscroll.interval"); 1410 1411 timer = new Timer(prop == null ? 100 : prop.intValue(), this); 1412 1413 prop = (Integer) 1414 t.getDesktopProperty("DnD.Autoscroll.initialDelay"); 1415 1416 timer.setInitialDelay(prop == null ? 100 : prop.intValue()); 1417 1418 prop = (Integer) 1419 t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis"); 1420 1421 if (prop != null) { 1422 hysteresis = prop.intValue(); 1423 } 1424 } 1425 } 1426 1427 /** 1428 * The timer fired, perform autoscroll if the pointer is within the 1429 * autoscroll region. 1430 * <P> 1431 * @param e the <code>ActionEvent</code> 1432 */ 1433 public void actionPerformed(ActionEvent e) { 1434 updateAutoscrollRegion((JComponent)component); 1435 if (outer.contains(lastPosition) && !inner.contains(lastPosition)) { 1436 autoscroll((JComponent)component, lastPosition); 1437 } 1438 } 1439 1440 // --- DropTargetListener methods ----------------------------------- 1441 1442 private void setComponentDropLocation(TransferSupport support, 1443 boolean forDrop) { 1444 1445 DropLocation dropLocation = (support == null) 1446 ? null 1447 : support.getDropLocation(); 1448 1449 if (SunToolkit.isInstanceOf(component, "javax.swing.text.JTextComponent")) { 1450 state = SwingAccessor.getJTextComponentAccessor(). 1451 setDropLocation((JTextComponent)component, dropLocation, state, forDrop); 1452 } else if (component instanceof JComponent) { 1453 state = ((JComponent)component).setDropLocation(dropLocation, state, forDrop); 1454 } 1455 } 1456 1457 private void handleDrag(DropTargetDragEvent e) { 1458 TransferHandler importer = 1459 ((HasGetTransferHandler)component).getTransferHandler(); 1460 1461 if (importer == null) { 1462 e.rejectDrag(); 1463 setComponentDropLocation(null, false); 1464 return; 1465 } 1466 1467 support.setDNDVariables(component, e); 1468 boolean canImport = importer.canImport(support); 1469 1470 if (canImport) { 1471 e.acceptDrag(support.getDropAction()); 1472 } else { 1473 e.rejectDrag(); 1474 } 1475 1476 boolean showLocation = support.showDropLocationIsSet ? 1477 support.showDropLocation : 1478 canImport; 1479 1480 setComponentDropLocation(showLocation ? support : null, false); 1481 } 1482 1483 public void dragEnter(DropTargetDragEvent e) { 1484 state = null; 1485 component = e.getDropTargetContext().getComponent(); 1486 1487 handleDrag(e); 1488 1489 if (component instanceof JComponent) { 1490 lastPosition = e.getLocation(); 1491 updateAutoscrollRegion((JComponent)component); 1492 initPropertiesIfNecessary(); 1493 } 1494 } 1495 1496 public void dragOver(DropTargetDragEvent e) { 1497 handleDrag(e); 1498 1499 if (!(component instanceof JComponent)) { 1500 return; 1501 } 1502 1503 Point p = e.getLocation(); 1504 1505 if (Math.abs(p.x - lastPosition.x) > hysteresis 1506 || Math.abs(p.y - lastPosition.y) > hysteresis) { 1507 // no autoscroll 1508 if (timer.isRunning()) timer.stop(); 1509 } else { 1510 if (!timer.isRunning()) timer.start(); 1511 } 1512 1513 lastPosition = p; 1514 } 1515 1516 public void dragExit(DropTargetEvent e) { 1517 cleanup(false); 1518 } 1519 1520 public void drop(DropTargetDropEvent e) { 1521 TransferHandler importer = 1522 ((HasGetTransferHandler)component).getTransferHandler(); 1523 1524 if (importer == null) { 1525 e.rejectDrop(); 1526 cleanup(false); 1527 return; 1528 } 1529 1530 support.setDNDVariables(component, e); 1531 boolean canImport = importer.canImport(support); 1532 1533 if (canImport) { 1534 e.acceptDrop(support.getDropAction()); 1535 1536 boolean showLocation = support.showDropLocationIsSet ? 1537 support.showDropLocation : 1538 canImport; 1539 1540 setComponentDropLocation(showLocation ? support : null, false); 1541 1542 boolean success; 1543 1544 try { 1545 success = importer.importData(support); 1546 } catch (RuntimeException re) { 1547 success = false; 1548 } 1549 1550 e.dropComplete(success); 1551 cleanup(success); 1552 } else { 1553 e.rejectDrop(); 1554 cleanup(false); 1555 } 1556 } 1557 1558 public void dropActionChanged(DropTargetDragEvent e) { 1559 /* 1560 * Work-around for Linux bug where dropActionChanged 1561 * is called before dragEnter. 1562 */ 1563 if (component == null) { 1564 return; 1565 } 1566 1567 handleDrag(e); 1568 } 1569 1570 private void cleanup(boolean forDrop) { 1571 setComponentDropLocation(null, forDrop); 1572 if (component instanceof JComponent) { 1573 ((JComponent)component).dndDone(); 1574 } 1575 1576 if (timer != null) { 1577 timer.stop(); 1578 } 1579 1580 state = null; 1581 component = null; 1582 lastPosition = null; 1583 } 1584 } 1585 1586 /** 1587 * This is the default drag handler for drag and drop operations that 1588 * use the <code>TransferHandler</code>. 1589 */ 1590 private static class DragHandler implements DragGestureListener, DragSourceListener { 1591 1592 private boolean scrolls; 1593 1594 // --- DragGestureListener methods ----------------------------------- 1595 1596 /** 1597 * a Drag gesture has been recognized 1598 */ 1599 public void dragGestureRecognized(DragGestureEvent dge) { 1600 JComponent c = (JComponent) dge.getComponent(); 1601 TransferHandler th = c.getTransferHandler(); 1602 Transferable t = th.createTransferable(c); 1603 if (t != null) { 1604 scrolls = c.getAutoscrolls(); 1605 c.setAutoscrolls(false); 1606 try { 1607 Image im = th.getDragImage(); 1608 if (im == null) { 1609 dge.startDrag(null, t, this); 1610 } else { 1611 dge.startDrag(null, im, th.getDragImageOffset(), t, this); 1612 } 1613 return; 1614 } catch (RuntimeException re) { 1615 c.setAutoscrolls(scrolls); 1616 } 1617 } 1618 1619 th.exportDone(c, t, NONE); 1620 } 1621 1622 // --- DragSourceListener methods ----------------------------------- 1623 1624 /** 1625 * as the hotspot enters a platform dependent drop site 1626 */ 1627 public void dragEnter(DragSourceDragEvent dsde) { 1628 } 1629 1630 /** 1631 * as the hotspot moves over a platform dependent drop site 1632 */ 1633 public void dragOver(DragSourceDragEvent dsde) { 1634 } 1635 1636 /** 1637 * as the hotspot exits a platform dependent drop site 1638 */ 1639 public void dragExit(DragSourceEvent dsde) { 1640 } 1641 1642 /** 1643 * as the operation completes 1644 */ 1645 public void dragDropEnd(DragSourceDropEvent dsde) { 1646 DragSourceContext dsc = dsde.getDragSourceContext(); 1647 JComponent c = (JComponent)dsc.getComponent(); 1648 if (dsde.getDropSuccess()) { 1649 c.getTransferHandler().exportDone(c, dsc.getTransferable(), dsde.getDropAction()); 1650 } else { 1651 c.getTransferHandler().exportDone(c, dsc.getTransferable(), NONE); 1652 } 1653 c.setAutoscrolls(scrolls); 1654 } 1655 1656 public void dropActionChanged(DragSourceDragEvent dsde) { 1657 } 1658 } 1659 1660 private static class SwingDragGestureRecognizer extends DragGestureRecognizer { 1661 1662 SwingDragGestureRecognizer(DragGestureListener dgl) { 1663 super(DragSource.getDefaultDragSource(), null, NONE, dgl); 1664 } 1665 1666 void gestured(JComponent c, MouseEvent e, int srcActions, int action) { 1667 setComponent(c); 1668 setSourceActions(srcActions); 1669 appendEvent(e); 1670 fireDragGestureRecognized(action, e.getPoint()); 1671 } 1672 1673 /** 1674 * register this DragGestureRecognizer's Listeners with the Component 1675 */ 1676 protected void registerListeners() { 1677 } 1678 1679 /** 1680 * unregister this DragGestureRecognizer's Listeners with the Component 1681 * 1682 * subclasses must override this method 1683 */ 1684 protected void unregisterListeners() { 1685 } 1686 1687 } 1688 1689 static final Action cutAction = new TransferAction("cut"); 1690 static final Action copyAction = new TransferAction("copy"); 1691 static final Action pasteAction = new TransferAction("paste"); 1692 1693 static class TransferAction extends UIAction implements UIResource { 1694 1695 TransferAction(String name) { 1696 super(name); 1697 } 1698 1699 public boolean isEnabled(Object sender) { 1700 if (sender instanceof JComponent 1701 && ((JComponent)sender).getTransferHandler() == null) { 1702 return false; 1703 } 1704 1705 return true; 1706 } 1707 1708 private static final JavaSecurityAccess javaSecurityAccess = 1709 SharedSecrets.getJavaSecurityAccess(); 1710 1711 public void actionPerformed(final ActionEvent e) { 1712 final Object src = e.getSource(); 1713 1714 final PrivilegedAction<Void> action = new PrivilegedAction<Void>() { 1715 public Void run() { 1716 actionPerformedImpl(e); 1717 return null; 1718 } 1719 }; 1720 1721 final AccessControlContext stack = AccessController.getContext(); 1722 final AccessControlContext srcAcc = AWTAccessor.getComponentAccessor().getAccessControlContext((Component)src); 1723 final AccessControlContext eventAcc = AWTAccessor.getAWTEventAccessor().getAccessControlContext(e); 1724 1725 if (srcAcc == null) { 1726 javaSecurityAccess.doIntersectionPrivilege(action, stack, eventAcc); 1727 } else { 1728 javaSecurityAccess.doIntersectionPrivilege( 1729 new PrivilegedAction<Void>() { 1730 public Void run() { 1731 javaSecurityAccess.doIntersectionPrivilege(action, eventAcc); 1732 return null; 1733 } 1734 }, stack, srcAcc); 1735 } 1736 } 1737 1738 private void actionPerformedImpl(ActionEvent e) { 1739 Object src = e.getSource(); 1740 if (src instanceof JComponent) { 1741 JComponent c = (JComponent) src; 1742 TransferHandler th = c.getTransferHandler(); 1743 Clipboard clipboard = getClipboard(c); 1744 String name = (String) getValue(Action.NAME); 1745 1746 Transferable trans = null; 1747 1748 // any of these calls may throw IllegalStateException 1749 try { 1750 if ((clipboard != null) && (th != null) && (name != null)) { 1751 if ("cut".equals(name)) { 1752 th.exportToClipboard(c, clipboard, MOVE); 1753 } else if ("copy".equals(name)) { 1754 th.exportToClipboard(c, clipboard, COPY); 1755 } else if ("paste".equals(name)) { 1756 trans = clipboard.getContents(null); 1757 } 1758 } 1759 } catch (IllegalStateException ise) { 1760 // clipboard was unavailable 1761 UIManager.getLookAndFeel().provideErrorFeedback(c); 1762 return; 1763 } 1764 1765 // this is a paste action, import data into the component 1766 if (trans != null) { 1767 th.importData(new TransferSupport(c, trans)); 1768 } 1769 } 1770 } 1771 1772 /** 1773 * Returns the clipboard to use for cut/copy/paste. 1774 */ 1775 private Clipboard getClipboard(JComponent c) { 1776 if (SwingUtilities2.canAccessSystemClipboard()) { 1777 return c.getToolkit().getSystemClipboard(); 1778 } 1779 Clipboard clipboard = (Clipboard)sun.awt.AppContext.getAppContext(). 1780 get(SandboxClipboardKey); 1781 if (clipboard == null) { 1782 clipboard = new Clipboard("Sandboxed Component Clipboard"); 1783 sun.awt.AppContext.getAppContext().put(SandboxClipboardKey, 1784 clipboard); 1785 } 1786 return clipboard; 1787 } 1788 1789 /** 1790 * Key used in app context to lookup Clipboard to use if access to 1791 * System clipboard is denied. 1792 */ 1793 private static Object SandboxClipboardKey = new Object(); 1794 1795 } 1796 1797 }