1 /*
   2  * Copyright (c) 2000, 2009, 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 import javax.tools.annotation.GenerateNativeHeader;
  58 
  59 /**
  60  * <p>
  61  * TBC
  62  * </p>
  63  *
  64  * @since JDK1.3.1
  65  *
  66  */
  67 /* No native methods here, but the constants are needed in the supporting JNI code */
  68 @GenerateNativeHeader
  69 public abstract class SunDragSourceContextPeer implements DragSourceContextPeer {
  70 
  71     private DragGestureEvent  trigger;
  72     private Component         component;
  73     private Cursor            cursor;
  74     private Image             dragImage;
  75     private Point             dragImageOffset;
  76     private long              nativeCtxt;
  77     private DragSourceContext dragSourceContext;
  78     private int               sourceActions;
  79 
  80     private static boolean    dragDropInProgress = false;
  81     private static boolean    discardingMouseEvents = false;
  82 
  83     /*
  84      * dispatch constants
  85      */
  86 
  87     protected final static int DISPATCH_ENTER   = 1;
  88     protected final static int DISPATCH_MOTION  = 2;
  89     protected final static int DISPATCH_CHANGED = 3;
  90     protected final static int DISPATCH_EXIT    = 4;
  91     protected final static int DISPATCH_FINISH  = 5;
  92     protected final static int DISPATCH_MOUSE_MOVED  = 6;
  93 
  94     /**
  95      * construct a new SunDragSourceContextPeer
  96      */
  97 
  98     public SunDragSourceContextPeer(DragGestureEvent dge) {
  99         trigger = dge;
 100         if (trigger != null) {
 101             component = trigger.getComponent();
 102         } else {
 103             component = null;
 104         }
 105     }
 106 
 107     /**
 108      * Synchro messages in AWT
 109      */
 110     public void startSecondaryEventLoop(){}
 111     public void quitSecondaryEventLoop(){}
 112 
 113     /**
 114      * initiate a DnD operation ...
 115      */
 116 
 117     public void startDrag(DragSourceContext dsc, Cursor c, Image di, Point p)
 118       throws InvalidDnDOperationException {
 119 
 120         /* Fix for 4354044: don't initiate a drag if event sequence provided by
 121          * DragGestureRecognizer is empty */
 122         if (getTrigger().getTriggerEvent() == null) {
 123             throw new InvalidDnDOperationException("DragGestureEvent has a null trigger");
 124         }
 125 
 126         dragSourceContext = dsc;
 127         cursor            = c;
 128         sourceActions     = getDragSourceContext().getSourceActions();
 129         dragImage         = di;
 130         dragImageOffset   = p;
 131 
 132         Transferable transferable  = getDragSourceContext().getTransferable();
 133         SortedMap<Long,DataFlavor> formatMap = DataTransferer.getInstance().
 134             getFormatsForTransferable(transferable, DataTransferer.adaptFlavorMap
 135                 (getTrigger().getDragSource().getFlavorMap()));
 136         long[] formats = DataTransferer.getInstance().
 137             keysToLongArray(formatMap);
 138         startDrag(transferable, formats, formatMap);
 139 
 140         /*
 141          * Fix for 4613903.
 142          * Filter out all mouse events that are currently on the event queue.
 143          */
 144         discardingMouseEvents = true;
 145         EventQueue.invokeLater(new Runnable() {
 146                 public void run() {
 147                     discardingMouseEvents = false;
 148                 }
 149             });
 150     }
 151 
 152     protected abstract void startDrag(Transferable trans,
 153                                       long[] formats, Map formatMap);
 154 
 155     /**
 156      * set cursor
 157      */
 158 
 159     public void setCursor(Cursor c) throws InvalidDnDOperationException {
 160         synchronized (this) {
 161             if (cursor == null || !cursor.equals(c)) {
 162                 cursor = c;
 163                 // NOTE: native context can be null at this point.
 164                 // setNativeCursor() should handle it properly.
 165                 setNativeCursor(getNativeContext(), c,
 166                                 c != null ? c.getType() : 0);
 167             }
 168         }
 169     }
 170 
 171     /**
 172      * return cursor
 173      */
 174 
 175     public Cursor getCursor() {
 176         return cursor;
 177     }
 178 
 179     /**
 180      * Returns the drag image. If there is no image to drag,
 181      * the returned value is {@code null}
 182      *
 183      * @return the reference to the drag image
 184      */
 185     public Image getDragImage() {
 186         return dragImage;
 187     }
 188 
 189     /**
 190      * Returns an anchor offset for the image to drag.
 191      *
 192      * @return a {@code Point} object that corresponds
 193      * to coordinates of an anchor offset of the image
 194      * relative to the upper left corner of the image.
 195      * The point {@code (0,0)} returns by default.
 196      */
 197     public Point getDragImageOffset() {
 198         if (dragImageOffset == null) {
 199             return new Point(0,0);
 200         }
 201         return new Point(dragImageOffset);
 202     }
 203 
 204     /**
 205      * downcall into native code
 206      */
 207 
 208 
 209     protected abstract void setNativeCursor(long nativeCtxt, Cursor c,
 210                                             int cType);
 211 
 212     protected synchronized void setTrigger(DragGestureEvent dge) {
 213         trigger = dge;
 214         if (trigger != null) {
 215             component = trigger.getComponent();
 216         } else {
 217             component = null;
 218         }
 219     }
 220 
 221     protected DragGestureEvent getTrigger() {
 222         return trigger;
 223     }
 224 
 225     protected Component getComponent() {
 226         return component;
 227     }
 228 
 229     protected synchronized void setNativeContext(long ctxt) {
 230         nativeCtxt = ctxt;
 231     }
 232 
 233     protected synchronized long getNativeContext() {
 234         return nativeCtxt;
 235     }
 236 
 237     protected DragSourceContext getDragSourceContext() {
 238         return dragSourceContext;
 239     }
 240 
 241     /**
 242      * Notify the peer that the transferables' DataFlavors have changed.
 243      *
 244      * No longer useful as the transferables are determined at the time
 245      * of the drag.
 246      */
 247 
 248     public void transferablesFlavorsChanged() {
 249     }
 250 
 251 
 252 
 253 
 254 
 255     protected final void postDragSourceDragEvent(final int targetAction,
 256                                                  final int modifiers,
 257                                                  final int x, final int y,
 258                                                  final int dispatchType) {
 259 
 260         final int dropAction =
 261             SunDragSourceContextPeer.convertModifiersToDropAction(modifiers,
 262                                                                   sourceActions);
 263 
 264         DragSourceDragEvent event =
 265             new DragSourceDragEvent(getDragSourceContext(),
 266                                     dropAction,
 267                                     targetAction & sourceActions,
 268                                     modifiers, x, y);
 269         EventDispatcher dispatcher = new EventDispatcher(dispatchType, event);
 270 
 271         SunToolkit.invokeLaterOnAppContext(
 272             SunToolkit.targetToAppContext(getComponent()), dispatcher);
 273 
 274         startSecondaryEventLoop();
 275     }
 276 
 277     /**
 278      * upcall from native code
 279      */
 280 
 281     protected void dragEnter(final int targetActions,
 282                            final int modifiers,
 283                            final int x, final int y) {
 284         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_ENTER);
 285     }
 286 
 287     /**
 288      * upcall from native code
 289      */
 290 
 291     private void dragMotion(final int targetActions,
 292                             final int modifiers,
 293                             final int x, final int y) {
 294         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_MOTION);
 295     }
 296 
 297     /**
 298      * upcall from native code
 299      */
 300 
 301     private void operationChanged(final int targetActions,
 302                                   final int modifiers,
 303                                   final int x, final int y) {
 304         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_CHANGED);
 305     }
 306 
 307     /**
 308      * upcall from native code
 309      */
 310 
 311     protected final void dragExit(final int x, final int y) {
 312         DragSourceEvent event =
 313             new DragSourceEvent(getDragSourceContext(), x, y);
 314         EventDispatcher dispatcher =
 315             new EventDispatcher(DISPATCH_EXIT, event);
 316 
 317         SunToolkit.invokeLaterOnAppContext(
 318             SunToolkit.targetToAppContext(getComponent()), dispatcher);
 319 
 320         startSecondaryEventLoop();
 321     }
 322 
 323     /**
 324      * upcall from native code
 325      */
 326 
 327     private void dragMouseMoved(final int targetActions,
 328                                 final int modifiers,
 329                                 final int x, final int y) {
 330         postDragSourceDragEvent(targetActions, modifiers, x, y,
 331                                 DISPATCH_MOUSE_MOVED);
 332     }
 333 
 334     /**
 335      * upcall from native code via implemented class (do)
 336      */
 337 
 338     protected final void dragDropFinished(final boolean success,
 339                                           final int operations,
 340                                           final int x, final int y) {
 341         DragSourceEvent event =
 342             new DragSourceDropEvent(getDragSourceContext(),
 343                                     operations & sourceActions,
 344                                     success, x, y);
 345         EventDispatcher dispatcher =
 346             new EventDispatcher(DISPATCH_FINISH, event);
 347 
 348         SunToolkit.invokeLaterOnAppContext(
 349             SunToolkit.targetToAppContext(getComponent()), dispatcher);
 350 
 351         startSecondaryEventLoop();
 352         setNativeContext(0);
 353         dragImage = null;
 354         dragImageOffset = null;
 355     }
 356 
 357     public static void setDragDropInProgress(boolean b)
 358       throws InvalidDnDOperationException {
 359         synchronized (SunDragSourceContextPeer.class) {
 360             if (dragDropInProgress == b) {
 361                 throw new InvalidDnDOperationException(getExceptionMessage(b));
 362             }
 363             dragDropInProgress = b;
 364         }
 365     }
 366 
 367     /**
 368      * Filters out all mouse events that were on the java event queue when
 369      * startDrag was called.
 370      */
 371     public static boolean checkEvent(AWTEvent event) {
 372         if (discardingMouseEvents && event instanceof MouseEvent) {
 373             MouseEvent mouseEvent = (MouseEvent)event;
 374             if (!(mouseEvent instanceof SunDropTargetEvent)) {
 375                 return false;
 376             }
 377         }
 378         return true;
 379     }
 380 
 381     public static void checkDragDropInProgress()
 382       throws InvalidDnDOperationException {
 383         if (dragDropInProgress) {
 384             throw new InvalidDnDOperationException(getExceptionMessage(true));
 385         }
 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 }