1/* 2 * Copyright (c) 1997, 2017, 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 26package java.awt.dnd; 27 28import java.awt.AWTError; 29import java.awt.Component; 30import java.awt.Cursor; 31import java.awt.Image; 32import java.awt.Point; 33import java.awt.Toolkit; 34import java.awt.datatransfer.DataFlavor; 35import java.awt.datatransfer.Transferable; 36import java.awt.datatransfer.UnsupportedFlavorException; 37import java.awt.dnd.peer.DragSourceContextPeer; 38import java.io.IOException; 39import java.io.InvalidObjectException; 40import java.io.ObjectInputStream; 41import java.io.ObjectOutputStream; 42import java.io.Serializable; 43import java.util.TooManyListenersException; 44 45import sun.awt.AWTAccessor; 46import 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 id="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 92public 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 custom 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 custom drag {@code Cursor}. 302 * 303 * @return the current custom drag {@code Cursor}, if it was set 304 * otherwise returns {@code null}. 305 * @see #setCursor 306 */ 307 308 public Cursor getCursor() { return cursor; } 309 310 /** 311 * Add a {@code DragSourceListener} to this 312 * {@code DragSourceContext} if one has not already been added. 313 * If a {@code DragSourceListener} already exists, 314 * this method throws a {@code TooManyListenersException}. 315 * 316 * @param dsl the {@code DragSourceListener} to add. 317 * Note that while {@code null} is not prohibited, 318 * it is not acceptable as a parameter. 319 * 320 * @throws TooManyListenersException if 321 * a {@code DragSourceListener} has already been added 322 */ 323 324 public synchronized void addDragSourceListener(DragSourceListener dsl) throws TooManyListenersException { 325 if (dsl == null) return; 326 327 if (equals(dsl)) throw new IllegalArgumentException("DragSourceContext may not be its own listener"); 328 329 if (listener != null) 330 throw new TooManyListenersException(); 331 else 332 listener = dsl; 333 } 334 335 /** 336 * Removes the specified {@code DragSourceListener} 337 * from this {@code DragSourceContext}. 338 * 339 * @param dsl the {@code DragSourceListener} to remove; 340 * note that while {@code null} is not prohibited, 341 * it is not acceptable as a parameter 342 */ 343 344 public synchronized void removeDragSourceListener(DragSourceListener dsl) { 345 if (listener != null && listener.equals(dsl)) { 346 listener = null; 347 } else 348 throw new IllegalArgumentException(); 349 } 350 351 /** 352 * Notifies the peer that the {@code Transferable}'s 353 * {@code DataFlavor}s have changed. 354 */ 355 356 public void transferablesFlavorsChanged() { 357 if (peer != null) peer.transferablesFlavorsChanged(); 358 } 359 360 /** 361 * Calls {@code dragEnter} on the 362 * {@code DragSourceListener}s registered with this 363 * {@code DragSourceContext} and with the associated 364 * {@code DragSource}, and passes them the specified 365 * {@code DragSourceDragEvent}. 366 * 367 * @param dsde the {@code DragSourceDragEvent} 368 */ 369 public void dragEnter(DragSourceDragEvent dsde) { 370 DragSourceListener dsl = listener; 371 if (dsl != null) { 372 dsl.dragEnter(dsde); 373 } 374 getDragSource().processDragEnter(dsde); 375 376 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER); 377 } 378 379 /** 380 * Calls {@code dragOver} on the 381 * {@code DragSourceListener}s registered with this 382 * {@code DragSourceContext} and with the associated 383 * {@code DragSource}, and passes them the specified 384 * {@code DragSourceDragEvent}. 385 * 386 * @param dsde the {@code DragSourceDragEvent} 387 */ 388 public void dragOver(DragSourceDragEvent dsde) { 389 DragSourceListener dsl = listener; 390 if (dsl != null) { 391 dsl.dragOver(dsde); 392 } 393 getDragSource().processDragOver(dsde); 394 395 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER); 396 } 397 398 /** 399 * Calls {@code dragExit} on the 400 * {@code DragSourceListener}s registered with this 401 * {@code DragSourceContext} and with the associated 402 * {@code DragSource}, and passes them the specified 403 * {@code DragSourceEvent}. 404 * 405 * @param dse the {@code DragSourceEvent} 406 */ 407 public void dragExit(DragSourceEvent dse) { 408 DragSourceListener dsl = listener; 409 if (dsl != null) { 410 dsl.dragExit(dse); 411 } 412 getDragSource().processDragExit(dse); 413 414 updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT); 415 } 416 417 /** 418 * Calls {@code dropActionChanged} on the 419 * {@code DragSourceListener}s registered with this 420 * {@code DragSourceContext} and with the associated 421 * {@code DragSource}, and passes them the specified 422 * {@code DragSourceDragEvent}. 423 * 424 * @param dsde the {@code DragSourceDragEvent} 425 */ 426 public void dropActionChanged(DragSourceDragEvent dsde) { 427 DragSourceListener dsl = listener; 428 if (dsl != null) { 429 dsl.dropActionChanged(dsde); 430 } 431 getDragSource().processDropActionChanged(dsde); 432 433 updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED); 434 } 435 436 /** 437 * Calls {@code dragDropEnd} on the 438 * {@code DragSourceListener}s registered with this 439 * {@code DragSourceContext} and with the associated 440 * {@code DragSource}, and passes them the specified 441 * {@code DragSourceDropEvent}. 442 * 443 * @param dsde the {@code DragSourceDropEvent} 444 */ 445 public void dragDropEnd(DragSourceDropEvent dsde) { 446 DragSourceListener dsl = listener; 447 if (dsl != null) { 448 dsl.dragDropEnd(dsde); 449 } 450 getDragSource().processDragDropEnd(dsde); 451 } 452 453 /** 454 * Calls {@code dragMouseMoved} on the 455 * {@code DragSourceMotionListener}s registered with the 456 * {@code DragSource} associated with this 457 * {@code DragSourceContext}, and them passes the specified 458 * {@code DragSourceDragEvent}. 459 * 460 * @param dsde the {@code DragSourceDragEvent} 461 * @since 1.4 462 */ 463 public void dragMouseMoved(DragSourceDragEvent dsde) { 464 getDragSource().processDragMouseMoved(dsde); 465 } 466 467 /** 468 * Returns the {@code Transferable} associated with 469 * this {@code DragSourceContext}. 470 * 471 * @return the {@code Transferable} 472 */ 473 public Transferable getTransferable() { return transferable; } 474 475 /** 476 * If the default drag cursor behavior is active, this method 477 * sets the default drag cursor for the specified actions 478 * supported by the drag source, the drop target action, 479 * and status, otherwise this method does nothing. 480 * 481 * @param sourceAct the actions supported by the drag source 482 * @param targetAct the drop target action 483 * @param status one of the fields {@code DEFAULT}, 484 * {@code ENTER}, {@code OVER}, 485 * {@code CHANGED} 486 */ 487 @SuppressWarnings("fallthrough") 488 protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) { 489 490 // if the cursor has been previously set then don't do any defaults 491 // processing. 492 493 if (useCustomCursor) { 494 return; 495 } 496 497 // do defaults processing 498 499 Cursor c = null; 500 501 switch (status) { 502 default: 503 targetAct = DnDConstants.ACTION_NONE; 504 case ENTER: 505 case OVER: 506 case CHANGED: 507 int ra = sourceAct & targetAct; 508 509 if (ra == DnDConstants.ACTION_NONE) { // no drop possible 510 if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) 511 c = DragSource.DefaultLinkNoDrop; 512 else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) 513 c = DragSource.DefaultMoveNoDrop; 514 else 515 c = DragSource.DefaultCopyNoDrop; 516 } else { // drop possible 517 if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) 518 c = DragSource.DefaultLinkDrop; 519 else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) 520 c = DragSource.DefaultMoveDrop; 521 else 522 c = DragSource.DefaultCopyDrop; 523 } 524 } 525 526 setCursorImpl(c); 527 } 528 529 private void setCursorImpl(Cursor c) { 530 if (cursor == null || !cursor.equals(c)) { 531 cursor = c; 532 if (peer != null) peer.setCursor(cursor); 533 } 534 } 535 536 /** 537 * Serializes this {@code DragSourceContext}. This method first 538 * performs default serialization. Next, this object's 539 * {@code Transferable} is written out if and only if it can be 540 * serialized. If not, {@code null} is written instead. In this case, 541 * a {@code DragSourceContext} created from the resulting deserialized 542 * stream will contain a dummy {@code Transferable} which supports no 543 * {@code DataFlavor}s. Finally, this object's 544 * {@code DragSourceListener} is written out if and only if it can be 545 * serialized. If not, {@code null} is written instead. 546 * 547 * @serialData The default serializable fields, in alphabetical order, 548 * followed by either a {@code Transferable} instance, or 549 * {@code null}, followed by either a 550 * {@code DragSourceListener} instance, or 551 * {@code null}. 552 * @since 1.4 553 */ 554 private void writeObject(ObjectOutputStream s) throws IOException { 555 s.defaultWriteObject(); 556 557 s.writeObject(SerializationTester.test(transferable) 558 ? transferable : null); 559 s.writeObject(SerializationTester.test(listener) 560 ? listener : null); 561 } 562 563 /** 564 * Deserializes this {@code DragSourceContext}. This method first 565 * performs default deserialization for all non-{@code transient} 566 * fields. This object's {@code Transferable} and 567 * {@code DragSourceListener} are then deserialized as well by using 568 * the next two objects in the stream. If the resulting 569 * {@code Transferable} is {@code null}, this object's 570 * {@code Transferable} is set to a dummy {@code Transferable} 571 * which supports no {@code DataFlavor}s. 572 * 573 * @since 1.4 574 */ 575 private void readObject(ObjectInputStream s) 576 throws ClassNotFoundException, IOException 577 { 578 ObjectInputStream.GetField f = s.readFields(); 579 580 DragGestureEvent newTrigger = (DragGestureEvent)f.get("trigger", null); 581 if (newTrigger == null) { 582 throw new InvalidObjectException("Null trigger"); 583 } 584 if (newTrigger.getDragSource() == null) { 585 throw new InvalidObjectException("Null DragSource"); 586 } 587 if (newTrigger.getComponent() == null) { 588 throw new InvalidObjectException("Null trigger component"); 589 } 590 591 int newSourceActions = f.get("sourceActions", 0) 592 & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK); 593 if (newSourceActions == DnDConstants.ACTION_NONE) { 594 throw new InvalidObjectException("Invalid source actions"); 595 } 596 int triggerActions = newTrigger.getDragAction(); 597 if (triggerActions != DnDConstants.ACTION_COPY && 598 triggerActions != DnDConstants.ACTION_MOVE && 599 triggerActions != DnDConstants.ACTION_LINK) { 600 throw new InvalidObjectException("No drag action"); 601 } 602 trigger = newTrigger; 603 604 cursor = (Cursor)f.get("cursor", null); 605 useCustomCursor = f.get("useCustomCursor", false); 606 sourceActions = newSourceActions; 607 608 transferable = (Transferable)s.readObject(); 609 listener = (DragSourceListener)s.readObject(); 610 611 // Implementation assumes 'transferable' is never null. 612 if (transferable == null) { 613 if (emptyTransferable == null) { 614 emptyTransferable = new Transferable() { 615 public DataFlavor[] getTransferDataFlavors() { 616 return new DataFlavor[0]; 617 } 618 public boolean isDataFlavorSupported(DataFlavor flavor) 619 { 620 return false; 621 } 622 public Object getTransferData(DataFlavor flavor) 623 throws UnsupportedFlavorException 624 { 625 throw new UnsupportedFlavorException(flavor); 626 } 627 }; 628 } 629 transferable = emptyTransferable; 630 } 631 } 632 633 private static Transferable emptyTransferable; 634 635 /* 636 * fields 637 */ 638 private final transient DragSourceContextPeer peer; 639 640 /** 641 * The event which triggered the start of the drag. 642 * 643 * @serial 644 */ 645 private DragGestureEvent trigger; 646 647 /** 648 * The current drag cursor. 649 * 650 * @serial 651 */ 652 private Cursor cursor; 653 654 private transient Transferable transferable; 655 656 private transient DragSourceListener listener; 657 658 /** 659 * {@code true} if the custom drag cursor is used instead of the 660 * default one. 661 * 662 * @serial 663 */ 664 private boolean useCustomCursor; 665 666 /** 667 * A bitwise mask of {@code DnDConstants} that represents the set of 668 * drop actions supported by the drag source for the drag operation associated 669 * with this {@code DragSourceContext.} 670 * 671 * @serial 672 */ 673 private int sourceActions; 674} 675