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 import java.awt.Toolkit; 33 import java.awt.datatransfer.DataFlavor; 34 import java.awt.datatransfer.Transferable; 35 import java.awt.datatransfer.UnsupportedFlavorException; 36 import java.awt.dnd.peer.DragSourceContextPeer; 37 import java.io.IOException; 38 import java.io.InvalidObjectException; 39 import java.io.ObjectInputStream; 40 import java.io.ObjectOutputStream; 41 import java.io.Serializable; 42 import java.util.TooManyListenersException; 43 44 import sun.awt.AWTAccessor; 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"></a> 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 static { 126 AWTAccessor.setDragSourceContextAccessor(dsc -> dsc.peer); 127 } 128 129 /** 130 * Called from <code>DragSource</code>, this constructor creates a new 131 * <code>DragSourceContext</code> given the 132 * <code>DragSourceContextPeer</code> for this Drag, the 133 * <code>DragGestureEvent</code> that triggered the Drag, the initial 134 * <code>Cursor</code> to use for the Drag, an (optional) 135 * <code>Image</code> to display while the Drag is taking place, the offset 136 * of the <code>Image</code> origin from the hotspot at the instant of the 137 * triggering event, the <code>Transferable</code> subject data, and the 138 * <code>DragSourceListener</code> to use during the Drag and Drop 139 * operation. 140 * <br> 141 * If <code>DragSourceContextPeer</code> is <code>null</code> 142 * <code>NullPointerException</code> is thrown. 143 * <br> 144 * If <code>DragGestureEvent</code> is <code>null</code> 145 * <code>NullPointerException</code> is thrown. 146 * <br> 147 * If <code>Cursor</code> is <code>null</code> no exception is thrown and 148 * the default drag cursor behavior is activated for this drag operation. 149 * <br> 150 * If <code>Image</code> is <code>null</code> no exception is thrown. 151 * <br> 152 * If <code>Image</code> is not <code>null</code> and the offset is 153 * <code>null</code> <code>NullPointerException</code> is thrown. 154 * <br> 155 * If <code>Transferable</code> is <code>null</code> 156 * <code>NullPointerException</code> is thrown. 157 * <br> 158 * If <code>DragSourceListener</code> is <code>null</code> no exception 159 * is thrown. 160 * 161 * @param trigger the triggering event 162 * @param dragCursor the initial {@code Cursor} for this drag operation 163 * or {@code null} for the default cursor handling; 164 * see <a href="DragSourceContext.html#defaultCursor">class level documentation</a> 165 * for more details on the cursor handling mechanism during drag and drop 166 * @param dragImage the <code>Image</code> to drag (or <code>null</code>) 167 * @param offset the offset of the image origin from the hotspot at the 168 * instant of the triggering event 169 * @param t the <code>Transferable</code> 170 * @param dsl the <code>DragSourceListener</code> 171 * 172 * @throws IllegalArgumentException if the <code>Component</code> associated 173 * with the trigger event is <code>null</code>. 174 * @throws IllegalArgumentException if the <code>DragSource</code> for the 175 * trigger event is <code>null</code>. 176 * @throws IllegalArgumentException if the drag action for the 177 * trigger event is <code>DnDConstants.ACTION_NONE</code>. 178 * @throws IllegalArgumentException if the source actions for the 179 * <code>DragGestureRecognizer</code> associated with the trigger 180 * event are equal to <code>DnDConstants.ACTION_NONE</code>. 181 * @throws NullPointerException if dscp, trigger, or t are null, or 182 * if dragImage is non-null and offset is null 183 */ 184 public DragSourceContext(DragGestureEvent trigger, Cursor dragCursor, 185 Image dragImage, Point offset, Transferable t, 186 DragSourceListener dsl) { 187 DragSourceContextPeer dscp = Toolkit.getDefaultToolkit() 188 .createDragSourceContextPeer(trigger); 189 190 if (dscp == null) { 191 throw new NullPointerException("DragSourceContextPeer"); 192 } 193 194 if (trigger == null) { 195 throw new NullPointerException("Trigger"); 196 } 197 198 if (trigger.getDragSource() == null) { 199 throw new IllegalArgumentException("DragSource"); 200 } 201 202 if (trigger.getComponent() == null) { 203 throw new IllegalArgumentException("Component"); 204 } 205 206 if (trigger.getSourceAsDragGestureRecognizer().getSourceActions() == 207 DnDConstants.ACTION_NONE) { 208 throw new IllegalArgumentException("source actions"); 209 } 210 211 if (trigger.getDragAction() == DnDConstants.ACTION_NONE) { 212 throw new IllegalArgumentException("no drag action"); 213 } 214 215 if (t == null) { 216 throw new NullPointerException("Transferable"); 217 } 218 219 if (dragImage != null && offset == null) { 220 throw new NullPointerException("offset"); 221 } 222 223 peer = dscp; 224 this.trigger = trigger; 225 cursor = dragCursor; 226 transferable = t; 227 listener = dsl; 228 sourceActions = 229 trigger.getSourceAsDragGestureRecognizer().getSourceActions(); 230 231 useCustomCursor = (dragCursor != null); 232 233 updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT); 234 } 235 236 /** 237 * Returns the <code>DragSource</code> 238 * that instantiated this <code>DragSourceContext</code>. 239 * 240 * @return the <code>DragSource</code> that 241 * instantiated this <code>DragSourceContext</code> 242 */ 243 244 public DragSource getDragSource() { return trigger.getDragSource(); } 245 246 /** 247 * Returns the <code>Component</code> associated with this 248 * <code>DragSourceContext</code>. 249 * 250 * @return the <code>Component</code> that started the drag 251 */ 252 253 public Component getComponent() { return trigger.getComponent(); } 254 255 /** 256 * Returns the <code>DragGestureEvent</code> 257 * that initially triggered the drag. 258 * 259 * @return the Event that triggered the drag 260 */ 261 262 public DragGestureEvent getTrigger() { return trigger; } 263 264 /** 265 * Returns a bitwise mask of <code>DnDConstants</code> that 266 * represent the set of drop actions supported by the drag source for the 267 * drag operation associated with this <code>DragSourceContext</code>. 268 * 269 * @return the drop actions supported by the drag source 270 */ 271 public int getSourceActions() { 272 return sourceActions; 273 } 274 275 /** 276 * Sets the cursor for this drag operation to the specified 277 * <code>Cursor</code>. If the specified <code>Cursor</code> 278 * is <code>null</code>, the default drag cursor behavior is 279 * activated for this drag operation, otherwise it is deactivated. 280 * 281 * @param c the initial {@code Cursor} for this drag operation, 282 * or {@code null} for the default cursor handling; 283 * see {@linkplain Cursor class 284 * level documentation} for more details 285 * on the cursor handling during drag and drop 286 * 287 */ 288 289 public synchronized void setCursor(Cursor c) { 290 useCustomCursor = (c != null); 291 setCursorImpl(c); 292 } 293 294 /** 295 * Returns the current drag <code>Cursor</code>. 296 * 297 * @return the current drag <code>Cursor</code> 298 */ 299 300 public Cursor getCursor() { return cursor; } 301 302 /** 303 * Add a <code>DragSourceListener</code> to this 304 * <code>DragSourceContext</code> if one has not already been added. 305 * If a <code>DragSourceListener</code> already exists, 306 * this method throws a <code>TooManyListenersException</code>. 307 * 308 * @param dsl the <code>DragSourceListener</code> to add. 309 * Note that while <code>null</code> is not prohibited, 310 * it is not acceptable as a parameter. 311 * 312 * @throws TooManyListenersException if 313 * a <code>DragSourceListener</code> has already been added 314 */ 315 316 public synchronized void addDragSourceListener(DragSourceListener dsl) throws TooManyListenersException { 317 if (dsl == null) return; 318 319 if (equals(dsl)) throw new IllegalArgumentException("DragSourceContext may not be its own listener"); 320 321 if (listener != null) 322 throw new TooManyListenersException(); 323 else 324 listener = dsl; 325 } 326 327 /** 328 * Removes the specified <code>DragSourceListener</code> 329 * from this <code>DragSourceContext</code>. 330 * 331 * @param dsl the <code>DragSourceListener</code> to remove; 332 * note that while <code>null</code> is not prohibited, 333 * it is not acceptable as a parameter 334 */ 335 336 public synchronized void removeDragSourceListener(DragSourceListener dsl) { 337 if (listener != null && listener.equals(dsl)) { 338 listener = null; 339 } else 340 throw new IllegalArgumentException(); 341 } 342 343 /** 344 * Notifies the peer that the <code>Transferable</code>'s 345 * <code>DataFlavor</code>s have changed. 346 */ 347 348 public void transferablesFlavorsChanged() { 349 if (peer != null) peer.transferablesFlavorsChanged(); 350 } 351 352 /** 353 * Calls <code>dragEnter</code> on the 354 * <code>DragSourceListener</code>s registered with this 355 * <code>DragSourceContext</code> and with the associated 356 * <code>DragSource</code>, and passes them the specified 357 * <code>DragSourceDragEvent</code>. 358 * 359 * @param dsde the <code>DragSourceDragEvent</code> 360 */ 361 public void dragEnter(DragSourceDragEvent dsde) { 362 DragSourceListener dsl = listener; 363 if (dsl != null) { 364 dsl.dragEnter(dsde); 365 } 366 getDragSource().processDragEnter(dsde); 367 368 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER); 369 } 370 371 /** 372 * Calls <code>dragOver</code> on the 373 * <code>DragSourceListener</code>s registered with this 374 * <code>DragSourceContext</code> and with the associated 375 * <code>DragSource</code>, and passes them the specified 376 * <code>DragSourceDragEvent</code>. 377 * 378 * @param dsde the <code>DragSourceDragEvent</code> 379 */ 380 public void dragOver(DragSourceDragEvent dsde) { 381 DragSourceListener dsl = listener; 382 if (dsl != null) { 383 dsl.dragOver(dsde); 384 } 385 getDragSource().processDragOver(dsde); 386 387 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER); 388 } 389 390 /** 391 * Calls <code>dragExit</code> on the 392 * <code>DragSourceListener</code>s registered with this 393 * <code>DragSourceContext</code> and with the associated 394 * <code>DragSource</code>, and passes them the specified 395 * <code>DragSourceEvent</code>. 396 * 397 * @param dse the <code>DragSourceEvent</code> 398 */ 399 public void dragExit(DragSourceEvent dse) { 400 DragSourceListener dsl = listener; 401 if (dsl != null) { 402 dsl.dragExit(dse); 403 } 404 getDragSource().processDragExit(dse); 405 406 updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT); 407 } 408 409 /** 410 * Calls <code>dropActionChanged</code> on the 411 * <code>DragSourceListener</code>s registered with this 412 * <code>DragSourceContext</code> and with the associated 413 * <code>DragSource</code>, and passes them the specified 414 * <code>DragSourceDragEvent</code>. 415 * 416 * @param dsde the <code>DragSourceDragEvent</code> 417 */ 418 public void dropActionChanged(DragSourceDragEvent dsde) { 419 DragSourceListener dsl = listener; 420 if (dsl != null) { 421 dsl.dropActionChanged(dsde); 422 } 423 getDragSource().processDropActionChanged(dsde); 424 425 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED); 426 } 427 428 /** 429 * Calls <code>dragDropEnd</code> on the 430 * <code>DragSourceListener</code>s registered with this 431 * <code>DragSourceContext</code> and with the associated 432 * <code>DragSource</code>, and passes them the specified 433 * <code>DragSourceDropEvent</code>. 434 * 435 * @param dsde the <code>DragSourceDropEvent</code> 436 */ 437 public void dragDropEnd(DragSourceDropEvent dsde) { 438 DragSourceListener dsl = listener; 439 if (dsl != null) { 440 dsl.dragDropEnd(dsde); 441 } 442 getDragSource().processDragDropEnd(dsde); 443 } 444 445 /** 446 * Calls <code>dragMouseMoved</code> on the 447 * <code>DragSourceMotionListener</code>s registered with the 448 * <code>DragSource</code> associated with this 449 * <code>DragSourceContext</code>, and them passes the specified 450 * <code>DragSourceDragEvent</code>. 451 * 452 * @param dsde the <code>DragSourceDragEvent</code> 453 * @since 1.4 454 */ 455 public void dragMouseMoved(DragSourceDragEvent dsde) { 456 getDragSource().processDragMouseMoved(dsde); 457 } 458 459 /** 460 * Returns the <code>Transferable</code> associated with 461 * this <code>DragSourceContext</code>. 462 * 463 * @return the <code>Transferable</code> 464 */ 465 public Transferable getTransferable() { return transferable; } 466 467 /** 468 * If the default drag cursor behavior is active, this method 469 * sets the default drag cursor for the specified actions 470 * supported by the drag source, the drop target action, 471 * and status, otherwise this method does nothing. 472 * 473 * @param sourceAct the actions supported by the drag source 474 * @param targetAct the drop target action 475 * @param status one of the fields <code>DEFAULT</code>, 476 * <code>ENTER</code>, <code>OVER</code>, 477 * <code>CHANGED</code> 478 */ 479 @SuppressWarnings("fallthrough") 480 protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) { 481 482 // if the cursor has been previously set then don't do any defaults 483 // processing. 484 485 if (useCustomCursor) { 486 return; 487 } 488 489 // do defaults processing 490 491 Cursor c = null; 492 493 switch (status) { 494 default: 495 targetAct = DnDConstants.ACTION_NONE; 496 case ENTER: 497 case OVER: 498 case CHANGED: 499 int ra = sourceAct & targetAct; 500 501 if (ra == DnDConstants.ACTION_NONE) { // no drop possible 502 if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) 503 c = DragSource.DefaultLinkNoDrop; 504 else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) 505 c = DragSource.DefaultMoveNoDrop; 506 else 507 c = DragSource.DefaultCopyNoDrop; 508 } else { // drop possible 509 if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) 510 c = DragSource.DefaultLinkDrop; 511 else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) 512 c = DragSource.DefaultMoveDrop; 513 else 514 c = DragSource.DefaultCopyDrop; 515 } 516 } 517 518 setCursorImpl(c); 519 } 520 521 private void setCursorImpl(Cursor c) { 522 if (cursor == null || !cursor.equals(c)) { 523 cursor = c; 524 if (peer != null) peer.setCursor(cursor); 525 } 526 } 527 528 /** 529 * Serializes this <code>DragSourceContext</code>. This method first 530 * performs default serialization. Next, this object's 531 * <code>Transferable</code> is written out if and only if it can be 532 * serialized. If not, <code>null</code> is written instead. In this case, 533 * a <code>DragSourceContext</code> created from the resulting deserialized 534 * stream will contain a dummy <code>Transferable</code> which supports no 535 * <code>DataFlavor</code>s. Finally, this object's 536 * <code>DragSourceListener</code> is written out if and only if it can be 537 * serialized. If not, <code>null</code> is written instead. 538 * 539 * @serialData The default serializable fields, in alphabetical order, 540 * followed by either a <code>Transferable</code> instance, or 541 * <code>null</code>, followed by either a 542 * <code>DragSourceListener</code> instance, or 543 * <code>null</code>. 544 * @since 1.4 545 */ 546 private void writeObject(ObjectOutputStream s) throws IOException { 547 s.defaultWriteObject(); 548 549 s.writeObject(SerializationTester.test(transferable) 550 ? transferable : null); 551 s.writeObject(SerializationTester.test(listener) 552 ? listener : null); 553 } 554 555 /** 556 * Deserializes this <code>DragSourceContext</code>. This method first 557 * performs default deserialization for all non-<code>transient</code> 558 * fields. This object's <code>Transferable</code> and 559 * <code>DragSourceListener</code> are then deserialized as well by using 560 * the next two objects in the stream. If the resulting 561 * <code>Transferable</code> is <code>null</code>, this object's 562 * <code>Transferable</code> is set to a dummy <code>Transferable</code> 563 * which supports no <code>DataFlavor</code>s. 564 * 565 * @since 1.4 566 */ 567 private void readObject(ObjectInputStream s) 568 throws ClassNotFoundException, IOException 569 { 570 ObjectInputStream.GetField f = s.readFields(); 571 572 DragGestureEvent newTrigger = (DragGestureEvent)f.get("trigger", null); 573 if (newTrigger == null) { 574 throw new InvalidObjectException("Null trigger"); 575 } 576 if (newTrigger.getDragSource() == null) { 577 throw new InvalidObjectException("Null DragSource"); 578 } 579 if (newTrigger.getComponent() == null) { 580 throw new InvalidObjectException("Null trigger component"); 581 } 582 583 int newSourceActions = f.get("sourceActions", 0) 584 & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK); 585 if (newSourceActions == DnDConstants.ACTION_NONE) { 586 throw new InvalidObjectException("Invalid source actions"); 587 } 588 int triggerActions = newTrigger.getDragAction(); 589 if (triggerActions != DnDConstants.ACTION_COPY && 590 triggerActions != DnDConstants.ACTION_MOVE && 591 triggerActions != DnDConstants.ACTION_LINK) { 592 throw new InvalidObjectException("No drag action"); 593 } 594 trigger = newTrigger; 595 596 cursor = (Cursor)f.get("cursor", null); 597 useCustomCursor = f.get("useCustomCursor", false); 598 sourceActions = newSourceActions; 599 600 transferable = (Transferable)s.readObject(); 601 listener = (DragSourceListener)s.readObject(); 602 603 // Implementation assumes 'transferable' is never null. 604 if (transferable == null) { 605 if (emptyTransferable == null) { 606 emptyTransferable = new Transferable() { 607 public DataFlavor[] getTransferDataFlavors() { 608 return new DataFlavor[0]; 609 } 610 public boolean isDataFlavorSupported(DataFlavor flavor) 611 { 612 return false; 613 } 614 public Object getTransferData(DataFlavor flavor) 615 throws UnsupportedFlavorException 616 { 617 throw new UnsupportedFlavorException(flavor); 618 } 619 }; 620 } 621 transferable = emptyTransferable; 622 } 623 } 624 625 private static Transferable emptyTransferable; 626 627 /* 628 * fields 629 */ 630 private final transient DragSourceContextPeer peer; 631 632 /** 633 * The event which triggered the start of the drag. 634 * 635 * @serial 636 */ 637 private DragGestureEvent trigger; 638 639 /** 640 * The current drag cursor. 641 * 642 * @serial 643 */ 644 private Cursor cursor; 645 646 private transient Transferable transferable; 647 648 private transient DragSourceListener listener; 649 650 /** 651 * <code>true</code> if the custom drag cursor is used instead of the 652 * default one. 653 * 654 * @serial 655 */ 656 private boolean useCustomCursor; 657 658 /** 659 * A bitwise mask of <code>DnDConstants</code> that represents the set of 660 * drop actions supported by the drag source for the drag operation associated 661 * with this <code>DragSourceContext.</code> 662 * 663 * @serial 664 */ 665 private int sourceActions; 666 }