1 /*
   2  * Copyright (c) 2000, 2020, 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 sun.awt.dnd;
  27 
  28 import java.awt.AWTEvent;
  29 import java.awt.Component;
  30 import java.awt.Cursor;
  31 import java.awt.EventQueue;
  32 import java.awt.Image;
  33 import java.awt.Point;
  34 
  35 import java.awt.datatransfer.Transferable;
  36 
  37 import java.awt.dnd.DnDConstants;
  38 import java.awt.dnd.DragSourceContext;
  39 import java.awt.dnd.DragSourceEvent;
  40 import java.awt.dnd.DragSourceDropEvent;
  41 import java.awt.dnd.DragSourceDragEvent;
  42 import java.awt.dnd.DragGestureEvent;
  43 import java.awt.dnd.InvalidDnDOperationException;
  44 
  45 import java.awt.dnd.peer.DragSourceContextPeer;
  46 
  47 import java.awt.event.InputEvent;
  48 import java.awt.event.MouseEvent;
  49 
  50 import java.util.Map;
  51 import java.util.SortedMap;
  52 
  53 import sun.awt.SunToolkit;
  54 import sun.awt.datatransfer.DataTransferer;
  55 import java.awt.datatransfer.DataFlavor;
  56 
  57 
  58 /**
  59  * <p>
  60  * TBC
  61  * </p>
  62  *
  63  * @since 1.3.1
  64  *
  65  */
  66 public abstract class SunDragSourceContextPeer implements DragSourceContextPeer {
  67 
  68     private DragGestureEvent  trigger;
  69     private Component         component;
  70     private Cursor            cursor;
  71     private Image             dragImage;
  72     private Point             dragImageOffset;
  73     private long              nativeCtxt;
  74     private DragSourceContext dragSourceContext;
  75     private int               sourceActions;
  76 
  77     private static volatile boolean dragDropInProgress = false;
  78     private static boolean discardingMouseEvents = false;
  79 
  80     /*
  81      * dispatch constants
  82      */
  83 
  84     protected static final int DISPATCH_ENTER   = 1;
  85     protected static final int DISPATCH_MOTION  = 2;
  86     protected static final int DISPATCH_CHANGED = 3;
  87     protected static final int DISPATCH_EXIT    = 4;
  88     protected static final int DISPATCH_FINISH  = 5;
  89     protected static final int DISPATCH_MOUSE_MOVED  = 6;
  90 
  91     /**
  92      * construct a new SunDragSourceContextPeer
  93      */
  94 
  95     public SunDragSourceContextPeer(DragGestureEvent dge) {
  96         trigger = dge;
  97         if (trigger != null) {
  98             component = trigger.getComponent();
  99         } else {
 100             component = null;
 101         }
 102     }
 103 
 104     /**
 105      * Synchro messages in AWT
 106      */
 107     public void startSecondaryEventLoop(){}
 108     public void quitSecondaryEventLoop(){}
 109 
 110     /**
 111      * initiate a DnD operation ...
 112      */
 113 
 114     public void startDrag(DragSourceContext dsc, Cursor c, Image di, Point p)
 115       throws InvalidDnDOperationException {
 116 
 117         /* Fix for 4354044: don't initiate a drag if event sequence provided by
 118          * DragGestureRecognizer is empty */
 119         if (getTrigger().getTriggerEvent() == null) {
 120             throw new InvalidDnDOperationException("DragGestureEvent has a null trigger");
 121         }
 122 
 123         dragSourceContext = dsc;
 124         cursor            = c;
 125         sourceActions     = getDragSourceContext().getSourceActions();
 126         dragImage         = di;
 127         dragImageOffset   = p;
 128 
 129         Transferable transferable  = getDragSourceContext().getTransferable();
 130         SortedMap<Long,DataFlavor> formatMap = DataTransferer.getInstance().
 131             getFormatsForTransferable(transferable, DataTransferer.adaptFlavorMap
 132                 (getTrigger().getDragSource().getFlavorMap()));
 133         long[] formats = DataTransferer.keysToLongArray(formatMap);
 134         startDrag(transferable, formats, formatMap);
 135 
 136         /*
 137          * Fix for 4613903.
 138          * Filter out all mouse events that are currently on the event queue.
 139          */
 140         discardingMouseEvents = true;
 141         EventQueue.invokeLater(new Runnable() {
 142                 public void run() {
 143                     discardingMouseEvents = false;
 144                 }
 145             });
 146     }
 147 
 148     protected abstract void startDrag(Transferable trans,
 149                                       long[] formats, Map<Long, DataFlavor> formatMap);
 150 
 151     /**
 152      * set cursor
 153      */
 154 
 155     public void setCursor(Cursor c) throws InvalidDnDOperationException {
 156         synchronized (this) {
 157             if (cursor == null || !cursor.equals(c)) {
 158                 cursor = c;
 159                 // NOTE: native context can be null at this point.
 160                 // setNativeCursor() should handle it properly.
 161                 setNativeCursor(getNativeContext(), c,
 162                                 c != null ? c.getType() : 0);
 163             }
 164         }
 165     }
 166 
 167     /**
 168      * return cursor
 169      */
 170 
 171     public Cursor getCursor() {
 172         return cursor;
 173     }
 174 
 175     /**
 176      * Returns the drag image. If there is no image to drag,
 177      * the returned value is {@code null}
 178      *
 179      * @return the reference to the drag image
 180      */
 181     public Image getDragImage() {
 182         return dragImage;
 183     }
 184 
 185     /**
 186      * Returns an anchor offset for the image to drag.
 187      *
 188      * @return a {@code Point} object that corresponds
 189      * to coordinates of an anchor offset of the image
 190      * relative to the upper left corner of the image.
 191      * The point {@code (0,0)} returns by default.
 192      */
 193     public Point getDragImageOffset() {
 194         if (dragImageOffset == null) {
 195             return new Point(0,0);
 196         }
 197         return new Point(dragImageOffset);
 198     }
 199 
 200     /**
 201      * downcall into native code
 202      */
 203 
 204 
 205     protected abstract void setNativeCursor(long nativeCtxt, Cursor c,
 206                                             int cType);
 207 
 208     protected synchronized void setTrigger(DragGestureEvent dge) {
 209         trigger = dge;
 210         if (trigger != null) {
 211             component = trigger.getComponent();
 212         } else {
 213             component = null;
 214         }
 215     }
 216 
 217     protected DragGestureEvent getTrigger() {
 218         return trigger;
 219     }
 220 
 221     protected Component getComponent() {
 222         return component;
 223     }
 224 
 225     protected synchronized void setNativeContext(long ctxt) {
 226         nativeCtxt = ctxt;
 227     }
 228 
 229     protected synchronized long getNativeContext() {
 230         return nativeCtxt;
 231     }
 232 
 233     protected DragSourceContext getDragSourceContext() {
 234         return dragSourceContext;
 235     }
 236 
 237     /**
 238      * Notify the peer that the transferables' DataFlavors have changed.
 239      *
 240      * No longer useful as the transferables are determined at the time
 241      * of the drag.
 242      */
 243 
 244     public void transferablesFlavorsChanged() {
 245     }
 246 
 247 
 248 
 249 
 250 
 251     protected final void postDragSourceDragEvent(final int targetAction,
 252                                                  final int modifiers,
 253                                                  final int x, final int y,
 254                                                  final int dispatchType) {
 255 
 256         final int dropAction =
 257             SunDragSourceContextPeer.convertModifiersToDropAction(modifiers,
 258                                                                   sourceActions);
 259 
 260         DragSourceDragEvent event =
 261             new DragSourceDragEvent(getDragSourceContext(),
 262                                     dropAction,
 263                                     targetAction & sourceActions,
 264                                     modifiers, x, y);
 265         EventDispatcher dispatcher = new EventDispatcher(dispatchType, event);
 266 
 267         SunToolkit.invokeLaterOnAppContext(
 268             SunToolkit.targetToAppContext(getComponent()), dispatcher);
 269 
 270         startSecondaryEventLoop();
 271     }
 272 
 273     /**
 274      * upcall from native code
 275      */
 276 
 277     protected void dragEnter(final int targetActions,
 278                            final int modifiers,
 279                            final int x, final int y) {
 280         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_ENTER);
 281     }
 282 
 283     /**
 284      * upcall from native code
 285      */
 286 
 287     private void dragMotion(final int targetActions,
 288                             final int modifiers,
 289                             final int x, final int y) {
 290         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_MOTION);
 291     }
 292 
 293     /**
 294      * upcall from native code
 295      */
 296 
 297     private void operationChanged(final int targetActions,
 298                                   final int modifiers,
 299                                   final int x, final int y) {
 300         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_CHANGED);
 301     }
 302 
 303     /**
 304      * upcall from native code
 305      */
 306 
 307     protected final void dragExit(final int x, final int y) {
 308         DragSourceEvent event =
 309             new DragSourceEvent(getDragSourceContext(), x, y);
 310         EventDispatcher dispatcher =
 311             new EventDispatcher(DISPATCH_EXIT, event);
 312 
 313         SunToolkit.invokeLaterOnAppContext(
 314             SunToolkit.targetToAppContext(getComponent()), dispatcher);
 315 
 316         startSecondaryEventLoop();
 317     }
 318 
 319     /**
 320      * upcall from native code
 321      */
 322 
 323     private void dragMouseMoved(final int targetActions,
 324                                 final int modifiers,
 325                                 final int x, final int y) {
 326         postDragSourceDragEvent(targetActions, modifiers, x, y,
 327                                 DISPATCH_MOUSE_MOVED);
 328     }
 329 
 330     /**
 331      * upcall from native code via implemented class (do)
 332      */
 333 
 334     protected final void dragDropFinished(final boolean success,
 335                                           final int operations,
 336                                           final int x, final int y) {
 337         DragSourceEvent event =
 338             new DragSourceDropEvent(getDragSourceContext(),
 339                                     operations & sourceActions,
 340                                     success, x, y);
 341         EventDispatcher dispatcher =
 342             new EventDispatcher(DISPATCH_FINISH, event);
 343 
 344         SunToolkit.invokeLaterOnAppContext(
 345             SunToolkit.targetToAppContext(getComponent()), dispatcher);
 346 
 347         startSecondaryEventLoop();
 348         setNativeContext(0);
 349         dragImage = null;
 350         dragImageOffset = null;
 351     }
 352 
 353     public static void setDragDropInProgress(boolean b)
 354       throws InvalidDnDOperationException {
 355         synchronized (SunDragSourceContextPeer.class) {
 356             if (dragDropInProgress == b) {
 357                 throw new InvalidDnDOperationException(getExceptionMessage(b));
 358             }
 359             dragDropInProgress = b;
 360         }
 361     }
 362 
 363     /**
 364      * Filters out all mouse events that were on the java event queue when
 365      * startDrag was called.
 366      */
 367     public static boolean checkEvent(AWTEvent event) {
 368         if (discardingMouseEvents && event instanceof MouseEvent) {
 369             MouseEvent mouseEvent = (MouseEvent)event;
 370             if (!(mouseEvent instanceof SunDropTargetEvent)) {
 371                 return false;
 372             }
 373         }
 374         return true;
 375     }
 376 
 377     public static void checkDragDropInProgress()
 378       throws InvalidDnDOperationException {
 379         if (dragDropInProgress) {
 380             throw new InvalidDnDOperationException(getExceptionMessage(true));
 381         }
 382     }
 383 
 384     public static boolean isDragDropInProgress() {
 385         return dragDropInProgress;
 386     }
 387 
 388     private static String getExceptionMessage(boolean b) {
 389         return b ? "Drag and drop in progress" : "No drag in progress";
 390     }
 391 
 392     public static int convertModifiersToDropAction(final int modifiers,
 393                                                    final int supportedActions) {
 394         int dropAction = DnDConstants.ACTION_NONE;
 395 
 396         /*
 397          * Fix for 4285634.
 398          * Calculate the drop action to match Motif DnD behavior.
 399          * If the user selects an operation (by pressing a modifier key),
 400          * return the selected operation or ACTION_NONE if the selected
 401          * operation is not supported by the drag source.
 402          * If the user doesn't select an operation search the set of operations
 403          * supported by the drag source for ACTION_MOVE, then for
 404          * ACTION_COPY, then for ACTION_LINK and return the first operation
 405          * found.
 406          */
 407         switch (modifiers & (InputEvent.SHIFT_DOWN_MASK |
 408                              InputEvent.CTRL_DOWN_MASK)) {
 409         case InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK:
 410             dropAction = DnDConstants.ACTION_LINK; break;
 411         case InputEvent.CTRL_DOWN_MASK:
 412             dropAction = DnDConstants.ACTION_COPY; break;
 413         case InputEvent.SHIFT_DOWN_MASK:
 414             dropAction = DnDConstants.ACTION_MOVE; break;
 415         default:
 416             if ((supportedActions & DnDConstants.ACTION_MOVE) != 0) {
 417                 dropAction = DnDConstants.ACTION_MOVE;
 418             } else if ((supportedActions & DnDConstants.ACTION_COPY) != 0) {
 419                 dropAction = DnDConstants.ACTION_COPY;
 420             } else if ((supportedActions & DnDConstants.ACTION_LINK) != 0) {
 421                 dropAction = DnDConstants.ACTION_LINK;
 422             }
 423         }
 424 
 425         return dropAction & supportedActions;
 426     }
 427 
 428     private void cleanup() {
 429         trigger = null;
 430         component = null;
 431         cursor = null;
 432         dragSourceContext = null;
 433         SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(null);
 434         SunDragSourceContextPeer.setDragDropInProgress(false);
 435     }
 436 
 437     private class EventDispatcher implements Runnable {
 438 
 439         private final int dispatchType;
 440 
 441         private final DragSourceEvent event;
 442 
 443         EventDispatcher(int dispatchType, DragSourceEvent event) {
 444             switch (dispatchType) {
 445             case DISPATCH_ENTER:
 446             case DISPATCH_MOTION:
 447             case DISPATCH_CHANGED:
 448             case DISPATCH_MOUSE_MOVED:
 449                 if (!(event instanceof DragSourceDragEvent)) {
 450                     throw new IllegalArgumentException("Event: " + event);
 451                 }
 452                 break;
 453             case DISPATCH_EXIT:
 454                 break;
 455             case DISPATCH_FINISH:
 456                 if (!(event instanceof DragSourceDropEvent)) {
 457                     throw new IllegalArgumentException("Event: " + event);
 458                 }
 459                 break;
 460             default:
 461                 throw new IllegalArgumentException("Dispatch type: " +
 462                                                    dispatchType);
 463             }
 464 
 465             this.dispatchType  = dispatchType;
 466             this.event         = event;
 467         }
 468 
 469         public void run() {
 470             DragSourceContext dragSourceContext =
 471                 SunDragSourceContextPeer.this.getDragSourceContext();
 472             try {
 473                 switch (dispatchType) {
 474                 case DISPATCH_ENTER:
 475                     dragSourceContext.dragEnter((DragSourceDragEvent)event);
 476                     break;
 477                 case DISPATCH_MOTION:
 478                     dragSourceContext.dragOver((DragSourceDragEvent)event);
 479                     break;
 480                 case DISPATCH_CHANGED:
 481                     dragSourceContext.dropActionChanged((DragSourceDragEvent)event);
 482                     break;
 483                 case DISPATCH_EXIT:
 484                     dragSourceContext.dragExit(event);
 485                     break;
 486                 case DISPATCH_MOUSE_MOVED:
 487                     dragSourceContext.dragMouseMoved((DragSourceDragEvent)event);
 488                     break;
 489                 case DISPATCH_FINISH:
 490                     try {
 491                         dragSourceContext.dragDropEnd((DragSourceDropEvent)event);
 492                     } finally {
 493                         SunDragSourceContextPeer.this.cleanup();
 494                     }
 495                     break;
 496                 default:
 497                     throw new IllegalStateException("Dispatch type: " +
 498                                                     dispatchType);
 499                 }
 500             } finally {
 501                  SunDragSourceContextPeer.this.quitSecondaryEventLoop();
 502             }
 503         }
 504     }
 505 }