0001 /*
0002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing;
0026
0027 import java.awt.*;
0028 import java.awt.event.*;
0029 import java.awt.datatransfer.*;
0030 import java.awt.dnd.*;
0031 import java.beans.*;
0032 import java.lang.reflect.*;
0033 import java.io.*;
0034 import java.util.TooManyListenersException;
0035 import javax.swing.plaf.UIResource;
0036 import javax.swing.event.*;
0037 import javax.swing.text.JTextComponent;
0038
0039 import sun.reflect.misc.MethodUtil;
0040 import sun.swing.SwingUtilities2;
0041 import sun.awt.AppContext;
0042 import sun.swing.*;
0043
0044 /**
0045 * This class is used to handle the transfer of a <code>Transferable</code>
0046 * to and from Swing components. The <code>Transferable</code> is used to
0047 * represent data that is exchanged via a cut, copy, or paste
0048 * to/from a clipboard. It is also used in drag-and-drop operations
0049 * to represent a drag from a component, and a drop to a component.
0050 * Swing provides functionality that automatically supports cut, copy,
0051 * and paste keyboard bindings that use the functionality provided by
0052 * an implementation of this class. Swing also provides functionality
0053 * that automatically supports drag and drop that uses the functionality
0054 * provided by an implementation of this class. The Swing developer can
0055 * concentrate on specifying the semantics of a transfer primarily by setting
0056 * the <code>transferHandler</code> property on a Swing component.
0057 * <p>
0058 * This class is implemented to provide a default behavior of transferring
0059 * a component property simply by specifying the name of the property in
0060 * the constructor. For example, to transfer the foreground color from
0061 * one component to another either via the clipboard or a drag and drop operation
0062 * a <code>TransferHandler</code> can be constructed with the string "foreground". The
0063 * built in support will use the color returned by <code>getForeground</code> as the source
0064 * of the transfer, and <code>setForeground</code> for the target of a transfer.
0065 * <p>
0066 * Please see
0067 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/dnd.html">
0068 * How to Use Drag and Drop and Data Transfer</a>,
0069 * a section in <em>The Java Tutorial</em>, for more information.
0070 *
0071 *
0072 * @author Timothy Prinzing
0073 * @author Shannon Hickey
0074 * @version 1.53 05/05/07
0075 * @since 1.4
0076 */
0077 @SuppressWarnings("serial")
0078 public class TransferHandler implements Serializable {
0079
0080 /**
0081 * An <code>int</code> representing no transfer action.
0082 */
0083 public static final int NONE = DnDConstants.ACTION_NONE;
0084
0085 /**
0086 * An <code>int</code> representing a "copy" transfer action.
0087 * This value is used when data is copied to a clipboard
0088 * or copied elsewhere in a drag and drop operation.
0089 */
0090 public static final int COPY = DnDConstants.ACTION_COPY;
0091
0092 /**
0093 * An <code>int</code> representing a "move" transfer action.
0094 * This value is used when data is moved to a clipboard (i.e. a cut)
0095 * or moved elsewhere in a drag and drop operation.
0096 */
0097 public static final int MOVE = DnDConstants.ACTION_MOVE;
0098
0099 /**
0100 * An <code>int</code> representing a source action capability of either
0101 * "copy" or "move".
0102 */
0103 public static final int COPY_OR_MOVE = DnDConstants.ACTION_COPY_OR_MOVE;
0104
0105 /**
0106 * An <code>int</code> representing a "link" transfer action.
0107 * This value is used to specify that data should be linked in a drag
0108 * and drop operation.
0109 *
0110 * @see java.awt.dnd.DnDConstants#ACTION_LINK
0111 * @since 1.6
0112 */
0113 public static final int LINK = DnDConstants.ACTION_LINK;
0114
0115 /**
0116 * An interface to tag things with a {@code getTransferHandler} method.
0117 */
0118 interface HasGetTransferHandler {
0119
0120 /** Returns the {@code TransferHandler}.
0121 *
0122 * @return The {@code TransferHandler} or {@code null}
0123 */
0124 public TransferHandler getTransferHandler();
0125 }
0126
0127 /**
0128 * Represents a location where dropped data should be inserted.
0129 * This is a base class that only encapsulates a point.
0130 * Components supporting drop may provide subclasses of this
0131 * containing more information.
0132 * <p>
0133 * Developers typically shouldn't create instances of, or extend, this
0134 * class. Instead, these are something provided by the DnD
0135 * implementation by <code>TransferSupport</code> instances and by
0136 * components with a <code>getDropLocation()</code> method.
0137 *
0138 * @see javax.swing.TransferHandler.TransferSupport#getDropLocation
0139 * @since 1.6
0140 */
0141 public static class DropLocation {
0142 private final Point dropPoint;
0143
0144 /**
0145 * Constructs a drop location for the given point.
0146 *
0147 * @param dropPoint the drop point, representing the mouse's
0148 * current location within the component.
0149 * @throws IllegalArgumentException if the point
0150 * is <code>null</code>
0151 */
0152 protected DropLocation(Point dropPoint) {
0153 if (dropPoint == null) {
0154 throw new IllegalArgumentException(
0155 "Point cannot be null");
0156 }
0157
0158 this .dropPoint = new Point(dropPoint);
0159 }
0160
0161 /**
0162 * Returns the drop point, representing the mouse's
0163 * current location within the component.
0164 *
0165 * @return the drop point.
0166 */
0167 public final Point getDropPoint() {
0168 return new Point(dropPoint);
0169 }
0170
0171 /**
0172 * Returns a string representation of this drop location.
0173 * This method is intended to be used for debugging purposes,
0174 * and the content and format of the returned string may vary
0175 * between implementations.
0176 *
0177 * @return a string representation of this drop location
0178 */
0179 public String toString() {
0180 return getClass().getName() + "[dropPoint=" + dropPoint
0181 + "]";
0182 }
0183 };
0184
0185 /**
0186 * This class encapsulates all relevant details of a clipboard
0187 * or drag and drop transfer, and also allows for customizing
0188 * aspects of the drag and drop experience.
0189 * <p>
0190 * The main purpose of this class is to provide the information
0191 * needed by a developer to determine the suitability of a
0192 * transfer or to import the data contained within. But it also
0193 * doubles as a controller for customizing properties during drag
0194 * and drop, such as whether or not to show the drop location,
0195 * and which drop action to use.
0196 * <p>
0197 * Developers typically need not create instances of this
0198 * class. Instead, they are something provided by the DnD
0199 * implementation to certain methods in <code>TransferHandler</code>.
0200 *
0201 * @see #canImport(TransferHandler.TransferSupport)
0202 * @see #importData(TransferHandler.TransferSupport)
0203 * @since 1.6
0204 */
0205 public final static class TransferSupport {
0206 private boolean isDrop;
0207 private Component component;
0208
0209 private boolean showDropLocationIsSet;
0210 private boolean showDropLocation;
0211
0212 private int dropAction = -1;
0213
0214 /**
0215 * The source is a {@code DropTargetDragEvent} or
0216 * {@code DropTargetDropEvent} for drops,
0217 * and a {@code Transferable} otherwise
0218 */
0219 private Object source;
0220
0221 private DropLocation dropLocation;
0222
0223 /**
0224 * Create a <code>TransferSupport</code> with <code>isDrop()</code>
0225 * <code>true</code> for the given component, event, and index.
0226 *
0227 * @param component the target component
0228 * @param event a <code>DropTargetEvent</code>
0229 */
0230 private TransferSupport(Component component,
0231 DropTargetEvent event) {
0232
0233 isDrop = true;
0234 setDNDVariables(component, event);
0235 }
0236
0237 /**
0238 * Create a <code>TransferSupport</code> with <code>isDrop()</code>
0239 * <code>false</code> for the given component and
0240 * <code>Transferable</code>.
0241 *
0242 * @param component the target component
0243 * @param transferable the transferable
0244 * @throws NullPointerException if either parameter
0245 * is <code>null</code>
0246 */
0247 public TransferSupport(Component component,
0248 Transferable transferable) {
0249 if (component == null) {
0250 throw new NullPointerException("component is null");
0251 }
0252
0253 if (transferable == null) {
0254 throw new NullPointerException("transferable is null");
0255 }
0256
0257 isDrop = false;
0258 this .component = component;
0259 this .source = transferable;
0260 }
0261
0262 /**
0263 * Allows for a single instance to be reused during DnD.
0264 *
0265 * @param component the target component
0266 * @param event a <code>DropTargetEvent</code>
0267 */
0268 private void setDNDVariables(Component component,
0269 DropTargetEvent event) {
0270
0271 assert isDrop;
0272
0273 this .component = component;
0274 this .source = event;
0275 dropLocation = null;
0276 dropAction = -1;
0277 showDropLocationIsSet = false;
0278
0279 if (source == null) {
0280 return;
0281 }
0282
0283 assert source instanceof DropTargetDragEvent
0284 || source instanceof DropTargetDropEvent;
0285
0286 Point p = source instanceof DropTargetDragEvent ? ((DropTargetDragEvent) source)
0287 .getLocation()
0288 : ((DropTargetDropEvent) source).getLocation();
0289
0290 if (component instanceof JTextComponent) {
0291 try {
0292 AccessibleMethod method = new AccessibleMethod(
0293 JTextComponent.class,
0294 "dropLocationForPoint", Point.class);
0295
0296 dropLocation = (DropLocation) method
0297 .invokeNoChecked(component, p);
0298 } catch (NoSuchMethodException e) {
0299 throw new AssertionError(
0300 "Couldn't locate method JTextComponent.dropLocationForPoint");
0301 }
0302 } else if (component instanceof JComponent) {
0303 dropLocation = ((JComponent) component)
0304 .dropLocationForPoint(p);
0305 }
0306
0307 /*
0308 * The drop location may be null at this point if the component
0309 * doesn't return custom drop locations. In this case, a point-only
0310 * drop location will be created lazily when requested.
0311 */
0312 }
0313
0314 /**
0315 * Returns whether or not this <code>TransferSupport</code>
0316 * represents a drop operation.
0317 *
0318 * @return <code>true</code> if this is a drop operation,
0319 * <code>false</code> otherwise.
0320 */
0321 public boolean isDrop() {
0322 return isDrop;
0323 }
0324
0325 /**
0326 * Returns the target component of this transfer.
0327 *
0328 * @return the target component
0329 */
0330 public Component getComponent() {
0331 return component;
0332 }
0333
0334 /**
0335 * Checks that this is a drop and throws an
0336 * {@code IllegalStateException} if it isn't.
0337 *
0338 * @throws IllegalStateException if {@code isDrop} is false.
0339 */
0340 private void assureIsDrop() {
0341 if (!isDrop) {
0342 throw new IllegalStateException("Not a drop");
0343 }
0344 }
0345
0346 /**
0347 * Returns the current (non-{@code null}) drop location for the component,
0348 * when this {@code TransferSupport} represents a drop.
0349 * <p>
0350 * Note: For components with built-in drop support, this location
0351 * will be a subclass of {@code DropLocation} of the same type
0352 * returned by that component's {@code getDropLocation} method.
0353 * <p>
0354 * This method is only for use with drag and drop transfers.
0355 * Calling it when {@code isDrop()} is {@code false} results
0356 * in an {@code IllegalStateException}.
0357 *
0358 * @return the drop location
0359 * @throws IllegalStateException if this is not a drop
0360 * @see #isDrop
0361 */
0362 public DropLocation getDropLocation() {
0363 assureIsDrop();
0364
0365 if (dropLocation == null) {
0366 /*
0367 * component didn't give us a custom drop location,
0368 * so lazily create a point-only location
0369 */
0370 Point p = source instanceof DropTargetDragEvent ? ((DropTargetDragEvent) source)
0371 .getLocation()
0372 : ((DropTargetDropEvent) source).getLocation();
0373
0374 dropLocation = new DropLocation(p);
0375 }
0376
0377 return dropLocation;
0378 }
0379
0380 /**
0381 * Sets whether or not the drop location should be visually indicated
0382 * for the transfer - which must represent a drop. This is applicable to
0383 * those components that automatically
0384 * show the drop location when appropriate during a drag and drop
0385 * operation). By default, the drop location is shown only when the
0386 * {@code TransferHandler} has said it can accept the import represented
0387 * by this {@code TransferSupport}. With this method you can force the
0388 * drop location to always be shown, or always not be shown.
0389 * <p>
0390 * This method is only for use with drag and drop transfers.
0391 * Calling it when {@code isDrop()} is {@code false} results
0392 * in an {@code IllegalStateException}.
0393 *
0394 * @param showDropLocation whether or not to indicate the drop location
0395 * @throws IllegalStateException if this is not a drop
0396 * @see #isDrop
0397 */
0398 public void setShowDropLocation(boolean showDropLocation) {
0399 assureIsDrop();
0400
0401 this .showDropLocation = showDropLocation;
0402 this .showDropLocationIsSet = true;
0403 }
0404
0405 /**
0406 * Sets the drop action for the transfer - which must represent a drop
0407 * - to the given action,
0408 * instead of the default user drop action. The action must be
0409 * supported by the source's drop actions, and must be one
0410 * of {@code COPY}, {@code MOVE} or {@code LINK}.
0411 * <p>
0412 * This method is only for use with drag and drop transfers.
0413 * Calling it when {@code isDrop()} is {@code false} results
0414 * in an {@code IllegalStateException}.
0415 *
0416 * @param dropAction the drop action
0417 * @throws IllegalStateException if this is not a drop
0418 * @throws IllegalArgumentException if an invalid action is specified
0419 * @see #getDropAction
0420 * @see #getUserDropAction
0421 * @see #getSourceDropActions
0422 * @see #isDrop
0423 */
0424 public void setDropAction(int dropAction) {
0425 assureIsDrop();
0426
0427 int action = dropAction & getSourceDropActions();
0428
0429 if (!(action == COPY || action == MOVE || action == LINK)) {
0430 throw new IllegalArgumentException(
0431 "unsupported drop action: " + dropAction);
0432 }
0433
0434 this .dropAction = dropAction;
0435 }
0436
0437 /**
0438 * Returns the action chosen for the drop, when this
0439 * {@code TransferSupport} represents a drop.
0440 * <p>
0441 * Unless explicitly chosen by way of {@code setDropAction},
0442 * this returns the user drop action provided by
0443 * {@code getUserDropAction}.
0444 * <p>
0445 * You may wish to query this in {@code TransferHandler}'s
0446 * {@code importData} method to customize processing based
0447 * on the action.
0448 * <p>
0449 * This method is only for use with drag and drop transfers.
0450 * Calling it when {@code isDrop()} is {@code false} results
0451 * in an {@code IllegalStateException}.
0452 *
0453 * @return the action chosen for the drop
0454 * @throws IllegalStateException if this is not a drop
0455 * @see #setDropAction
0456 * @see #getUserDropAction
0457 * @see #isDrop
0458 */
0459 public int getDropAction() {
0460 return dropAction == -1 ? getUserDropAction() : dropAction;
0461 }
0462
0463 /**
0464 * Returns the user drop action for the drop, when this
0465 * {@code TransferSupport} represents a drop.
0466 * <p>
0467 * The user drop action is chosen for a drop as described in the
0468 * documentation for {@link java.awt.dnd.DropTargetDragEvent} and
0469 * {@link java.awt.dnd.DropTargetDropEvent}. A different action
0470 * may be chosen as the drop action by way of the {@code setDropAction}
0471 * method.
0472 * <p>
0473 * You may wish to query this in {@code TransferHandler}'s
0474 * {@code canImport} method when determining the suitability of a
0475 * drop or when deciding on a drop action to explicitly choose.
0476 * <p>
0477 * This method is only for use with drag and drop transfers.
0478 * Calling it when {@code isDrop()} is {@code false} results
0479 * in an {@code IllegalStateException}.
0480 *
0481 * @return the user drop action
0482 * @throws IllegalStateException if this is not a drop
0483 * @see #setDropAction
0484 * @see #getDropAction
0485 * @see #isDrop
0486 */
0487 public int getUserDropAction() {
0488 assureIsDrop();
0489
0490 return (source instanceof DropTargetDragEvent) ? ((DropTargetDragEvent) source)
0491 .getDropAction()
0492 : ((DropTargetDropEvent) source).getDropAction();
0493 }
0494
0495 /**
0496 * Returns the drag source's supported drop actions, when this
0497 * {@code TransferSupport} represents a drop.
0498 * <p>
0499 * The source actions represent the set of actions supported by the
0500 * source of this transfer, and are represented as some bitwise-OR
0501 * combination of {@code COPY}, {@code MOVE} and {@code LINK}.
0502 * You may wish to query this in {@code TransferHandler}'s
0503 * {@code canImport} method when determining the suitability of a drop
0504 * or when deciding on a drop action to explicitly choose. To determine
0505 * if a particular action is supported by the source, bitwise-AND
0506 * the action with the source drop actions, and then compare the result
0507 * against the original action. For example:
0508 * <pre>
0509 * boolean copySupported = (COPY & getSourceDropActions()) == COPY;
0510 * </pre>
0511 * <p>
0512 * This method is only for use with drag and drop transfers.
0513 * Calling it when {@code isDrop()} is {@code false} results
0514 * in an {@code IllegalStateException}.
0515 *
0516 * @return the drag source's supported drop actions
0517 * @throws IllegalStateException if this is not a drop
0518 * @see #isDrop
0519 */
0520 public int getSourceDropActions() {
0521 assureIsDrop();
0522
0523 return (source instanceof DropTargetDragEvent) ? ((DropTargetDragEvent) source)
0524 .getSourceActions()
0525 : ((DropTargetDropEvent) source).getSourceActions();
0526 }
0527
0528 /**
0529 * Returns the data flavors for this transfer.
0530 *
0531 * @return the data flavors for this transfer
0532 */
0533 public DataFlavor[] getDataFlavors() {
0534 if (isDrop) {
0535 if (source instanceof DropTargetDragEvent) {
0536 return ((DropTargetDragEvent) source)
0537 .getCurrentDataFlavors();
0538 } else {
0539 return ((DropTargetDropEvent) source)
0540 .getCurrentDataFlavors();
0541 }
0542 }
0543
0544 return ((Transferable) source).getTransferDataFlavors();
0545 }
0546
0547 /**
0548 * Returns whether or not the given data flavor is supported.
0549 *
0550 * @param df the <code>DataFlavor</code> to test
0551 * @return whether or not the given flavor is supported.
0552 */
0553 public boolean isDataFlavorSupported(DataFlavor df) {
0554 if (isDrop) {
0555 if (source instanceof DropTargetDragEvent) {
0556 return ((DropTargetDragEvent) source)
0557 .isDataFlavorSupported(df);
0558 } else {
0559 return ((DropTargetDropEvent) source)
0560 .isDataFlavorSupported(df);
0561 }
0562 }
0563
0564 return ((Transferable) source).isDataFlavorSupported(df);
0565 }
0566
0567 /**
0568 * Returns the <code>Transferable</code> associated with this transfer.
0569 * <p>
0570 * Note: Unless it is necessary to fetch the <code>Transferable</code>
0571 * directly, use one of the other methods on this class to inquire about
0572 * the transfer. This may perform better than fetching the
0573 * <code>Transferable</code> and asking it directly.
0574 *
0575 * @return the <code>Transferable</code> associated with this transfer
0576 */
0577 public Transferable getTransferable() {
0578 if (isDrop) {
0579 if (source instanceof DropTargetDragEvent) {
0580 return ((DropTargetDragEvent) source)
0581 .getTransferable();
0582 } else {
0583 return ((DropTargetDropEvent) source)
0584 .getTransferable();
0585 }
0586 }
0587
0588 return (Transferable) source;
0589 }
0590 }
0591
0592 /**
0593 * Returns an {@code Action} that performs cut operations to the
0594 * clipboard. When performed, this action operates on the {@code JComponent}
0595 * source of the {@code ActionEvent} by invoking {@code exportToClipboard},
0596 * with a {@code MOVE} action, on the component's {@code TransferHandler}.
0597 *
0598 * @return an {@code Action} for performing cuts to the clipboard
0599 */
0600 public static Action getCutAction() {
0601 return cutAction;
0602 }
0603
0604 /**
0605 * Returns an {@code Action} that performs copy operations to the
0606 * clipboard. When performed, this action operates on the {@code JComponent}
0607 * source of the {@code ActionEvent} by invoking {@code exportToClipboard},
0608 * with a {@code COPY} action, on the component's {@code TransferHandler}.
0609 *
0610 * @return an {@code Action} for performing copies to the clipboard
0611 */
0612 public static Action getCopyAction() {
0613 return copyAction;
0614 }
0615
0616 /**
0617 * Returns an {@code Action} that performs paste operations from the
0618 * clipboard. When performed, this action operates on the {@code JComponent}
0619 * source of the {@code ActionEvent} by invoking {@code importData},
0620 * with the clipboard contents, on the component's {@code TransferHandler}.
0621 *
0622 * @return an {@code Action} for performing pastes from the clipboard
0623 */
0624 public static Action getPasteAction() {
0625 return pasteAction;
0626 }
0627
0628 /**
0629 * Constructs a transfer handler that can transfer a Java Bean property
0630 * from one component to another via the clipboard or a drag and drop
0631 * operation.
0632 *
0633 * @param property the name of the property to transfer; this can
0634 * be <code>null</code> if there is no property associated with the transfer
0635 * handler (a subclass that performs some other kind of transfer, for example)
0636 */
0637 public TransferHandler(String property) {
0638 propertyName = property;
0639 }
0640
0641 /**
0642 * Convenience constructor for subclasses.
0643 */
0644 protected TransferHandler() {
0645 this (null);
0646 }
0647
0648 /**
0649 * Causes the Swing drag support to be initiated. This is called by
0650 * the various UI implementations in the <code>javax.swing.plaf.basic</code>
0651 * package if the dragEnabled property is set on the component.
0652 * This can be called by custom UI
0653 * implementations to use the Swing drag support. This method can also be called
0654 * by a Swing extension written as a subclass of <code>JComponent</code>
0655 * to take advantage of the Swing drag support.
0656 * <p>
0657 * The transfer <em>will not necessarily</em> have been completed at the
0658 * return of this call (i.e. the call does not block waiting for the drop).
0659 * The transfer will take place through the Swing implementation of the
0660 * <code>java.awt.dnd</code> mechanism, requiring no further effort
0661 * from the developer. The <code>exportDone</code> method will be called
0662 * when the transfer has completed.
0663 *
0664 * @param comp the component holding the data to be transferred;
0665 * provided to enable sharing of <code>TransferHandler</code>s
0666 * @param e the event that triggered the transfer
0667 * @param action the transfer action initially requested;
0668 * either {@code COPY}, {@code MOVE} or {@code LINK};
0669 * the DnD system may change the action used during the
0670 * course of the drag operation
0671 */
0672 public void exportAsDrag(JComponent comp, InputEvent e, int action) {
0673 int srcActions = getSourceActions(comp);
0674
0675 // only mouse events supported for drag operations
0676 if (!(e instanceof MouseEvent)
0677 // only support known actions
0678 || !(action == COPY || action == MOVE || action == LINK)
0679 // only support valid source actions
0680 || (srcActions & action) == 0) {
0681
0682 action = NONE;
0683 }
0684
0685 if (action != NONE && !GraphicsEnvironment.isHeadless()) {
0686 if (recognizer == null) {
0687 recognizer = new SwingDragGestureRecognizer(
0688 new DragHandler());
0689 }
0690 recognizer.gestured(comp, (MouseEvent) e, srcActions,
0691 action);
0692 } else {
0693 exportDone(comp, null, NONE);
0694 }
0695 }
0696
0697 /**
0698 * Causes a transfer from the given component to the
0699 * given clipboard. This method is called by the default cut and
0700 * copy actions registered in a component's action map.
0701 * <p>
0702 * The transfer will take place using the <code>java.awt.datatransfer</code>
0703 * mechanism, requiring no further effort from the developer. Any data
0704 * transfer <em>will</em> be complete and the <code>exportDone</code>
0705 * method will be called with the action that occurred, before this method
0706 * returns. Should the clipboard be unavailable when attempting to place
0707 * data on it, the <code>IllegalStateException</code> thrown by
0708 * {@link Clipboard#setContents(Transferable, ClipboardOwner)} will
0709 * be propogated through this method. However,
0710 * <code>exportDone</code> will first be called with an action
0711 * of <code>NONE</code> for consistency.
0712 *
0713 * @param comp the component holding the data to be transferred;
0714 * provided to enable sharing of <code>TransferHandler</code>s
0715 * @param clip the clipboard to transfer the data into
0716 * @param action the transfer action requested; this should
0717 * be a value of either <code>COPY</code> or <code>MOVE</code>;
0718 * the operation performed is the intersection of the transfer
0719 * capabilities given by getSourceActions and the requested action;
0720 * the intersection may result in an action of <code>NONE</code>
0721 * if the requested action isn't supported
0722 * @throws IllegalStateException if the clipboard is currently unavailable
0723 * @see Clipboard#setContents(Transferable, ClipboardOwner)
0724 */
0725 public void exportToClipboard(JComponent comp, Clipboard clip,
0726 int action) throws IllegalStateException {
0727
0728 if ((action == COPY || action == MOVE)
0729 && (getSourceActions(comp) & action) != 0) {
0730
0731 Transferable t = createTransferable(comp);
0732 if (t != null) {
0733 try {
0734 clip.setContents(t, null);
0735 exportDone(comp, t, action);
0736 return;
0737 } catch (IllegalStateException ise) {
0738 exportDone(comp, t, NONE);
0739 throw ise;
0740 }
0741 }
0742 }
0743
0744 exportDone(comp, null, NONE);
0745 }
0746
0747 /**
0748 * Causes a transfer to occur from a clipboard or a drag and
0749 * drop operation. The <code>Transferable</code> to be
0750 * imported and the component to transfer to are contained
0751 * within the <code>TransferSupport</code>.
0752 * <p>
0753 * While the drag and drop implementation calls {@code canImport}
0754 * to determine the suitability of a transfer before calling this
0755 * method, the implementation of paste does not. As such, it cannot
0756 * be assumed that the transfer is acceptable upon a call to
0757 * this method for paste. It is recommended that {@code canImport} be
0758 * explicitly called to cover this case.
0759 * <p>
0760 * Note: The <code>TransferSupport</code> object passed to this method
0761 * is only valid for the duration of the method call. It is undefined
0762 * what values it may contain after this method returns.
0763 *
0764 * @param support the object containing the details of
0765 * the transfer, not <code>null</code>.
0766 * @return true if the data was inserted into the component,
0767 * false otherwise
0768 * @throws NullPointerException if <code>support</code> is {@code null}
0769 * @see #canImport(TransferHandler.TransferSupport)
0770 * @since 1.6
0771 */
0772 public boolean importData(TransferSupport support) {
0773 return support.getComponent() instanceof JComponent ? importData(
0774 (JComponent) support.getComponent(), support
0775 .getTransferable())
0776 : false;
0777 }
0778
0779 /**
0780 * Causes a transfer to a component from a clipboard or a
0781 * DND drop operation. The <code>Transferable</code> represents
0782 * the data to be imported into the component.
0783 * <p>
0784 * Note: Swing now calls the newer version of <code>importData</code>
0785 * that takes a <code>TransferSupport</code>, which in turn calls this
0786 * method (if the component in the {@code TransferSupport} is a
0787 * {@code JComponent}). Developers are encouraged to call and override the
0788 * newer version as it provides more information (and is the only
0789 * version that supports use with a {@code TransferHandler} set directly
0790 * on a {@code JFrame} or other non-{@code JComponent}).
0791 *
0792 * @param comp the component to receive the transfer;
0793 * provided to enable sharing of <code>TransferHandler</code>s
0794 * @param t the data to import
0795 * @return true if the data was inserted into the component, false otherwise
0796 * @see #importData(TransferHandler.TransferSupport)
0797 */
0798 public boolean importData(JComponent comp, Transferable t) {
0799 PropertyDescriptor prop = getPropertyDescriptor(comp);
0800 if (prop != null) {
0801 Method writer = prop.getWriteMethod();
0802 if (writer == null) {
0803 // read-only property. ignore
0804 return false;
0805 }
0806 Class<?>[] params = writer.getParameterTypes();
0807 if (params.length != 1) {
0808 // zero or more than one argument, ignore
0809 return false;
0810 }
0811 DataFlavor flavor = getPropertyDataFlavor(params[0], t
0812 .getTransferDataFlavors());
0813 if (flavor != null) {
0814 try {
0815 Object value = t.getTransferData(flavor);
0816 Object[] args = { value };
0817 MethodUtil.invoke(writer, comp, args);
0818 return true;
0819 } catch (Exception ex) {
0820 System.err.println("Invocation failed");
0821 // invocation code
0822 }
0823 }
0824 }
0825 return false;
0826 }
0827
0828 /**
0829 * This method is called repeatedly during a drag and drop operation
0830 * to allow the developer to configure properties of, and to return
0831 * the acceptability of transfers; with a return value of {@code true}
0832 * indicating that the transfer represented by the given
0833 * {@code TransferSupport} (which contains all of the details of the
0834 * transfer) is acceptable at the current time, and a value of {@code false}
0835 * rejecting the transfer.
0836 * <p>
0837 * For those components that automatically display a drop location during
0838 * drag and drop, accepting the transfer, by default, tells them to show
0839 * the drop location. This can be changed by calling
0840 * {@code setShowDropLocation} on the {@code TransferSupport}.
0841 * <p>
0842 * By default, when the transfer is accepted, the chosen drop action is that
0843 * picked by the user via their drag gesture. The developer can override
0844 * this and choose a different action, from the supported source
0845 * actions, by calling {@code setDropAction} on the {@code TransferSupport}.
0846 * <p>
0847 * On every call to {@code canImport}, the {@code TransferSupport} contains
0848 * fresh state. As such, any properties set on it must be set on every
0849 * call. Upon a drop, {@code canImport} is called one final time before
0850 * calling into {@code importData}. Any state set on the
0851 * {@code TransferSupport} during that last call will be available in
0852 * {@code importData}.
0853 * <p>
0854 * This method is not called internally in response to paste operations.
0855 * As such, it is recommended that implementations of {@code importData}
0856 * explicitly call this method for such cases and that this method
0857 * be prepared to return the suitability of paste operations as well.
0858 * <p>
0859 * Note: The <code>TransferSupport</code> object passed to this method
0860 * is only valid for the duration of the method call. It is undefined
0861 * what values it may contain after this method returns.
0862 *
0863 * @param support the object containing the details of
0864 * the transfer, not <code>null</code>.
0865 * @return <code>true</code> if the import can happen,
0866 * <code>false</code> otherwise
0867 * @throws NullPointerException if <code>support</code> is {@code null}
0868 * @see #importData(TransferHandler.TransferSupport)
0869 * @see javax.swing.TransferHandler.TransferSupport#setShowDropLocation
0870 * @see javax.swing.TransferHandler.TransferSupport#setDropAction
0871 * @since 1.6
0872 */
0873 public boolean canImport(TransferSupport support) {
0874 return support.getComponent() instanceof JComponent ? canImport(
0875 (JComponent) support.getComponent(), support
0876 .getDataFlavors())
0877 : false;
0878 }
0879
0880 /**
0881 * Indicates whether a component will accept an import of the given
0882 * set of data flavors prior to actually attempting to import it.
0883 * <p>
0884 * Note: Swing now calls the newer version of <code>canImport</code>
0885 * that takes a <code>TransferSupport</code>, which in turn calls this
0886 * method (only if the component in the {@code TransferSupport} is a
0887 * {@code JComponent}). Developers are encouraged to call and override the
0888 * newer version as it provides more information (and is the only
0889 * version that supports use with a {@code TransferHandler} set directly
0890 * on a {@code JFrame} or other non-{@code JComponent}).
0891 *
0892 * @param comp the component to receive the transfer;
0893 * provided to enable sharing of <code>TransferHandler</code>s
0894 * @param transferFlavors the data formats available
0895 * @return true if the data can be inserted into the component, false otherwise
0896 * @see #canImport(TransferHandler.TransferSupport)
0897 */
0898 public boolean canImport(JComponent comp,
0899 DataFlavor[] transferFlavors) {
0900 PropertyDescriptor prop = getPropertyDescriptor(comp);
0901 if (prop != null) {
0902 Method writer = prop.getWriteMethod();
0903 if (writer == null) {
0904 // read-only property. ignore
0905 return false;
0906 }
0907 Class<?>[] params = writer.getParameterTypes();
0908 if (params.length != 1) {
0909 // zero or more than one argument, ignore
0910 return false;
0911 }
0912 DataFlavor flavor = getPropertyDataFlavor(params[0],
0913 transferFlavors);
0914 if (flavor != null) {
0915 return true;
0916 }
0917 }
0918 return false;
0919 }
0920
0921 /**
0922 * Returns the type of transfer actions supported by the source;
0923 * any bitwise-OR combination of {@code COPY}, {@code MOVE}
0924 * and {@code LINK}.
0925 * <p>
0926 * Some models are not mutable, so a transfer operation of {@code MOVE}
0927 * should not be advertised in that case. Returning {@code NONE}
0928 * disables transfers from the component.
0929 *
0930 * @param c the component holding the data to be transferred;
0931 * provided to enable sharing of <code>TransferHandler</code>s
0932 * @return {@code COPY} if the transfer property can be found,
0933 * otherwise returns <code>NONE</code>
0934 */
0935 public int getSourceActions(JComponent c) {
0936 PropertyDescriptor prop = getPropertyDescriptor(c);
0937 if (prop != null) {
0938 return COPY;
0939 }
0940 return NONE;
0941 }
0942
0943 /**
0944 * Returns an object that establishes the look of a transfer. This is
0945 * useful for both providing feedback while performing a drag operation and for
0946 * representing the transfer in a clipboard implementation that has a visual
0947 * appearance. The implementation of the <code>Icon</code> interface should
0948 * not alter the graphics clip or alpha level.
0949 * The icon implementation need not be rectangular or paint all of the
0950 * bounding rectangle and logic that calls the icons paint method should
0951 * not assume the all bits are painted. <code>null</code> is a valid return value
0952 * for this method and indicates there is no visual representation provided.
0953 * In that case, the calling logic is free to represent the
0954 * transferable however it wants.
0955 * <p>
0956 * The default Swing logic will not do an alpha blended drag animation if
0957 * the return is <code>null</code>.
0958 *
0959 * @param t the data to be transferred; this value is expected to have been
0960 * created by the <code>createTransferable</code> method
0961 * @return <code>null</code>, indicating
0962 * there is no default visual representation
0963 */
0964 public Icon getVisualRepresentation(Transferable t) {
0965 return null;
0966 }
0967
0968 /**
0969 * Creates a <code>Transferable</code> to use as the source for
0970 * a data transfer. Returns the representation of the data to
0971 * be transferred, or <code>null</code> if the component's
0972 * property is <code>null</code>
0973 *
0974 * @param c the component holding the data to be transferred;
0975 * provided to enable sharing of <code>TransferHandler</code>s
0976 * @return the representation of the data to be transferred, or
0977 * <code>null</code> if the property associated with <code>c</code>
0978 * is <code>null</code>
0979 *
0980 */
0981 protected Transferable createTransferable(JComponent c) {
0982 PropertyDescriptor property = getPropertyDescriptor(c);
0983 if (property != null) {
0984 return new PropertyTransferable(property, c);
0985 }
0986 return null;
0987 }
0988
0989 /**
0990 * Invoked after data has been exported. This method should remove
0991 * the data that was transferred if the action was <code>MOVE</code>.
0992 * <p>
0993 * This method is implemented to do nothing since <code>MOVE</code>
0994 * is not a supported action of this implementation
0995 * (<code>getSourceActions</code> does not include <code>MOVE</code>).
0996 *
0997 * @param source the component that was the source of the data
0998 * @param data The data that was transferred or possibly null
0999 * if the action is <code>NONE</code>.
1000 * @param action the actual action that was performed
1001 */
1002 protected void exportDone(JComponent source, Transferable data,
1003 int action) {
1004 }
1005
1006 /**
1007 * Fetches the property descriptor for the property assigned to this transfer
1008 * handler on the given component (transfer handler may be shared). This
1009 * returns <code>null</code> if the property descriptor can't be found
1010 * or there is an error attempting to fetch the property descriptor.
1011 */
1012 private PropertyDescriptor getPropertyDescriptor(JComponent comp) {
1013 if (propertyName == null) {
1014 return null;
1015 }
1016 Class<?> k = comp.getClass();
1017 BeanInfo bi;
1018 try {
1019 bi = Introspector.getBeanInfo(k);
1020 } catch (IntrospectionException ex) {
1021 return null;
1022 }
1023 PropertyDescriptor props[] = bi.getPropertyDescriptors();
1024 for (int i = 0; i < props.length; i++) {
1025 if (propertyName.equals(props[i].getName())) {
1026 Method reader = props[i].getReadMethod();
1027
1028 if (reader != null) {
1029 Class<?>[] params = reader.getParameterTypes();
1030
1031 if (params == null || params.length == 0) {
1032 // found the desired descriptor
1033 return props[i];
1034 }
1035 }
1036 }
1037 }
1038 return null;
1039 }
1040
1041 /**
1042 * Fetches the data flavor from the array of possible flavors that
1043 * has data of the type represented by property type. Null is
1044 * returned if there is no match.
1045 */
1046 private DataFlavor getPropertyDataFlavor(Class<?> k,
1047 DataFlavor[] flavors) {
1048 for (int i = 0; i < flavors.length; i++) {
1049 DataFlavor flavor = flavors[i];
1050 if ("application".equals(flavor.getPrimaryType())
1051 && "x-java-jvm-local-objectref".equals(flavor
1052 .getSubType())
1053 && k.isAssignableFrom(flavor
1054 .getRepresentationClass())) {
1055
1056 return flavor;
1057 }
1058 }
1059 return null;
1060 }
1061
1062 private String propertyName;
1063 private static SwingDragGestureRecognizer recognizer = null;
1064
1065 private static DropTargetListener getDropTargetListener() {
1066 synchronized (DropHandler.class) {
1067 DropHandler handler = (DropHandler) AppContext
1068 .getAppContext().get(DropHandler.class);
1069
1070 if (handler == null) {
1071 handler = new DropHandler();
1072 AppContext.getAppContext().put(DropHandler.class,
1073 handler);
1074 }
1075
1076 return handler;
1077 }
1078 }
1079
1080 static class PropertyTransferable implements Transferable {
1081
1082 PropertyTransferable(PropertyDescriptor p, JComponent c) {
1083 property = p;
1084 component = c;
1085 }
1086
1087 // --- Transferable methods ----------------------------------------------
1088
1089 /**
1090 * Returns an array of <code>DataFlavor</code> objects indicating the flavors the data
1091 * can be provided in. The array should be ordered according to preference
1092 * for providing the data (from most richly descriptive to least descriptive).
1093 * @return an array of data flavors in which this data can be transferred
1094 */
1095 public DataFlavor[] getTransferDataFlavors() {
1096 DataFlavor[] flavors = new DataFlavor[1];
1097 Class<?> propertyType = property.getPropertyType();
1098 String mimeType = DataFlavor.javaJVMLocalObjectMimeType
1099 + ";class=" + propertyType.getName();
1100 try {
1101 flavors[0] = new DataFlavor(mimeType);
1102 } catch (ClassNotFoundException cnfe) {
1103 flavors = new DataFlavor[0];
1104 }
1105 return flavors;
1106 }
1107
1108 /**
1109 * Returns whether the specified data flavor is supported for
1110 * this object.
1111 * @param flavor the requested flavor for the data
1112 * @return true if this <code>DataFlavor</code> is supported,
1113 * otherwise false
1114 */
1115 public boolean isDataFlavorSupported(DataFlavor flavor) {
1116 Class<?> propertyType = property.getPropertyType();
1117 if ("application".equals(flavor.getPrimaryType())
1118 && "x-java-jvm-local-objectref".equals(flavor
1119 .getSubType())
1120 && flavor.getRepresentationClass()
1121 .isAssignableFrom(propertyType)) {
1122
1123 return true;
1124 }
1125 return false;
1126 }
1127
1128 /**
1129 * Returns an object which represents the data to be transferred. The class
1130 * of the object returned is defined by the representation class of the flavor.
1131 *
1132 * @param flavor the requested flavor for the data
1133 * @see DataFlavor#getRepresentationClass
1134 * @exception IOException if the data is no longer available
1135 * in the requested flavor.
1136 * @exception UnsupportedFlavorException if the requested data flavor is
1137 * not supported.
1138 */
1139 public Object getTransferData(DataFlavor flavor)
1140 throws UnsupportedFlavorException, IOException {
1141 if (!isDataFlavorSupported(flavor)) {
1142 throw new UnsupportedFlavorException(flavor);
1143 }
1144 Method reader = property.getReadMethod();
1145 Object value = null;
1146 try {
1147 value = MethodUtil.invoke(reader, component,
1148 (Object[]) null);
1149 } catch (Exception ex) {
1150 throw new IOException("Property read failed: "
1151 + property.getName());
1152 }
1153 return value;
1154 }
1155
1156 JComponent component;
1157 PropertyDescriptor property;
1158 }
1159
1160 /**
1161 * This is the default drop target for drag and drop operations if
1162 * one isn't provided by the developer. <code>DropTarget</code>
1163 * only supports one <code>DropTargetListener</code> and doesn't
1164 * function properly if it isn't set.
1165 * This class sets the one listener as the linkage of drop handling
1166 * to the <code>TransferHandler</code>, and adds support for
1167 * additional listeners which some of the <code>ComponentUI</code>
1168 * implementations install to manipulate a drop insertion location.
1169 */
1170 static class SwingDropTarget extends DropTarget implements
1171 UIResource {
1172
1173 SwingDropTarget(Component c) {
1174 super (c, COPY_OR_MOVE | LINK, null);
1175 try {
1176 // addDropTargetListener is overridden
1177 // we specifically need to add to the superclass
1178 super .addDropTargetListener(getDropTargetListener());
1179 } catch (TooManyListenersException tmle) {
1180 }
1181 }
1182
1183 public void addDropTargetListener(DropTargetListener dtl)
1184 throws TooManyListenersException {
1185 // Since the super class only supports one DropTargetListener,
1186 // and we add one from the constructor, we always add to the
1187 // extended list.
1188 if (listenerList == null) {
1189 listenerList = new EventListenerList();
1190 }
1191 listenerList.add(DropTargetListener.class, dtl);
1192 }
1193
1194 public void removeDropTargetListener(DropTargetListener dtl) {
1195 if (listenerList != null) {
1196 listenerList.remove(DropTargetListener.class, dtl);
1197 }
1198 }
1199
1200 // --- DropTargetListener methods (multicast) --------------------------
1201
1202 public void dragEnter(DropTargetDragEvent e) {
1203 super .dragEnter(e);
1204 if (listenerList != null) {
1205 Object[] listeners = listenerList.getListenerList();
1206 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1207 if (listeners[i] == DropTargetListener.class) {
1208 ((DropTargetListener) listeners[i + 1])
1209 .dragEnter(e);
1210 }
1211 }
1212 }
1213 }
1214
1215 public void dragOver(DropTargetDragEvent e) {
1216 super .dragOver(e);
1217 if (listenerList != null) {
1218 Object[] listeners = listenerList.getListenerList();
1219 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1220 if (listeners[i] == DropTargetListener.class) {
1221 ((DropTargetListener) listeners[i + 1])
1222 .dragOver(e);
1223 }
1224 }
1225 }
1226 }
1227
1228 public void dragExit(DropTargetEvent e) {
1229 super .dragExit(e);
1230 if (listenerList != null) {
1231 Object[] listeners = listenerList.getListenerList();
1232 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1233 if (listeners[i] == DropTargetListener.class) {
1234 ((DropTargetListener) listeners[i + 1])
1235 .dragExit(e);
1236 }
1237 }
1238 }
1239 }
1240
1241 public void drop(DropTargetDropEvent e) {
1242 super .drop(e);
1243 if (listenerList != null) {
1244 Object[] listeners = listenerList.getListenerList();
1245 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1246 if (listeners[i] == DropTargetListener.class) {
1247 ((DropTargetListener) listeners[i + 1]).drop(e);
1248 }
1249 }
1250 }
1251 }
1252
1253 public void dropActionChanged(DropTargetDragEvent e) {
1254 super .dropActionChanged(e);
1255 if (listenerList != null) {
1256 Object[] listeners = listenerList.getListenerList();
1257 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1258 if (listeners[i] == DropTargetListener.class) {
1259 ((DropTargetListener) listeners[i + 1])
1260 .dropActionChanged(e);
1261 }
1262 }
1263 }
1264 }
1265
1266 private EventListenerList listenerList;
1267 }
1268
1269 private static class DropHandler implements DropTargetListener,
1270 Serializable, ActionListener {
1271
1272 private Timer timer;
1273 private Point lastPosition;
1274 private Rectangle outer = new Rectangle();
1275 private Rectangle inner = new Rectangle();
1276 private int hysteresis = 10;
1277
1278 private Component component;
1279 private Object state;
1280 private TransferSupport support = new TransferSupport(null,
1281 (DropTargetEvent) null);
1282
1283 private static final int AUTOSCROLL_INSET = 10;
1284
1285 /**
1286 * Update the geometry of the autoscroll region. The geometry is
1287 * maintained as a pair of rectangles. The region can cause
1288 * a scroll if the pointer sits inside it for the duration of the
1289 * timer. The region that causes the timer countdown is the area
1290 * between the two rectangles.
1291 * <p>
1292 * This is implemented to use the visible area of the component
1293 * as the outer rectangle, and the insets are fixed at 10. Should
1294 * the component be smaller than a total of 20 in any direction,
1295 * autoscroll will not occur in that direction.
1296 */
1297 private void updateAutoscrollRegion(JComponent c) {
1298 // compute the outer
1299 Rectangle visible = c.getVisibleRect();
1300 outer.setBounds(visible.x, visible.y, visible.width,
1301 visible.height);
1302
1303 // compute the insets
1304 Insets i = new Insets(0, 0, 0, 0);
1305 if (c instanceof Scrollable) {
1306 int minSize = 2 * AUTOSCROLL_INSET;
1307
1308 if (visible.width >= minSize) {
1309 i.left = i.right = AUTOSCROLL_INSET;
1310 }
1311
1312 if (visible.height >= minSize) {
1313 i.top = i.bottom = AUTOSCROLL_INSET;
1314 }
1315 }
1316
1317 // set the inner from the insets
1318 inner.setBounds(visible.x + i.left, visible.y + i.top,
1319 visible.width - (i.left + i.right), visible.height
1320 - (i.top + i.bottom));
1321 }
1322
1323 /**
1324 * Perform an autoscroll operation. This is implemented to scroll by the
1325 * unit increment of the Scrollable using scrollRectToVisible. If the
1326 * cursor is in a corner of the autoscroll region, more than one axis will
1327 * scroll.
1328 */
1329 private void autoscroll(JComponent c, Point pos) {
1330 if (c instanceof Scrollable) {
1331 Scrollable s = (Scrollable) c;
1332 if (pos.y < inner.y) {
1333 // scroll upward
1334 int dy = s.getScrollableUnitIncrement(outer,
1335 SwingConstants.VERTICAL, -1);
1336 Rectangle r = new Rectangle(inner.x, outer.y - dy,
1337 inner.width, dy);
1338 c.scrollRectToVisible(r);
1339 } else if (pos.y > (inner.y + inner.height)) {
1340 // scroll downard
1341 int dy = s.getScrollableUnitIncrement(outer,
1342 SwingConstants.VERTICAL, 1);
1343 Rectangle r = new Rectangle(inner.x, outer.y
1344 + outer.height, inner.width, dy);
1345 c.scrollRectToVisible(r);
1346 }
1347
1348 if (pos.x < inner.x) {
1349 // scroll left
1350 int dx = s.getScrollableUnitIncrement(outer,
1351 SwingConstants.HORIZONTAL, -1);
1352 Rectangle r = new Rectangle(outer.x - dx, inner.y,
1353 dx, inner.height);
1354 c.scrollRectToVisible(r);
1355 } else if (pos.x > (inner.x + inner.width)) {
1356 // scroll right
1357 int dx = s.getScrollableUnitIncrement(outer,
1358 SwingConstants.HORIZONTAL, 1);
1359 Rectangle r = new Rectangle(outer.x + outer.width,
1360 inner.y, dx, inner.height);
1361 c.scrollRectToVisible(r);
1362 }
1363 }
1364 }
1365
1366 /**
1367 * Initializes the internal properties if they haven't been already
1368 * inited. This is done lazily to avoid loading of desktop properties.
1369 */
1370 private void initPropertiesIfNecessary() {
1371 if (timer == null) {
1372 Toolkit t = Toolkit.getDefaultToolkit();
1373 Integer prop;
1374
1375 prop = (Integer) t
1376 .getDesktopProperty("DnD.Autoscroll.interval");
1377
1378 timer = new Timer(prop == null ? 100 : prop.intValue(),
1379 this );
1380
1381 prop = (Integer) t
1382 .getDesktopProperty("DnD.Autoscroll.initialDelay");
1383
1384 timer.setInitialDelay(prop == null ? 100 : prop
1385 .intValue());
1386
1387 prop = (Integer) t
1388 .getDesktopProperty("DnD.Autoscroll.cursorHysteresis");
1389
1390 if (prop != null) {
1391 hysteresis = prop.intValue();
1392 }
1393 }
1394 }
1395
1396 /**
1397 * The timer fired, perform autoscroll if the pointer is within the
1398 * autoscroll region.
1399 * <P>
1400 * @param e the <code>ActionEvent</code>
1401 */
1402 public void actionPerformed(ActionEvent e) {
1403 updateAutoscrollRegion((JComponent) component);
1404 if (outer.contains(lastPosition)
1405 && !inner.contains(lastPosition)) {
1406 autoscroll((JComponent) component, lastPosition);
1407 }
1408 }
1409
1410 // --- DropTargetListener methods -----------------------------------
1411
1412 private void setComponentDropLocation(TransferSupport support,
1413 boolean forDrop) {
1414
1415 DropLocation dropLocation = (support == null) ? null
1416 : support.getDropLocation();
1417
1418 if (component instanceof JTextComponent) {
1419 try {
1420 AccessibleMethod method = new AccessibleMethod(
1421 JTextComponent.class, "setDropLocation",
1422 DropLocation.class, Object.class,
1423 Boolean.TYPE);
1424
1425 state = method.invokeNoChecked(component,
1426 dropLocation, state, forDrop);
1427 } catch (NoSuchMethodException e) {
1428 throw new AssertionError(
1429 "Couldn't locate method JTextComponet.setDropLocation");
1430 }
1431 } else if (component instanceof JComponent) {
1432 state = ((JComponent) component).setDropLocation(
1433 dropLocation, state, forDrop);
1434 }
1435 }
1436
1437 private void handleDrag(DropTargetDragEvent e) {
1438 TransferHandler importer = ((HasGetTransferHandler) component)
1439 .getTransferHandler();
1440
1441 if (importer == null) {
1442 e.rejectDrag();
1443 setComponentDropLocation(null, false);
1444 return;
1445 }
1446
1447 support.setDNDVariables(component, e);
1448 boolean canImport = importer.canImport(support);
1449
1450 if (canImport) {
1451 e.acceptDrag(support.getDropAction());
1452 } else {
1453 e.rejectDrag();
1454 }
1455
1456 boolean showLocation = support.showDropLocationIsSet ? support.showDropLocation
1457 : canImport;
1458
1459 setComponentDropLocation(showLocation ? support : null,
1460 false);
1461 }
1462
1463 public void dragEnter(DropTargetDragEvent e) {
1464 state = null;
1465 component = e.getDropTargetContext().getComponent();
1466
1467 handleDrag(e);
1468
1469 if (component instanceof JComponent) {
1470 lastPosition = e.getLocation();
1471 updateAutoscrollRegion((JComponent) component);
1472 initPropertiesIfNecessary();
1473 }
1474 }
1475
1476 public void dragOver(DropTargetDragEvent e) {
1477 handleDrag(e);
1478
1479 if (!(component instanceof JComponent)) {
1480 return;
1481 }
1482
1483 Point p = e.getLocation();
1484
1485 if (Math.abs(p.x - lastPosition.x) > hysteresis
1486 || Math.abs(p.y - lastPosition.y) > hysteresis) {
1487 // no autoscroll
1488 if (timer.isRunning())
1489 timer.stop();
1490 } else {
1491 if (!timer.isRunning())
1492 timer.start();
1493 }
1494
1495 lastPosition = p;
1496 }
1497
1498 public void dragExit(DropTargetEvent e) {
1499 cleanup(false);
1500 }
1501
1502 public void drop(DropTargetDropEvent e) {
1503 TransferHandler importer = ((HasGetTransferHandler) component)
1504 .getTransferHandler();
1505
1506 if (importer == null) {
1507 e.rejectDrop();
1508 cleanup(false);
1509 return;
1510 }
1511
1512 support.setDNDVariables(component, e);
1513 boolean canImport = importer.canImport(support);
1514
1515 if (canImport) {
1516 e.acceptDrop(support.getDropAction());
1517
1518 boolean showLocation = support.showDropLocationIsSet ? support.showDropLocation
1519 : canImport;
1520
1521 setComponentDropLocation(showLocation ? support : null,
1522 false);
1523
1524 boolean success;
1525
1526 try {
1527 success = importer.importData(support);
1528 } catch (RuntimeException re) {
1529 success = false;
1530 }
1531
1532 e.dropComplete(success);
1533 cleanup(success);
1534 } else {
1535 e.rejectDrop();
1536 cleanup(false);
1537 }
1538 }
1539
1540 public void dropActionChanged(DropTargetDragEvent e) {
1541 /*
1542 * Work-around for Linux bug where dropActionChanged
1543 * is called before dragEnter.
1544 */
1545 if (component == null) {
1546 return;
1547 }
1548
1549 handleDrag(e);
1550 }
1551
1552 private void cleanup(boolean forDrop) {
1553 setComponentDropLocation(null, forDrop);
1554 if (component instanceof JComponent) {
1555 ((JComponent) component).dndDone();
1556 }
1557
1558 if (timer != null) {
1559 timer.stop();
1560 }
1561
1562 state = null;
1563 component = null;
1564 lastPosition = null;
1565 }
1566 }
1567
1568 /**
1569 * This is the default drag handler for drag and drop operations that
1570 * use the <code>TransferHandler</code>.
1571 */
1572 private static class DragHandler implements DragGestureListener,
1573 DragSourceListener {
1574
1575 private boolean scrolls;
1576
1577 // --- DragGestureListener methods -----------------------------------
1578
1579 /**
1580 * a Drag gesture has been recognized
1581 */
1582 public void dragGestureRecognized(DragGestureEvent dge) {
1583 JComponent c = (JComponent) dge.getComponent();
1584 TransferHandler th = c.getTransferHandler();
1585 Transferable t = th.createTransferable(c);
1586 if (t != null) {
1587 scrolls = c.getAutoscrolls();
1588 c.setAutoscrolls(false);
1589 try {
1590 dge.startDrag(null, t, this );
1591 return;
1592 } catch (RuntimeException re) {
1593 c.setAutoscrolls(scrolls);
1594 }
1595 }
1596
1597 th.exportDone(c, t, NONE);
1598 }
1599
1600 // --- DragSourceListener methods -----------------------------------
1601
1602 /**
1603 * as the hotspot enters a platform dependent drop site
1604 */
1605 public void dragEnter(DragSourceDragEvent dsde) {
1606 }
1607
1608 /**
1609 * as the hotspot moves over a platform dependent drop site
1610 */
1611 public void dragOver(DragSourceDragEvent dsde) {
1612 }
1613
1614 /**
1615 * as the hotspot exits a platform dependent drop site
1616 */
1617 public void dragExit(DragSourceEvent dsde) {
1618 }
1619
1620 /**
1621 * as the operation completes
1622 */
1623 public void dragDropEnd(DragSourceDropEvent dsde) {
1624 DragSourceContext dsc = dsde.getDragSourceContext();
1625 JComponent c = (JComponent) dsc.getComponent();
1626 if (dsde.getDropSuccess()) {
1627 c.getTransferHandler().exportDone(c,
1628 dsc.getTransferable(), dsde.getDropAction());
1629 } else {
1630 c.getTransferHandler().exportDone(c,
1631 dsc.getTransferable(), NONE);
1632 }
1633 c.setAutoscrolls(scrolls);
1634 }
1635
1636 public void dropActionChanged(DragSourceDragEvent dsde) {
1637 }
1638 }
1639
1640 private static class SwingDragGestureRecognizer extends
1641 DragGestureRecognizer {
1642
1643 SwingDragGestureRecognizer(DragGestureListener dgl) {
1644 super (DragSource.getDefaultDragSource(), null, NONE, dgl);
1645 }
1646
1647 void gestured(JComponent c, MouseEvent e, int srcActions,
1648 int action) {
1649 setComponent(c);
1650 setSourceActions(srcActions);
1651 appendEvent(e);
1652 fireDragGestureRecognized(action, e.getPoint());
1653 }
1654
1655 /**
1656 * register this DragGestureRecognizer's Listeners with the Component
1657 */
1658 protected void registerListeners() {
1659 }
1660
1661 /**
1662 * unregister this DragGestureRecognizer's Listeners with the Component
1663 *
1664 * subclasses must override this method
1665 */
1666 protected void unregisterListeners() {
1667 }
1668
1669 }
1670
1671 static final Action cutAction = new TransferAction("cut");
1672 static final Action copyAction = new TransferAction("copy");
1673 static final Action pasteAction = new TransferAction("paste");
1674
1675 static class TransferAction extends UIAction implements UIResource {
1676
1677 TransferAction(String name) {
1678 super (name);
1679 }
1680
1681 public boolean isEnabled(Object sender) {
1682 if (sender instanceof JComponent
1683 && ((JComponent) sender).getTransferHandler() == null) {
1684 return false;
1685 }
1686
1687 return true;
1688 }
1689
1690 public void actionPerformed(ActionEvent e) {
1691 Object src = e.getSource();
1692 if (src instanceof JComponent) {
1693 JComponent c = (JComponent) src;
1694 TransferHandler th = c.getTransferHandler();
1695 Clipboard clipboard = getClipboard(c);
1696 String name = (String) getValue(Action.NAME);
1697
1698 Transferable trans = null;
1699
1700 // any of these calls may throw IllegalStateException
1701 try {
1702 if ((clipboard != null) && (th != null)
1703 && (name != null)) {
1704 if ("cut".equals(name)) {
1705 th.exportToClipboard(c, clipboard, MOVE);
1706 } else if ("copy".equals(name)) {
1707 th.exportToClipboard(c, clipboard, COPY);
1708 } else if ("paste".equals(name)) {
1709 trans = clipboard.getContents(null);
1710 }
1711 }
1712 } catch (IllegalStateException ise) {
1713 // clipboard was unavailable
1714 UIManager.getLookAndFeel().provideErrorFeedback(c);
1715 return;
1716 }
1717
1718 // this is a paste action, import data into the component
1719 if (trans != null) {
1720 th.importData(new TransferSupport(c, trans));
1721 }
1722 }
1723 }
1724
1725 /**
1726 * Returns the clipboard to use for cut/copy/paste.
1727 */
1728 private Clipboard getClipboard(JComponent c) {
1729 if (SwingUtilities2.canAccessSystemClipboard()) {
1730 return c.getToolkit().getSystemClipboard();
1731 }
1732 Clipboard clipboard = (Clipboard) sun.awt.AppContext
1733 .getAppContext().get(SandboxClipboardKey);
1734 if (clipboard == null) {
1735 clipboard = new Clipboard(
1736 "Sandboxed Component Clipboard");
1737 sun.awt.AppContext.getAppContext().put(
1738 SandboxClipboardKey, clipboard);
1739 }
1740 return clipboard;
1741 }
1742
1743 /**
1744 * Key used in app context to lookup Clipboard to use if access to
1745 * System clipboard is denied.
1746 */
1747 private static Object SandboxClipboardKey = new Object();
1748
1749 }
1750
1751 }
|