1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * 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.InvalidObjectException; 41 import java.io.ObjectOutputStream; 42 import java.io.ObjectInputStream; 43 import java.io.Serializable; 44 45 import java.util.TooManyListenersException; 46 47 /** 48 * The <code>DragSourceContext</code> class is responsible for managing the 49 * initiator side of the Drag and Drop protocol. In particular, it is responsible 50 * for managing drag event notifications to the 51 * {@linkplain DragSourceListener DragSourceListeners} 52 * and {@linkplain DragSourceMotionListener DragSourceMotionListeners}, and providing the 53 * {@link Transferable} representing the source data for the drag operation. 54 * <p> 55 * Note that the <code>DragSourceContext</code> itself 56 * implements the <code>DragSourceListener</code> and 57 * <code>DragSourceMotionListener</code> interfaces. 58 * This is to allow the platform peer 59 * (the {@link DragSourceContextPeer} instance) 60 * created by the {@link DragSource} to notify 61 * the <code>DragSourceContext</code> of 62 * state changes in the ongoing operation. This allows the 63 * <code>DragSourceContext</code> object to interpose 64 * itself between the platform and the 65 * listeners provided by the initiator of the drag operation. 66 * <p> 67 * <a name="defaultCursor"></a> 68 * By default, {@code DragSourceContext} sets the cursor as appropriate 69 * for the current state of the drag and drop operation. For example, if 70 * the user has chosen {@linkplain DnDConstants#ACTION_MOVE the move action}, 71 * and the pointer is over a target that accepts 72 * the move action, the default move cursor is shown. When 73 * the pointer is over an area that does not accept the transfer, 74 * the default "no drop" cursor is shown. 75 * <p> 76 * This default handling mechanism is disabled when a custom cursor is set 77 * by the {@link #setCursor} method. When the default handling is disabled, 78 * it becomes the responsibility 79 * of the developer to keep the cursor up to date, by listening 80 * to the {@code DragSource} events and calling the {@code setCursor()} method. 81 * Alternatively, you can provide custom cursor behavior by providing 82 * custom implementations of the {@code DragSource} 83 * and the {@code DragSourceContext} classes. 84 * 85 * @see DragSourceListener 86 * @see DragSourceMotionListener 87 * @see DnDConstants 88 * @since 1.2 89 */ 90 91 public class DragSourceContext 92 implements DragSourceListener, DragSourceMotionListener, Serializable { 93 94 private static final long serialVersionUID = -115407898692194719L; 95 96 // used by updateCurrentCursor 97 98 /** 99 * 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().getSourceActions() == 203 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 = 225 trigger.getSourceAsDragGestureRecognizer().getSourceActions(); 226 227 useCustomCursor = (dragCursor != null); 228 229 updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT); 230 } 231 232 /** 233 * Returns the <code>DragSource</code> 234 * that instantiated this <code>DragSourceContext</code>. 235 * 236 * @return the <code>DragSource</code> that 237 * instantiated this <code>DragSourceContext</code> 238 */ 239 240 public DragSource getDragSource() { return trigger.getDragSource(); } 241 242 /** 243 * Returns the <code>Component</code> associated with this 244 * <code>DragSourceContext</code>. 245 * 246 * @return the <code>Component</code> that started the drag 247 */ 248 249 public Component getComponent() { return trigger.getComponent(); } 250 251 /** 252 * Returns the <code>DragGestureEvent</code> 253 * that initially triggered the drag. 254 * 255 * @return the Event that triggered the drag 256 */ 257 258 public DragGestureEvent getTrigger() { return trigger; } 259 260 /** 261 * Returns a bitwise mask of <code>DnDConstants</code> that 262 * represent the set of drop actions supported by the drag source for the 263 * drag operation associated with this <code>DragSourceContext</code>. 264 * 265 * @return the drop actions supported by the drag source 266 */ 267 public int getSourceActions() { 268 return sourceActions; 269 } 270 271 /** 272 * Sets the cursor for this drag operation to the specified 273 * <code>Cursor</code>. If the specified <code>Cursor</code> 274 * is <code>null</code>, the default drag cursor behavior is 275 * activated for this drag operation, otherwise it is deactivated. 276 * 277 * @param c the initial {@code Cursor} for this drag operation, 278 * or {@code null} for the default cursor handling; 279 * see {@linkplain Cursor class 280 * level documentation} for more details 281 * on the cursor handling during drag and drop 282 * 283 */ 284 285 public synchronized void setCursor(Cursor c) { 286 useCustomCursor = (c != null); 287 setCursorImpl(c); 288 } 289 290 /** 291 * Returns the current drag <code>Cursor</code>. 292 * 293 * @return the current drag <code>Cursor</code> 294 */ 295 296 public Cursor getCursor() { return cursor; } 297 298 /** 299 * Add a <code>DragSourceListener</code> to this 300 * <code>DragSourceContext</code> if one has not already been added. 301 * If a <code>DragSourceListener</code> already exists, 302 * this method throws a <code>TooManyListenersException</code>. 303 * 304 * @param dsl the <code>DragSourceListener</code> to add. 305 * Note that while <code>null</code> is not prohibited, 306 * it is not acceptable as a parameter. 307 * 308 * @throws TooManyListenersException if 309 * a <code>DragSourceListener</code> has already been added 310 */ 311 312 public synchronized void addDragSourceListener(DragSourceListener dsl) throws TooManyListenersException { 313 if (dsl == null) return; 314 315 if (equals(dsl)) throw new IllegalArgumentException("DragSourceContext may not be its own listener"); 316 317 if (listener != null) 318 throw new TooManyListenersException(); 319 else 320 listener = dsl; 321 } 322 323 /** 324 * Removes the specified <code>DragSourceListener</code> 325 * from this <code>DragSourceContext</code>. 326 * 327 * @param dsl the <code>DragSourceListener</code> to remove; 328 * note that while <code>null</code> is not prohibited, 329 * it is not acceptable as a parameter 330 */ 331 332 public synchronized void removeDragSourceListener(DragSourceListener dsl) { 333 if (listener != null && listener.equals(dsl)) { 334 listener = null; 335 } else 336 throw new IllegalArgumentException(); 337 } 338 339 /** 340 * Notifies the peer that the <code>Transferable</code>'s 341 * <code>DataFlavor</code>s have changed. 342 */ 343 344 public void transferablesFlavorsChanged() { 345 if (peer != null) peer.transferablesFlavorsChanged(); 346 } 347 348 /** 349 * Calls <code>dragEnter</code> on the 350 * <code>DragSourceListener</code>s registered with this 351 * <code>DragSourceContext</code> and with the associated 352 * <code>DragSource</code>, and passes them the specified 353 * <code>DragSourceDragEvent</code>. 354 * 355 * @param dsde the <code>DragSourceDragEvent</code> 356 */ 357 public void dragEnter(DragSourceDragEvent dsde) { 358 DragSourceListener dsl = listener; 359 if (dsl != null) { 360 dsl.dragEnter(dsde); 361 } 362 getDragSource().processDragEnter(dsde); 363 364 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER); 365 } 366 367 /** 368 * Calls <code>dragOver</code> on the 369 * <code>DragSourceListener</code>s registered with this 370 * <code>DragSourceContext</code> and with the associated 371 * <code>DragSource</code>, and passes them the specified 372 * <code>DragSourceDragEvent</code>. 373 * 374 * @param dsde the <code>DragSourceDragEvent</code> 375 */ 376 public void dragOver(DragSourceDragEvent dsde) { 377 DragSourceListener dsl = listener; 378 if (dsl != null) { 379 dsl.dragOver(dsde); 380 } 381 getDragSource().processDragOver(dsde); 382 383 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER); 384 } 385 386 /** 387 * Calls <code>dragExit</code> on the 388 * <code>DragSourceListener</code>s registered with this 389 * <code>DragSourceContext</code> and with the associated 390 * <code>DragSource</code>, and passes them the specified 391 * <code>DragSourceEvent</code>. 392 * 393 * @param dse the <code>DragSourceEvent</code> 394 */ 395 public void dragExit(DragSourceEvent dse) { 396 DragSourceListener dsl = listener; 397 if (dsl != null) { 398 dsl.dragExit(dse); 399 } 400 getDragSource().processDragExit(dse); 401 402 updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT); 403 } 404 405 /** 406 * Calls <code>dropActionChanged</code> on the 407 * <code>DragSourceListener</code>s registered with this 408 * <code>DragSourceContext</code> and with the associated 409 * <code>DragSource</code>, and passes them the specified 410 * <code>DragSourceDragEvent</code>. 411 * 412 * @param dsde the <code>DragSourceDragEvent</code> 413 */ 414 public void dropActionChanged(DragSourceDragEvent dsde) { 415 DragSourceListener dsl = listener; 416 if (dsl != null) { 417 dsl.dropActionChanged(dsde); 418 } 419 getDragSource().processDropActionChanged(dsde); 420 421 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED); 422 } 423 424 /** 425 * Calls <code>dragDropEnd</code> on the 426 * <code>DragSourceListener</code>s registered with this 427 * <code>DragSourceContext</code> and with the associated 428 * <code>DragSource</code>, and passes them the specified 429 * <code>DragSourceDropEvent</code>. 430 * 431 * @param dsde the <code>DragSourceDropEvent</code> 432 */ 433 public void dragDropEnd(DragSourceDropEvent dsde) { 434 DragSourceListener dsl = listener; 435 if (dsl != null) { 436 dsl.dragDropEnd(dsde); 437 } 438 getDragSource().processDragDropEnd(dsde); 439 } 440 441 /** 442 * Calls <code>dragMouseMoved</code> on the 443 * <code>DragSourceMotionListener</code>s registered with the 444 * <code>DragSource</code> associated with this 445 * <code>DragSourceContext</code>, and them passes the specified 446 * <code>DragSourceDragEvent</code>. 447 * 448 * @param dsde the <code>DragSourceDragEvent</code> 449 * @since 1.4 450 */ 451 public void dragMouseMoved(DragSourceDragEvent dsde) { 452 getDragSource().processDragMouseMoved(dsde); 453 } 454 455 /** 456 * Returns the <code>Transferable</code> associated with 457 * this <code>DragSourceContext</code>. 458 * 459 * @return the <code>Transferable</code> 460 */ 461 public Transferable getTransferable() { return transferable; } 462 463 /** 464 * If the default drag cursor behavior is active, this method 465 * sets the default drag cursor for the specified actions 466 * supported by the drag source, the drop target action, 467 * and status, otherwise this method does nothing. 468 * 469 * @param sourceAct the actions supported by the drag source 470 * @param targetAct the drop target action 471 * @param status one of the fields <code>DEFAULT</code>, 472 * <code>ENTER</code>, <code>OVER</code>, 473 * <code>CHANGED</code> 474 */ 475 @SuppressWarnings("fallthrough") 476 protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) { 477 478 // if the cursor has been previously set then don't do any defaults 479 // processing. 480 481 if (useCustomCursor) { 482 return; 483 } 484 485 // do defaults processing 486 487 Cursor c = null; 488 489 switch (status) { 490 default: 491 targetAct = DnDConstants.ACTION_NONE; 492 case ENTER: 493 case OVER: 494 case CHANGED: 495 int ra = sourceAct & targetAct; 496 497 if (ra == DnDConstants.ACTION_NONE) { // no drop possible 498 if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) 499 c = DragSource.DefaultLinkNoDrop; 500 else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) 501 c = DragSource.DefaultMoveNoDrop; 502 else 503 c = DragSource.DefaultCopyNoDrop; 504 } else { // drop possible 505 if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) 506 c = DragSource.DefaultLinkDrop; 507 else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) 508 c = DragSource.DefaultMoveDrop; 509 else 510 c = DragSource.DefaultCopyDrop; 511 } 512 } 513 514 setCursorImpl(c); 515 } 516 517 private void setCursorImpl(Cursor c) { 518 if (cursor == null || !cursor.equals(c)) { 519 cursor = c; 520 if (peer != null) peer.setCursor(cursor); 521 } 522 } 523 524 /** 525 * Serializes this <code>DragSourceContext</code>. This method first 526 * performs default serialization. Next, this object's 527 * <code>Transferable</code> is written out if and only if it can be 528 * serialized. If not, <code>null</code> is written instead. In this case, 529 * a <code>DragSourceContext</code> created from the resulting deserialized 530 * stream will contain a dummy <code>Transferable</code> which supports no 531 * <code>DataFlavor</code>s. Finally, this object's 532 * <code>DragSourceListener</code> is written out if and only if it can be 533 * serialized. If not, <code>null</code> is written instead. 534 * 535 * @serialData The default serializable fields, in alphabetical order, 536 * followed by either a <code>Transferable</code> instance, or 537 * <code>null</code>, followed by either a 538 * <code>DragSourceListener</code> instance, or 539 * <code>null</code>. 540 * @since 1.4 541 */ 542 private void writeObject(ObjectOutputStream s) throws IOException { 543 s.defaultWriteObject(); 544 545 s.writeObject(SerializationTester.test(transferable) 546 ? transferable : null); 547 s.writeObject(SerializationTester.test(listener) 548 ? listener : null); 549 } 550 551 /** 552 * Deserializes this <code>DragSourceContext</code>. This method first 553 * performs default deserialization for all non-<code>transient</code> 554 * fields. This object's <code>Transferable</code> and 555 * <code>DragSourceListener</code> are then deserialized as well by using 556 * the next two objects in the stream. If the resulting 557 * <code>Transferable</code> is <code>null</code>, this object's 558 * <code>Transferable</code> is set to a dummy <code>Transferable</code> 559 * which supports no <code>DataFlavor</code>s. 560 * 561 * @since 1.4 562 */ 563 private void readObject(ObjectInputStream s) 564 throws ClassNotFoundException, IOException 565 { 566 ObjectInputStream.GetField f = s.readFields(); 567 568 DragGestureEvent newTrigger = (DragGestureEvent)f.get("trigger", null); 569 if (newTrigger == null) { 570 throw new InvalidObjectException("Null trigger"); 571 } 572 if (newTrigger.getDragSource() == null) { 573 throw new InvalidObjectException("Null DragSource"); 574 } 575 if (newTrigger.getComponent() == null) { 576 throw new InvalidObjectException("Null trigger component"); 577 } 578 579 int newSourceActions = f.get("sourceActions", 0) 580 & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK); 581 if (newSourceActions == DnDConstants.ACTION_NONE) { 582 throw new InvalidObjectException("Invalid source actions"); 583 } 584 int triggerActions = newTrigger.getDragAction(); 585 if (triggerActions != DnDConstants.ACTION_COPY && 586 triggerActions != DnDConstants.ACTION_MOVE && 587 triggerActions != DnDConstants.ACTION_LINK) { 588 throw new InvalidObjectException("No drag action"); 589 } 590 trigger = newTrigger; 591 592 cursor = (Cursor)f.get("cursor", null); 593 useCustomCursor = f.get("useCustomCursor", false); 594 sourceActions = newSourceActions; 595 596 transferable = (Transferable)s.readObject(); 597 listener = (DragSourceListener)s.readObject(); 598 599 // Implementation assumes 'transferable' is never null. 600 if (transferable == null) { 601 if (emptyTransferable == null) { 602 emptyTransferable = new Transferable() { 603 public DataFlavor[] getTransferDataFlavors() { 604 return new DataFlavor[0]; 605 } 606 public boolean isDataFlavorSupported(DataFlavor flavor) 607 { 608 return false; 609 } 610 public Object getTransferData(DataFlavor flavor) 611 throws UnsupportedFlavorException 612 { 613 throw new UnsupportedFlavorException(flavor); 614 } 615 }; 616 } 617 transferable = emptyTransferable; 618 } 619 } 620 621 private static Transferable emptyTransferable; 622 623 /* 624 * fields 625 */ 626 627 private transient DragSourceContextPeer peer; 628 629 /** 630 * The event which triggered the start of the drag. 631 * 632 * @serial 633 */ 634 private DragGestureEvent trigger; 635 636 /** 637 * The current drag cursor. 638 * 639 * @serial 640 */ 641 private Cursor cursor; 642 643 private transient Transferable transferable; 644 645 private transient DragSourceListener listener; 646 647 /** 648 * <code>true</code> if the custom drag cursor is used instead of the 649 * default one. 650 * 651 * @serial 652 */ 653 private boolean useCustomCursor; 654 655 /** 656 * A bitwise mask of <code>DnDConstants</code> that represents the set of 657 * drop actions supported by the drag source for the drag operation associated 658 * with this <code>DragSourceContext.</code> 659 * 660 * @serial 661 */ 662 private int sourceActions; 663 }