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