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 }