1 /*
   2  * Copyright 1997-2008 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 
  26 package java.awt.dnd;
  27 
  28 import java.awt.Component;
  29 import java.awt.Cursor;
  30 import java.awt.Image;
  31 import java.awt.Point;
  32 
  33 import java.awt.datatransfer.DataFlavor;
  34 import java.awt.datatransfer.Transferable;
  35 import java.awt.datatransfer.UnsupportedFlavorException;
  36 
  37 import java.awt.dnd.peer.DragSourceContextPeer;
  38 
  39 import java.io.IOException;
  40 import java.io.ObjectOutputStream;
  41 import java.io.ObjectInputStream;
  42 import java.io.Serializable;
  43 
  44 import 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 
  90 public 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         switch (status) {
 489             default:
 490                 targetAct = DnDConstants.ACTION_NONE;
 491             case ENTER:
 492             case OVER:
 493             case CHANGED:
 494                 int    ra = sourceAct & targetAct;
 495 
 496                 if (ra == DnDConstants.ACTION_NONE) { // no drop possible
 497                     if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
 498                         c = DragSource.DefaultLinkNoDrop;
 499                     else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
 500                         c = DragSource.DefaultMoveNoDrop;
 501                     else
 502                         c = DragSource.DefaultCopyNoDrop;
 503                 } else { // drop possible
 504                     if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
 505                         c = DragSource.DefaultLinkDrop;
 506                     else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
 507                         c = DragSource.DefaultMoveDrop;
 508                     else
 509                         c = DragSource.DefaultCopyDrop;
 510                 }
 511         }
 512 
 513         setCursorImpl(c);
 514     }
 515 
 516     private void setCursorImpl(Cursor c) {
 517         if (cursor == null || !cursor.equals(c)) {
 518             cursor = c;
 519             if (peer != null) peer.setCursor(cursor);
 520         }
 521     }
 522 
 523     /**
 524      * Serializes this <code>DragSourceContext</code>. This method first
 525      * performs default serialization. Next, this object's
 526      * <code>Transferable</code> is written out if and only if it can be
 527      * serialized. If not, <code>null</code> is written instead. In this case,
 528      * a <code>DragSourceContext</code> created from the resulting deserialized
 529      * stream will contain a dummy <code>Transferable</code> which supports no
 530      * <code>DataFlavor</code>s. Finally, this object's
 531      * <code>DragSourceListener</code> is written out if and only if it can be
 532      * serialized. If not, <code>null</code> is written instead.
 533      *
 534      * @serialData The default serializable fields, in alphabetical order,
 535      *             followed by either a <code>Transferable</code> instance, or
 536      *             <code>null</code>, followed by either a
 537      *             <code>DragSourceListener</code> instance, or
 538      *             <code>null</code>.
 539      * @since 1.4
 540      */
 541     private void writeObject(ObjectOutputStream s) throws IOException {
 542         s.defaultWriteObject();
 543 
 544         s.writeObject(SerializationTester.test(transferable)
 545                       ? transferable : null);
 546         s.writeObject(SerializationTester.test(listener)
 547                       ? listener : null);
 548     }
 549 
 550     /**
 551      * Deserializes this <code>DragSourceContext</code>. This method first
 552      * performs default deserialization for all non-<code>transient</code>
 553      * fields. This object's <code>Transferable</code> and
 554      * <code>DragSourceListener</code> are then deserialized as well by using
 555      * the next two objects in the stream. If the resulting
 556      * <code>Transferable</code> is <code>null</code>, this object's
 557      * <code>Transferable</code> is set to a dummy <code>Transferable</code>
 558      * which supports no <code>DataFlavor</code>s.
 559      *
 560      * @since 1.4
 561      */
 562     private void readObject(ObjectInputStream s)
 563         throws ClassNotFoundException, IOException
 564     {
 565         s.defaultReadObject();
 566 
 567         transferable = (Transferable)s.readObject();
 568         listener = (DragSourceListener)s.readObject();
 569 
 570         // Implementation assumes 'transferable' is never null.
 571         if (transferable == null) {
 572             if (emptyTransferable == null) {
 573                 emptyTransferable = new Transferable() {
 574                         public DataFlavor[] getTransferDataFlavors() {
 575                             return new DataFlavor[0];
 576                         }
 577                         public boolean isDataFlavorSupported(DataFlavor flavor)
 578                         {
 579                             return false;
 580                         }
 581                         public Object getTransferData(DataFlavor flavor)
 582                             throws UnsupportedFlavorException
 583                         {
 584                             throw new UnsupportedFlavorException(flavor);
 585                         }
 586                     };
 587             }
 588             transferable = emptyTransferable;
 589         }
 590     }
 591 
 592     private static Transferable emptyTransferable;
 593 
 594     /*
 595      * fields
 596      */
 597 
 598     private transient DragSourceContextPeer peer;
 599 
 600     /**
 601      * The event which triggered the start of the drag.
 602      *
 603      * @serial
 604      */
 605     private DragGestureEvent    trigger;
 606 
 607     /**
 608      * The current drag cursor.
 609      *
 610      * @serial
 611      */
 612     private Cursor              cursor;
 613 
 614     private transient Transferable      transferable;
 615 
 616     private transient DragSourceListener    listener;
 617 
 618     /**
 619      * <code>true</code> if the custom drag cursor is used instead of the
 620      * default one.
 621      *
 622      * @serial
 623      */
 624     private boolean useCustomCursor;
 625 
 626     /**
 627      * A bitwise mask of <code>DnDConstants</code> that represents the set of
 628      * drop actions supported by the drag source for the drag operation associated
 629      * with this <code>DragSourceContext.</code>
 630      *
 631      * @serial
 632      */
 633     private final int sourceActions;
 634 }