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