001 /*
002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.awt.dnd;
027
028 import java.awt.Component;
029 import java.awt.Cursor;
030 import java.awt.Image;
031 import java.awt.Point;
032
033 import java.awt.datatransfer.DataFlavor;
034 import java.awt.datatransfer.Transferable;
035 import java.awt.datatransfer.UnsupportedFlavorException;
036
037 import java.awt.dnd.peer.DragSourceContextPeer;
038
039 import java.io.IOException;
040 import java.io.ObjectOutputStream;
041 import java.io.ObjectInputStream;
042 import java.io.Serializable;
043
044 import java.util.TooManyListenersException;
045
046 /**
047 * The <code>DragSourceContext</code> class is responsible for managing the
048 * initiator side of the Drag and Drop protocol. In particular, it is responsible
049 * for managing drag event notifications to the
050 * {@linkplain DragSourceListener DragSourceListeners}
051 * and {@linkplain DragSourceMotionListener DragSourceMotionListeners}, and providing the
052 * {@link Transferable} representing the source data for the drag operation.
053 * <p>
054 * Note that the <code>DragSourceContext</code> itself
055 * implements the <code>DragSourceListener</code> and
056 * <code>DragSourceMotionListener</code> interfaces.
057 * This is to allow the platform peer
058 * (the {@link DragSourceContextPeer} instance)
059 * created by the {@link DragSource} to notify
060 * the <code>DragSourceContext</code> of
061 * state changes in the ongoing operation. This allows the
062 * <code>DragSourceContext</code> object to interpose
063 * itself between the platform and the
064 * listeners provided by the initiator of the drag operation.
065 * <p>
066 * <a name="defaultCursor" />
067 * By default, {@code DragSourceContext} sets the cursor as appropriate
068 * for the current state of the drag and drop operation. For example, if
069 * the user has chosen {@linkplain DnDConstants#ACTION_MOVE the move action},
070 * and the pointer is over a target that accepts
071 * the move action, the default move cursor is shown. When
072 * the pointer is over an area that does not accept the transfer,
073 * the default "no drop" cursor is shown.
074 * <p>
075 * This default handling mechanism is disabled when a custom cursor is set
076 * by the {@link #setCursor} method. When the default handling is disabled,
077 * it becomes the responsibility
078 * of the developer to keep the cursor up to date, by listening
079 * to the {@code DragSource} events and calling the {@code setCursor()} method.
080 * Alternatively, you can provide custom cursor behavior by providing
081 * custom implementations of the {@code DragSource}
082 * and the {@code DragSourceContext} classes.
083 *
084 * @see DragSourceListener
085 * @see DragSourceMotionListener
086 * @see DnDConstants
087 * @version 1.62, 06/05/07
088 * @since 1.2
089 */
090
091 public class DragSourceContext implements DragSourceListener,
092 DragSourceMotionListener, Serializable {
093
094 private static final long serialVersionUID = -115407898692194719L;
095
096 // used by updateCurrentCursor
097
098 /**
099 * An <code>int</code> used by updateCurrentCursor()
100 * indicating that the <code>Cursor</code> should change
101 * to the default (no drop) <code>Cursor</code>.
102 */
103 protected static final int DEFAULT = 0;
104
105 /**
106 * An <code>int</code> used by updateCurrentCursor()
107 * indicating that the <code>Cursor</code>
108 * has entered a <code>DropTarget</code>.
109 */
110 protected static final int ENTER = 1;
111
112 /**
113 * An <code>int</code> used by updateCurrentCursor()
114 * indicating that the <code>Cursor</code> is
115 * over a <code>DropTarget</code>.
116 */
117 protected static final int OVER = 2;
118
119 /**
120 * An <code>int</code> used by updateCurrentCursor()
121 * indicating that the user operation has changed.
122 */
123
124 protected static final int CHANGED = 3;
125
126 /**
127 * Called from <code>DragSource</code>, this constructor creates a new
128 * <code>DragSourceContext</code> given the
129 * <code>DragSourceContextPeer</code> for this Drag, the
130 * <code>DragGestureEvent</code> that triggered the Drag, the initial
131 * <code>Cursor</code> to use for the Drag, an (optional)
132 * <code>Image</code> to display while the Drag is taking place, the offset
133 * of the <code>Image</code> origin from the hotspot at the instant of the
134 * triggering event, the <code>Transferable</code> subject data, and the
135 * <code>DragSourceListener</code> to use during the Drag and Drop
136 * operation.
137 * <br>
138 * If <code>DragSourceContextPeer</code> is <code>null</code>
139 * <code>NullPointerException</code> is thrown.
140 * <br>
141 * If <code>DragGestureEvent</code> is <code>null</code>
142 * <code>NullPointerException</code> is thrown.
143 * <br>
144 * If <code>Cursor</code> is <code>null</code> no exception is thrown and
145 * the default drag cursor behavior is activated for this drag operation.
146 * <br>
147 * If <code>Image</code> is <code>null</code> no exception is thrown.
148 * <br>
149 * If <code>Image</code> is not <code>null</code> and the offset is
150 * <code>null</code> <code>NullPointerException</code> is thrown.
151 * <br>
152 * If <code>Transferable</code> is <code>null</code>
153 * <code>NullPointerException</code> is thrown.
154 * <br>
155 * If <code>DragSourceListener</code> is <code>null</code> no exception
156 * is thrown.
157 *
158 * @param dscp the <code>DragSourceContextPeer</code> for this drag
159 * @param trigger the triggering event
160 * @param dragCursor the initial {@code Cursor} for this drag operation
161 * or {@code null} for the default cursor handling;
162 * see <a href="DragSourceContext.html#defaultCursor">class level documentation</a>
163 * for more details on the cursor handling mechanism during drag and drop
164 * @param dragImage the <code>Image</code> to drag (or <code>null</code>)
165 * @param offset the offset of the image origin from the hotspot at the
166 * instant of the triggering event
167 * @param t the <code>Transferable</code>
168 * @param dsl the <code>DragSourceListener</code>
169 *
170 * @throws IllegalArgumentException if the <code>Component</code> associated
171 * with the trigger event is <code>null</code>.
172 * @throws IllegalArgumentException if the <code>DragSource</code> for the
173 * trigger event is <code>null</code>.
174 * @throws IllegalArgumentException if the drag action for the
175 * trigger event is <code>DnDConstants.ACTION_NONE</code>.
176 * @throws IllegalArgumentException if the source actions for the
177 * <code>DragGestureRecognizer</code> associated with the trigger
178 * event are equal to <code>DnDConstants.ACTION_NONE</code>.
179 * @throws NullPointerException if dscp, trigger, or t are null, or
180 * if dragImage is non-null and offset is null
181 */
182 public DragSourceContext(DragSourceContextPeer dscp,
183 DragGestureEvent trigger, Cursor dragCursor,
184 Image dragImage, Point offset, Transferable t,
185 DragSourceListener dsl) {
186 if (dscp == null) {
187 throw new NullPointerException("DragSourceContextPeer");
188 }
189
190 if (trigger == null) {
191 throw new NullPointerException("Trigger");
192 }
193
194 if (trigger.getDragSource() == null) {
195 throw new IllegalArgumentException("DragSource");
196 }
197
198 if (trigger.getComponent() == null) {
199 throw new IllegalArgumentException("Component");
200 }
201
202 if (trigger.getSourceAsDragGestureRecognizer()
203 .getSourceActions() == DnDConstants.ACTION_NONE) {
204 throw new IllegalArgumentException("source actions");
205 }
206
207 if (trigger.getDragAction() == DnDConstants.ACTION_NONE) {
208 throw new IllegalArgumentException("no drag action");
209 }
210
211 if (t == null) {
212 throw new NullPointerException("Transferable");
213 }
214
215 if (dragImage != null && offset == null) {
216 throw new NullPointerException("offset");
217 }
218
219 peer = dscp;
220 this .trigger = trigger;
221 cursor = dragCursor;
222 transferable = t;
223 listener = dsl;
224 sourceActions = trigger.getSourceAsDragGestureRecognizer()
225 .getSourceActions();
226
227 useCustomCursor = (dragCursor != null);
228
229 updateCurrentCursor(trigger.getDragAction(),
230 getSourceActions(), DEFAULT);
231 }
232
233 /**
234 * Returns the <code>DragSource</code>
235 * that instantiated this <code>DragSourceContext</code>.
236 *
237 * @return the <code>DragSource</code> that
238 * instantiated this <code>DragSourceContext</code>
239 */
240
241 public DragSource getDragSource() {
242 return trigger.getDragSource();
243 }
244
245 /**
246 * Returns the <code>Component</code> associated with this
247 * <code>DragSourceContext</code>.
248 *
249 * @return the <code>Component</code> that started the drag
250 */
251
252 public Component getComponent() {
253 return trigger.getComponent();
254 }
255
256 /**
257 * Returns the <code>DragGestureEvent</code>
258 * that initially triggered the drag.
259 *
260 * @return the Event that triggered the drag
261 */
262
263 public DragGestureEvent getTrigger() {
264 return trigger;
265 }
266
267 /**
268 * Returns a bitwise mask of <code>DnDConstants</code> that
269 * represent the set of drop actions supported by the drag source for the
270 * drag operation associated with this <code>DragSourceContext</code>.
271 *
272 * @return the drop actions supported by the drag source
273 */
274 public int getSourceActions() {
275 return sourceActions;
276 }
277
278 /**
279 * Sets the cursor for this drag operation to the specified
280 * <code>Cursor</code>. If the specified <code>Cursor</code>
281 * is <code>null</code>, the default drag cursor behavior is
282 * activated for this drag operation, otherwise it is deactivated.
283 *
284 * @param c the initial {@code Cursor} for this drag operation,
285 * or {@code null} for the default cursor handling;
286 * see {@linkplain #defaultCursor class
287 * level documentation} for more details
288 * on the cursor handling during drag and drop
289 *
290 */
291
292 public synchronized void setCursor(Cursor c) {
293 useCustomCursor = (c != null);
294 setCursorImpl(c);
295 }
296
297 /**
298 * Returns the current drag <code>Cursor</code>.
299 * <P>
300 * @return the current drag <code>Cursor</code>
301 */
302
303 public Cursor getCursor() {
304 return cursor;
305 }
306
307 /**
308 * Add a <code>DragSourceListener</code> to this
309 * <code>DragSourceContext</code> if one has not already been added.
310 * If a <code>DragSourceListener</code> already exists,
311 * this method throws a <code>TooManyListenersException</code>.
312 * <P>
313 * @param dsl the <code>DragSourceListener</code> to add.
314 * Note that while <code>null</code> is not prohibited,
315 * it is not acceptable as a parameter.
316 * <P>
317 * @throws TooManyListenersException if
318 * a <code>DragSourceListener</code> has already been added
319 */
320
321 public synchronized void addDragSourceListener(
322 DragSourceListener dsl) throws TooManyListenersException {
323 if (dsl == null)
324 return;
325
326 if (equals(dsl))
327 throw new IllegalArgumentException(
328 "DragSourceContext may not be its own listener");
329
330 if (listener != null)
331 throw new TooManyListenersException();
332 else
333 listener = dsl;
334 }
335
336 /**
337 * Removes the specified <code>DragSourceListener</code>
338 * from this <code>DragSourceContext</code>.
339 *
340 * @param dsl the <code>DragSourceListener</code> to remove;
341 * note that while <code>null</code> is not prohibited,
342 * it is not acceptable as a parameter
343 */
344
345 public synchronized void removeDragSourceListener(
346 DragSourceListener dsl) {
347 if (listener != null && listener.equals(dsl)) {
348 listener = null;
349 } else
350 throw new IllegalArgumentException();
351 }
352
353 /**
354 * Notifies the peer that the <code>Transferable</code>'s
355 * <code>DataFlavor</code>s have changed.
356 */
357
358 public void transferablesFlavorsChanged() {
359 if (peer != null)
360 peer.transferablesFlavorsChanged();
361 }
362
363 /**
364 * Calls <code>dragEnter</code> on the
365 * <code>DragSourceListener</code>s registered with this
366 * <code>DragSourceContext</code> and with the associated
367 * <code>DragSource</code>, and passes them the specified
368 * <code>DragSourceDragEvent</code>.
369 *
370 * @param dsde the <code>DragSourceDragEvent</code>
371 */
372 public void dragEnter(DragSourceDragEvent dsde) {
373 DragSourceListener dsl = listener;
374 if (dsl != null) {
375 dsl.dragEnter(dsde);
376 }
377 getDragSource().processDragEnter(dsde);
378
379 updateCurrentCursor(getSourceActions(),
380 dsde.getTargetActions(), ENTER);
381 }
382
383 /**
384 * Calls <code>dragOver</code> on the
385 * <code>DragSourceListener</code>s registered with this
386 * <code>DragSourceContext</code> and with the associated
387 * <code>DragSource</code>, and passes them the specified
388 * <code>DragSourceDragEvent</code>.
389 *
390 * @param dsde the <code>DragSourceDragEvent</code>
391 */
392 public void dragOver(DragSourceDragEvent dsde) {
393 DragSourceListener dsl = listener;
394 if (dsl != null) {
395 dsl.dragOver(dsde);
396 }
397 getDragSource().processDragOver(dsde);
398
399 updateCurrentCursor(getSourceActions(),
400 dsde.getTargetActions(), OVER);
401 }
402
403 /**
404 * Calls <code>dragExit</code> on the
405 * <code>DragSourceListener</code>s registered with this
406 * <code>DragSourceContext</code> and with the associated
407 * <code>DragSource</code>, and passes them the specified
408 * <code>DragSourceEvent</code>.
409 *
410 * @param dse the <code>DragSourceEvent</code>
411 */
412 public void dragExit(DragSourceEvent dse) {
413 DragSourceListener dsl = listener;
414 if (dsl != null) {
415 dsl.dragExit(dse);
416 }
417 getDragSource().processDragExit(dse);
418
419 updateCurrentCursor(DnDConstants.ACTION_NONE,
420 DnDConstants.ACTION_NONE, DEFAULT);
421 }
422
423 /**
424 * Calls <code>dropActionChanged</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>DragSourceDragEvent</code>.
429 *
430 * @param dsde the <code>DragSourceDragEvent</code>
431 */
432 public void dropActionChanged(DragSourceDragEvent dsde) {
433 DragSourceListener dsl = listener;
434 if (dsl != null) {
435 dsl.dropActionChanged(dsde);
436 }
437 getDragSource().processDropActionChanged(dsde);
438
439 updateCurrentCursor(getSourceActions(),
440 dsde.getTargetActions(), CHANGED);
441 }
442
443 /**
444 * Calls <code>dragDropEnd</code> on the
445 * <code>DragSourceListener</code>s registered with this
446 * <code>DragSourceContext</code> and with the associated
447 * <code>DragSource</code>, and passes them the specified
448 * <code>DragSourceDropEvent</code>.
449 *
450 * @param dsde the <code>DragSourceDropEvent</code>
451 */
452 public void dragDropEnd(DragSourceDropEvent dsde) {
453 DragSourceListener dsl = listener;
454 if (dsl != null) {
455 dsl.dragDropEnd(dsde);
456 }
457 getDragSource().processDragDropEnd(dsde);
458 }
459
460 /**
461 * Calls <code>dragMouseMoved</code> on the
462 * <code>DragSourceMotionListener</code>s registered with the
463 * <code>DragSource</code> associated with this
464 * <code>DragSourceContext</code>, and them passes the specified
465 * <code>DragSourceDragEvent</code>.
466 *
467 * @param dsde the <code>DragSourceDragEvent</code>
468 * @since 1.4
469 */
470 public void dragMouseMoved(DragSourceDragEvent dsde) {
471 getDragSource().processDragMouseMoved(dsde);
472 }
473
474 /**
475 * Returns the <code>Transferable</code> associated with
476 * this <code>DragSourceContext</code>.
477 *
478 * @return the <code>Transferable</code>
479 */
480 public Transferable getTransferable() {
481 return transferable;
482 }
483
484 /**
485 * If the default drag cursor behavior is active, this method
486 * sets the default drag cursor for the specified actions
487 * supported by the drag source, the drop target action,
488 * and status, otherwise this method does nothing.
489 *
490 * @param sourceAct the actions supported by the drag source
491 * @param targetAct the drop target action
492 * @param status one of the fields <code>DEFAULT</code>,
493 * <code>ENTER</code>, <code>OVER</code>,
494 * <code>CHANGED</code>
495 */
496
497 protected synchronized void updateCurrentCursor(int sourceAct,
498 int targetAct, int status) {
499
500 // if the cursor has been previously set then dont do any defaults
501 // processing.
502
503 if (useCustomCursor) {
504 return;
505 }
506
507 // do defaults processing
508
509 Cursor c = null;
510
511 targetAct = DnDConstants.ACTION_NONE;
512 switch (status) {
513 case ENTER:
514 case OVER:
515 case CHANGED:
516 int ra = sourceAct & targetAct;
517
518 if (ra == DnDConstants.ACTION_NONE) { // no drop possible
519 if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
520 c = DragSource.DefaultLinkNoDrop;
521 else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
522 c = DragSource.DefaultMoveNoDrop;
523 else
524 c = DragSource.DefaultCopyNoDrop;
525 } else { // drop possible
526 if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
527 c = DragSource.DefaultLinkDrop;
528 else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
529 c = DragSource.DefaultMoveDrop;
530 else
531 c = DragSource.DefaultCopyDrop;
532 }
533 }
534
535 setCursorImpl(c);
536 }
537
538 private void setCursorImpl(Cursor c) {
539 if (cursor == null || !cursor.equals(c)) {
540 cursor = c;
541 if (peer != null)
542 peer.setCursor(cursor);
543 }
544 }
545
546 /**
547 * Serializes this <code>DragSourceContext</code>. This method first
548 * performs default serialization. Next, this object's
549 * <code>Transferable</code> is written out if and only if it can be
550 * serialized. If not, <code>null</code> is written instead. In this case,
551 * a <code>DragSourceContext</code> created from the resulting deserialized
552 * stream will contain a dummy <code>Transferable</code> which supports no
553 * <code>DataFlavor</code>s. Finally, this object's
554 * <code>DragSourceListener</code> is written out if and only if it can be
555 * serialized. If not, <code>null</code> is written instead.
556 *
557 * @serialData The default serializable fields, in alphabetical order,
558 * followed by either a <code>Transferable</code> instance, or
559 * <code>null</code>, followed by either a
560 * <code>DragSourceListener</code> instance, or
561 * <code>null</code>.
562 * @since 1.4
563 */
564 private void writeObject(ObjectOutputStream s) throws IOException {
565 s.defaultWriteObject();
566
567 s
568 .writeObject(SerializationTester.test(transferable) ? transferable
569 : null);
570 s.writeObject(SerializationTester.test(listener) ? listener
571 : null);
572 }
573
574 /**
575 * Deserializes this <code>DragSourceContext</code>. This method first
576 * performs default deserialization for all non-<code>transient</code>
577 * fields. This object's <code>Transferable</code> and
578 * <code>DragSourceListener</code> are then deserialized as well by using
579 * the next two objects in the stream. If the resulting
580 * <code>Transferable</code> is <code>null</code>, this object's
581 * <code>Transferable</code> is set to a dummy <code>Transferable</code>
582 * which supports no <code>DataFlavor</code>s.
583 *
584 * @since 1.4
585 */
586 private void readObject(ObjectInputStream s)
587 throws ClassNotFoundException, IOException {
588 s.defaultReadObject();
589
590 transferable = (Transferable) s.readObject();
591 listener = (DragSourceListener) s.readObject();
592
593 // Implementation assumes 'transferable' is never null.
594 if (transferable == null) {
595 if (emptyTransferable == null) {
596 emptyTransferable = new Transferable() {
597 public DataFlavor[] getTransferDataFlavors() {
598 return new DataFlavor[0];
599 }
600
601 public boolean isDataFlavorSupported(
602 DataFlavor flavor) {
603 return false;
604 }
605
606 public Object getTransferData(DataFlavor flavor)
607 throws UnsupportedFlavorException {
608 throw new UnsupportedFlavorException(flavor);
609 }
610 };
611 }
612 transferable = emptyTransferable;
613 }
614 }
615
616 private static Transferable emptyTransferable;
617
618 /*
619 * fields
620 */
621
622 private transient DragSourceContextPeer peer;
623
624 /**
625 * The event which triggered the start of the drag.
626 *
627 * @serial
628 */
629 private DragGestureEvent trigger;
630
631 /**
632 * The current drag cursor.
633 *
634 * @serial
635 */
636 private Cursor cursor;
637
638 private transient Transferable transferable;
639
640 private transient DragSourceListener listener;
641
642 /**
643 * <code>true</code> if the custom drag cursor is used instead of the
644 * default one.
645 *
646 * @serial
647 */
648 private boolean useCustomCursor;
649
650 /**
651 * A bitwise mask of <code>DnDConstants</code> that represents the set of
652 * drop actions supported by the drag source for the drag operation associated
653 * with this <code>DragSourceContext.</code>
654 *
655 * @serial
656 */
657 private final int sourceActions;
658 }
|