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