blob: 050ab6d58267d03d227dcf59561d1792a7e71086 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2007 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 */
25
26package java.awt.dnd;
27
28import java.awt.Component;
29import java.awt.Cursor;
30import java.awt.Image;
31import java.awt.Point;
32
33import java.awt.datatransfer.DataFlavor;
34import java.awt.datatransfer.Transferable;
35import java.awt.datatransfer.UnsupportedFlavorException;
36
37import java.awt.dnd.peer.DragSourceContextPeer;
38
39import java.io.IOException;
40import java.io.ObjectOutputStream;
41import java.io.ObjectInputStream;
42import java.io.Serializable;
43
44import java.util.TooManyListenersException;
45
46/**
47 * The <code>DragSourceContext</code> class is responsible for managing the
48 * initiator side of the Drag and Drop protocol. In particular, it is responsible
49 * for managing drag event notifications to the
50 * {@linkplain DragSourceListener DragSourceListeners}
51 * and {@linkplain DragSourceMotionListener DragSourceMotionListeners}, and providing the
52 * {@link Transferable} representing the source data for the drag operation.
53 * <p>
54 * Note that the <code>DragSourceContext</code> itself
55 * implements the <code>DragSourceListener</code> and
56 * <code>DragSourceMotionListener</code> interfaces.
57 * This is to allow the platform peer
58 * (the {@link DragSourceContextPeer} instance)
59 * created by the {@link DragSource} to notify
60 * the <code>DragSourceContext</code> of
61 * state changes in the ongoing operation. This allows the
62 * <code>DragSourceContext</code> object to interpose
63 * itself between the platform and the
64 * listeners provided by the initiator of the drag operation.
65 * <p>
66 * <a name="defaultCursor" />
67 * By default, {@code DragSourceContext} sets the cursor as appropriate
68 * for the current state of the drag and drop operation. For example, if
69 * the user has chosen {@linkplain DnDConstants#ACTION_MOVE the move action},
70 * and the pointer is over a target that accepts
71 * the move action, the default move cursor is shown. When
72 * the pointer is over an area that does not accept the transfer,
73 * the default "no drop" cursor is shown.
74 * <p>
75 * This default handling mechanism is disabled when a custom cursor is set
76 * by the {@link #setCursor} method. When the default handling is disabled,
77 * it becomes the responsibility
78 * of the developer to keep the cursor up to date, by listening
79 * to the {@code DragSource} events and calling the {@code setCursor()} method.
80 * Alternatively, you can provide custom cursor behavior by providing
81 * custom implementations of the {@code DragSource}
82 * and the {@code DragSourceContext} classes.
83 *
84 * @see DragSourceListener
85 * @see DragSourceMotionListener
86 * @see DnDConstants
87 * @since 1.2
88 */
89
90public class DragSourceContext
91 implements DragSourceListener, DragSourceMotionListener, Serializable {
92
93 private static final long serialVersionUID = -115407898692194719L;
94
95 // used by updateCurrentCursor
96
97 /**
98 * An <code>int</code> used by updateCurrentCursor()
99 * indicating that the <code>Cursor</code> should change
100 * to the default (no drop) <code>Cursor</code>.
101 */
102 protected static final int DEFAULT = 0;
103
104 /**
105 * An <code>int</code> used by updateCurrentCursor()
106 * indicating that the <code>Cursor</code>
107 * has entered a <code>DropTarget</code>.
108 */
109 protected static final int ENTER = 1;
110
111 /**
112 * An <code>int</code> used by updateCurrentCursor()
113 * indicating that the <code>Cursor</code> is
114 * over a <code>DropTarget</code>.
115 */
116 protected static final int OVER = 2;
117
118 /**
119 * An <code>int</code> used by updateCurrentCursor()
120 * indicating that the user operation has changed.
121 */
122
123 protected static final int CHANGED = 3;
124
125 /**
126 * Called from <code>DragSource</code>, this constructor creates a new
127 * <code>DragSourceContext</code> given the
128 * <code>DragSourceContextPeer</code> for this Drag, the
129 * <code>DragGestureEvent</code> that triggered the Drag, the initial
130 * <code>Cursor</code> to use for the Drag, an (optional)
131 * <code>Image</code> to display while the Drag is taking place, the offset
132 * of the <code>Image</code> origin from the hotspot at the instant of the
133 * triggering event, the <code>Transferable</code> subject data, and the
134 * <code>DragSourceListener</code> to use during the Drag and Drop
135 * operation.
136 * <br>
137 * If <code>DragSourceContextPeer</code> is <code>null</code>
138 * <code>NullPointerException</code> is thrown.
139 * <br>
140 * If <code>DragGestureEvent</code> is <code>null</code>
141 * <code>NullPointerException</code> is thrown.
142 * <br>
143 * If <code>Cursor</code> is <code>null</code> no exception is thrown and
144 * the default drag cursor behavior is activated for this drag operation.
145 * <br>
146 * If <code>Image</code> is <code>null</code> no exception is thrown.
147 * <br>
148 * If <code>Image</code> is not <code>null</code> and the offset is
149 * <code>null</code> <code>NullPointerException</code> is thrown.
150 * <br>
151 * If <code>Transferable</code> is <code>null</code>
152 * <code>NullPointerException</code> is thrown.
153 * <br>
154 * If <code>DragSourceListener</code> is <code>null</code> no exception
155 * is thrown.
156 *
157 * @param dscp the <code>DragSourceContextPeer</code> for this drag
158 * @param trigger the triggering event
159 * @param dragCursor the initial {@code Cursor} for this drag operation
160 * or {@code null} for the default cursor handling;
161 * see <a href="DragSourceContext.html#defaultCursor">class level documentation</a>
162 * for more details on the cursor handling mechanism during drag and drop
163 * @param dragImage the <code>Image</code> to drag (or <code>null</code>)
164 * @param offset the offset of the image origin from the hotspot at the
165 * instant of the triggering event
166 * @param t the <code>Transferable</code>
167 * @param dsl the <code>DragSourceListener</code>
168 *
169 * @throws IllegalArgumentException if the <code>Component</code> associated
170 * with the trigger event is <code>null</code>.
171 * @throws IllegalArgumentException if the <code>DragSource</code> for the
172 * trigger event is <code>null</code>.
173 * @throws IllegalArgumentException if the drag action for the
174 * trigger event is <code>DnDConstants.ACTION_NONE</code>.
175 * @throws IllegalArgumentException if the source actions for the
176 * <code>DragGestureRecognizer</code> associated with the trigger
177 * event are equal to <code>DnDConstants.ACTION_NONE</code>.
178 * @throws NullPointerException if dscp, trigger, or t are null, or
179 * if dragImage is non-null and offset is null
180 */
181 public DragSourceContext(DragSourceContextPeer dscp,
182 DragGestureEvent trigger, Cursor dragCursor,
183 Image dragImage, Point offset, Transferable t,
184 DragSourceListener dsl) {
185 if (dscp == null) {
186 throw new NullPointerException("DragSourceContextPeer");
187 }
188
189 if (trigger == null) {
190 throw new NullPointerException("Trigger");
191 }
192
193 if (trigger.getDragSource() == null) {
194 throw new IllegalArgumentException("DragSource");
195 }
196
197 if (trigger.getComponent() == null) {
198 throw new IllegalArgumentException("Component");
199 }
200
201 if (trigger.getSourceAsDragGestureRecognizer().getSourceActions() ==
202 DnDConstants.ACTION_NONE) {
203 throw new IllegalArgumentException("source actions");
204 }
205
206 if (trigger.getDragAction() == DnDConstants.ACTION_NONE) {
207 throw new IllegalArgumentException("no drag action");
208 }
209
210 if (t == null) {
211 throw new NullPointerException("Transferable");
212 }
213
214 if (dragImage != null && offset == null) {
215 throw new NullPointerException("offset");
216 }
217
218 peer = dscp;
219 this.trigger = trigger;
220 cursor = dragCursor;
221 transferable = t;
222 listener = dsl;
223 sourceActions =
224 trigger.getSourceAsDragGestureRecognizer().getSourceActions();
225
226 useCustomCursor = (dragCursor != null);
227
228 updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT);
229 }
230
231 /**
232 * Returns the <code>DragSource</code>
233 * that instantiated this <code>DragSourceContext</code>.
234 *
235 * @return the <code>DragSource</code> that
236 * instantiated this <code>DragSourceContext</code>
237 */
238
239 public DragSource getDragSource() { return trigger.getDragSource(); }
240
241 /**
242 * Returns the <code>Component</code> associated with this
243 * <code>DragSourceContext</code>.
244 *
245 * @return the <code>Component</code> that started the drag
246 */
247
248 public Component getComponent() { return trigger.getComponent(); }
249
250 /**
251 * Returns the <code>DragGestureEvent</code>
252 * that initially triggered the drag.
253 *
254 * @return the Event that triggered the drag
255 */
256
257 public DragGestureEvent getTrigger() { return trigger; }
258
259 /**
260 * Returns a bitwise mask of <code>DnDConstants</code> that
261 * represent the set of drop actions supported by the drag source for the
262 * drag operation associated with this <code>DragSourceContext</code>.
263 *
264 * @return the drop actions supported by the drag source
265 */
266 public int getSourceActions() {
267 return sourceActions;
268 }
269
270 /**
271 * Sets the cursor for this drag operation to the specified
272 * <code>Cursor</code>. If the specified <code>Cursor</code>
273 * is <code>null</code>, the default drag cursor behavior is
274 * activated for this drag operation, otherwise it is deactivated.
275 *
276 * @param c the initial {@code Cursor} for this drag operation,
277 * or {@code null} for the default cursor handling;
278 * see {@linkplain #defaultCursor class
279 * level documentation} for more details
280 * on the cursor handling during drag and drop
281 *
282 */
283
284 public synchronized void setCursor(Cursor c) {
285 useCustomCursor = (c != null);
286 setCursorImpl(c);
287 }
288
289 /**
290 * Returns the current drag <code>Cursor</code>.
291 * <P>
292 * @return the current drag <code>Cursor</code>
293 */
294
295 public Cursor getCursor() { return cursor; }
296
297 /**
298 * Add a <code>DragSourceListener</code> to this
299 * <code>DragSourceContext</code> if one has not already been added.
300 * If a <code>DragSourceListener</code> already exists,
301 * this method throws a <code>TooManyListenersException</code>.
302 * <P>
303 * @param dsl the <code>DragSourceListener</code> to add.
304 * Note that while <code>null</code> is not prohibited,
305 * it is not acceptable as a parameter.
306 * <P>
307 * @throws TooManyListenersException if
308 * a <code>DragSourceListener</code> has already been added
309 */
310
311 public synchronized void addDragSourceListener(DragSourceListener dsl) throws TooManyListenersException {
312 if (dsl == null) return;
313
314 if (equals(dsl)) throw new IllegalArgumentException("DragSourceContext may not be its own listener");
315
316 if (listener != null)
317 throw new TooManyListenersException();
318 else
319 listener = dsl;
320 }
321
322 /**
323 * Removes the specified <code>DragSourceListener</code>
324 * from this <code>DragSourceContext</code>.
325 *
326 * @param dsl the <code>DragSourceListener</code> to remove;
327 * note that while <code>null</code> is not prohibited,
328 * it is not acceptable as a parameter
329 */
330
331 public synchronized void removeDragSourceListener(DragSourceListener dsl) {
332 if (listener != null && listener.equals(dsl)) {
333 listener = null;
334 } else
335 throw new IllegalArgumentException();
336 }
337
338 /**
339 * Notifies the peer that the <code>Transferable</code>'s
340 * <code>DataFlavor</code>s have changed.
341 */
342
343 public void transferablesFlavorsChanged() {
344 if (peer != null) peer.transferablesFlavorsChanged();
345 }
346
347 /**
348 * Calls <code>dragEnter</code> on the
349 * <code>DragSourceListener</code>s registered with this
350 * <code>DragSourceContext</code> and with the associated
351 * <code>DragSource</code>, and passes them the specified
352 * <code>DragSourceDragEvent</code>.
353 *
354 * @param dsde the <code>DragSourceDragEvent</code>
355 */
356 public void dragEnter(DragSourceDragEvent dsde) {
357 DragSourceListener dsl = listener;
358 if (dsl != null) {
359 dsl.dragEnter(dsde);
360 }
361 getDragSource().processDragEnter(dsde);
362
363 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER);
364 }
365
366 /**
367 * Calls <code>dragOver</code> on the
368 * <code>DragSourceListener</code>s registered with this
369 * <code>DragSourceContext</code> and with the associated
370 * <code>DragSource</code>, and passes them the specified
371 * <code>DragSourceDragEvent</code>.
372 *
373 * @param dsde the <code>DragSourceDragEvent</code>
374 */
375 public void dragOver(DragSourceDragEvent dsde) {
376 DragSourceListener dsl = listener;
377 if (dsl != null) {
378 dsl.dragOver(dsde);
379 }
380 getDragSource().processDragOver(dsde);
381
382 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER);
383 }
384
385 /**
386 * Calls <code>dragExit</code> on the
387 * <code>DragSourceListener</code>s registered with this
388 * <code>DragSourceContext</code> and with the associated
389 * <code>DragSource</code>, and passes them the specified
390 * <code>DragSourceEvent</code>.
391 *
392 * @param dse the <code>DragSourceEvent</code>
393 */
394 public void dragExit(DragSourceEvent dse) {
395 DragSourceListener dsl = listener;
396 if (dsl != null) {
397 dsl.dragExit(dse);
398 }
399 getDragSource().processDragExit(dse);
400
401 updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT);
402 }
403
404 /**
405 * Calls <code>dropActionChanged</code> on the
406 * <code>DragSourceListener</code>s registered with this
407 * <code>DragSourceContext</code> and with the associated
408 * <code>DragSource</code>, and passes them the specified
409 * <code>DragSourceDragEvent</code>.
410 *
411 * @param dsde the <code>DragSourceDragEvent</code>
412 */
413 public void dropActionChanged(DragSourceDragEvent dsde) {
414 DragSourceListener dsl = listener;
415 if (dsl != null) {
416 dsl.dropActionChanged(dsde);
417 }
418 getDragSource().processDropActionChanged(dsde);
419
420 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED);
421 }
422
423 /**
424 * Calls <code>dragDropEnd</code> on the
425 * <code>DragSourceListener</code>s registered with this
426 * <code>DragSourceContext</code> and with the associated
427 * <code>DragSource</code>, and passes them the specified
428 * <code>DragSourceDropEvent</code>.
429 *
430 * @param dsde the <code>DragSourceDropEvent</code>
431 */
432 public void dragDropEnd(DragSourceDropEvent dsde) {
433 DragSourceListener dsl = listener;
434 if (dsl != null) {
435 dsl.dragDropEnd(dsde);
436 }
437 getDragSource().processDragDropEnd(dsde);
438 }
439
440 /**
441 * Calls <code>dragMouseMoved</code> on the
442 * <code>DragSourceMotionListener</code>s registered with the
443 * <code>DragSource</code> associated with this
444 * <code>DragSourceContext</code>, and them passes the specified
445 * <code>DragSourceDragEvent</code>.
446 *
447 * @param dsde the <code>DragSourceDragEvent</code>
448 * @since 1.4
449 */
450 public void dragMouseMoved(DragSourceDragEvent dsde) {
451 getDragSource().processDragMouseMoved(dsde);
452 }
453
454 /**
455 * Returns the <code>Transferable</code> associated with
456 * this <code>DragSourceContext</code>.
457 *
458 * @return the <code>Transferable</code>
459 */
460 public Transferable getTransferable() { return transferable; }
461
462 /**
463 * If the default drag cursor behavior is active, this method
464 * sets the default drag cursor for the specified actions
465 * supported by the drag source, the drop target action,
466 * and status, otherwise this method does nothing.
467 *
468 * @param sourceAct the actions supported by the drag source
469 * @param targetAct the drop target action
470 * @param status one of the fields <code>DEFAULT</code>,
471 * <code>ENTER</code>, <code>OVER</code>,
472 * <code>CHANGED</code>
473 */
474
475 protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) {
476
477 // if the cursor has been previously set then dont do any defaults
478 // processing.
479
480 if (useCustomCursor) {
481 return;
482 }
483
484 // do defaults processing
485
486 Cursor c = null;
487
488 targetAct = DnDConstants.ACTION_NONE;
489 switch (status) {
490 case ENTER:
491 case OVER:
492 case CHANGED:
493 int ra = sourceAct & targetAct;
494
495 if (ra == DnDConstants.ACTION_NONE) { // no drop possible
496 if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
497 c = DragSource.DefaultLinkNoDrop;
498 else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
499 c = DragSource.DefaultMoveNoDrop;
500 else
501 c = DragSource.DefaultCopyNoDrop;
502 } else { // drop possible
503 if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
504 c = DragSource.DefaultLinkDrop;
505 else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
506 c = DragSource.DefaultMoveDrop;
507 else
508 c = DragSource.DefaultCopyDrop;
509 }
510 }
511
512 setCursorImpl(c);
513 }
514
515 private void setCursorImpl(Cursor c) {
516 if (cursor == null || !cursor.equals(c)) {
517 cursor = c;
518 if (peer != null) peer.setCursor(cursor);
519 }
520 }
521
522 /**
523 * Serializes this <code>DragSourceContext</code>. This method first
524 * performs default serialization. Next, this object's
525 * <code>Transferable</code> is written out if and only if it can be
526 * serialized. If not, <code>null</code> is written instead. In this case,
527 * a <code>DragSourceContext</code> created from the resulting deserialized
528 * stream will contain a dummy <code>Transferable</code> which supports no
529 * <code>DataFlavor</code>s. Finally, this object's
530 * <code>DragSourceListener</code> is written out if and only if it can be
531 * serialized. If not, <code>null</code> is written instead.
532 *
533 * @serialData The default serializable fields, in alphabetical order,
534 * followed by either a <code>Transferable</code> instance, or
535 * <code>null</code>, followed by either a
536 * <code>DragSourceListener</code> instance, or
537 * <code>null</code>.
538 * @since 1.4
539 */
540 private void writeObject(ObjectOutputStream s) throws IOException {
541 s.defaultWriteObject();
542
543 s.writeObject(SerializationTester.test(transferable)
544 ? transferable : null);
545 s.writeObject(SerializationTester.test(listener)
546 ? listener : null);
547 }
548
549 /**
550 * Deserializes this <code>DragSourceContext</code>. This method first
551 * performs default deserialization for all non-<code>transient</code>
552 * fields. This object's <code>Transferable</code> and
553 * <code>DragSourceListener</code> are then deserialized as well by using
554 * the next two objects in the stream. If the resulting
555 * <code>Transferable</code> is <code>null</code>, this object's
556 * <code>Transferable</code> is set to a dummy <code>Transferable</code>
557 * which supports no <code>DataFlavor</code>s.
558 *
559 * @since 1.4
560 */
561 private void readObject(ObjectInputStream s)
562 throws ClassNotFoundException, IOException
563 {
564 s.defaultReadObject();
565
566 transferable = (Transferable)s.readObject();
567 listener = (DragSourceListener)s.readObject();
568
569 // Implementation assumes 'transferable' is never null.
570 if (transferable == null) {
571 if (emptyTransferable == null) {
572 emptyTransferable = new Transferable() {
573 public DataFlavor[] getTransferDataFlavors() {
574 return new DataFlavor[0];
575 }
576 public boolean isDataFlavorSupported(DataFlavor flavor)
577 {
578 return false;
579 }
580 public Object getTransferData(DataFlavor flavor)
581 throws UnsupportedFlavorException
582 {
583 throw new UnsupportedFlavorException(flavor);
584 }
585 };
586 }
587 transferable = emptyTransferable;
588 }
589 }
590
591 private static Transferable emptyTransferable;
592
593 /*
594 * fields
595 */
596
597 private transient DragSourceContextPeer peer;
598
599 /**
600 * The event which triggered the start of the drag.
601 *
602 * @serial
603 */
604 private DragGestureEvent trigger;
605
606 /**
607 * The current drag cursor.
608 *
609 * @serial
610 */
611 private Cursor cursor;
612
613 private transient Transferable transferable;
614
615 private transient DragSourceListener listener;
616
617 /**
618 * <code>true</code> if the custom drag cursor is used instead of the
619 * default one.
620 *
621 * @serial
622 */
623 private boolean useCustomCursor;
624
625 /**
626 * A bitwise mask of <code>DnDConstants</code> that represents the set of
627 * drop actions supported by the drag source for the drag operation associated
628 * with this <code>DragSourceContext.</code>
629 *
630 * @serial
631 */
632 private final int sourceActions;
633}