1 /*
   2  * Copyright (c) 2011, 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 
  27 package sun.lwawt.macosx;
  28 
  29 import java.awt.*;
  30 import java.awt.datatransfer.*;
  31 import java.awt.dnd.*;
  32 import java.awt.event.*;
  33 import java.awt.image.*;
  34 import java.awt.peer.*;
  35 
  36 import javax.swing.*;
  37 import javax.swing.text.*;
  38 import javax.accessibility.*;
  39 
  40 import java.util.Map;
  41 import sun.awt.dnd.*;
  42 import sun.lwawt.LWComponentPeer;
  43 
  44 
  45 public final class CDragSourceContextPeer extends SunDragSourceContextPeer {
  46 
  47     private static final CDragSourceContextPeer fInstance = new CDragSourceContextPeer(null);
  48 
  49     private Image  fDragImage;
  50     private CImage fDragCImage;
  51     private Point  fDragImageOffset;
  52 
  53     private static Component hoveringComponent = null;
  54 
  55     private static double fMaxImageSize = 128.0;
  56 
  57     static {
  58         String propValue = java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction("apple.awt.dnd.defaultDragImageSize"));
  59         if (propValue != null) {
  60             try {
  61                 double value = Double.parseDouble(propValue);
  62                 if (value > 0) {
  63                     fMaxImageSize = value;
  64                 }
  65             } catch(NumberFormatException e) {}
  66         }
  67     }
  68 
  69     private CDragSourceContextPeer(DragGestureEvent dge) {
  70         super(dge);
  71     }
  72 
  73     public static CDragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException {
  74         fInstance.setTrigger(dge);
  75 
  76         return fInstance;
  77     }
  78 
  79     // We have to overload this method just to be able to grab the drag image and its offset as shared code doesn't store it:
  80     public void startDrag(DragSourceContext dsc, Cursor cursor, Image dragImage, Point dragImageOffset) throws InvalidDnDOperationException {
  81         fDragImage = dragImage;
  82         fDragImageOffset = dragImageOffset;
  83 
  84         super.startDrag(dsc, cursor, dragImage, dragImageOffset);
  85     }
  86 
  87     protected void startDrag(Transferable transferable, long[] formats, Map formatMap) {
  88         DragGestureEvent trigger = getTrigger();
  89         InputEvent         triggerEvent = trigger.getTriggerEvent();
  90 
  91         Point dragOrigin = new Point(trigger.getDragOrigin());
  92         int extModifiers = (triggerEvent.getModifiers() | triggerEvent.getModifiersEx());
  93         long timestamp   = triggerEvent.getWhen();
  94         int clickCount   = ((triggerEvent instanceof MouseEvent) ? (((MouseEvent) triggerEvent).getClickCount()) : 1);
  95 
  96         Component component = trigger.getComponent();
  97         // For a lightweight component traverse up the hierarchy to the root
  98         Point loc = component.getLocation();
  99         Component rootComponent = component;
 100         while (!(rootComponent instanceof Window)) {
 101             dragOrigin.translate(loc.x, loc.y);
 102             rootComponent = rootComponent.getParent();
 103             loc = rootComponent.getLocation();
 104         }
 105 
 106         //It sure will be LWComponentPeer instance as rootComponent is a Window
 107         LWComponentPeer peer = (LWComponentPeer)rootComponent.getPeer();
 108         //Get a pointer to a native window
 109         CPlatformWindow platformWindow = (CPlatformWindow) peer.getPlatformWindow();
 110         long nativeWindowPtr = platformWindow.getNSWindowPtr();
 111 
 112         // Get drag cursor:
 113         Cursor cursor = this.getCursor();
 114 
 115         // If there isn't any drag image make one of default appearance:
 116         if (fDragImage == null)
 117             this.setDefaultDragImage(component);
 118 
 119         // Get drag image (if any) as BufferedImage and convert that to CImage:
 120         Point dragImageOffset;
 121 
 122         if (fDragImage != null) {
 123             try {
 124                 fDragCImage = CImage.getCreator().createFromImageImmediately(fDragImage);
 125             } catch(Exception e) {
 126                 // image creation may fail for any reason
 127                 throw new InvalidDnDOperationException("Drag image can not be created.");
 128             }
 129             if (fDragCImage == null) {
 130                 throw new InvalidDnDOperationException("Drag image is not ready.");
 131             }
 132 
 133             dragImageOffset = fDragImageOffset;
 134         } else {
 135 
 136             fDragCImage = null;
 137             dragImageOffset = new Point(0, 0);
 138         }
 139 
 140         try {
 141             // Create native dragging source:
 142             final long nativeDragSource = createNativeDragSource(component, peer, nativeWindowPtr, transferable, triggerEvent,
 143                 (int) (dragOrigin.getX()), (int) (dragOrigin.getY()), extModifiers,
 144                 clickCount, timestamp, cursor, fDragCImage, dragImageOffset.x, dragImageOffset.y,
 145                 getDragSourceContext().getSourceActions(), formats, formatMap);
 146 
 147             if (nativeDragSource == 0)
 148                 throw new InvalidDnDOperationException("");
 149 
 150             setNativeContext(nativeDragSource);
 151 
 152             CCursorManager.getInstance().startDrag(
 153                     (int) (dragOrigin.getX()),
 154                     (int) (dragOrigin.getY()));
 155         }
 156 
 157         catch (Exception e) {
 158             throw new InvalidDnDOperationException("failed to create native peer: " + e);
 159         }
 160 
 161         SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(transferable);
 162 
 163         // Create a new thread to run the dragging operation since it's synchronous, only coming back
 164         // after dragging is finished. This leaves the AWT event thread free to handle AWT events which
 165         // are posted during dragging by native event handlers.
 166 
 167         try {
 168             Thread dragThread = new Thread() {
 169                 public void run() {
 170                     final long nativeDragSource = getNativeContext();
 171                     try {
 172                         doDragging(nativeDragSource);
 173                     } catch (Exception e) {
 174                         e.printStackTrace();
 175                     } finally {
 176                         CCursorManager.getInstance().stopDrag();
 177 
 178                         releaseNativeDragSource(nativeDragSource);
 179                         fDragImage = null;
 180                         if (fDragCImage != null) {
 181                             fDragCImage.dispose();
 182                             fDragCImage = null;
 183                         }
 184                     }
 185                 }
 186             };
 187 
 188             dragThread.start();
 189         }
 190 
 191         catch (Exception e) {
 192             CCursorManager.getInstance().stopDrag();
 193 
 194             final long nativeDragSource = getNativeContext();
 195             setNativeContext(0);
 196             releaseNativeDragSource(nativeDragSource);
 197             SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(null);
 198             throw new InvalidDnDOperationException("failed to start dragging thread: " + e);
 199         }
 200     }
 201 
 202     private void setDefaultDragImage(Component component) {
 203         boolean handled = false;
 204 
 205         // Special-case default drag image, depending on the drag source type:
 206         if (component.isLightweight()) {
 207             if (component instanceof JTextComponent) {
 208                 this.setDefaultDragImage((JTextComponent) component);
 209                 handled = true;
 210             } else if (component instanceof JTree) {
 211                             this.setDefaultDragImage((JTree) component);
 212                             handled = true;
 213                         } else if (component instanceof JTable) {
 214                             this.setDefaultDragImage((JTable) component);
 215                             handled = true;
 216                         } else if (component instanceof JList) {
 217                             this.setDefaultDragImage((JList) component);
 218                             handled = true;
 219                         }
 220         }
 221 
 222         if (handled == false)
 223             this.setDefaultDragImage();
 224     }
 225 
 226     private void setDefaultDragImage(JTextComponent component) {
 227         DragGestureEvent trigger = getTrigger();
 228         int selectionStart = component.getSelectionStart();
 229         int selectionEnd = component.getSelectionEnd();
 230         boolean handled = false;
 231 
 232         // Make sure we're dragging current selection:
 233         int index = component.viewToModel(trigger.getDragOrigin());
 234         if ((selectionStart < selectionEnd) && (index >= selectionStart) && (index <= selectionEnd)) {
 235             try {
 236                 Rectangle selectionStartBounds = component.modelToView(selectionStart);
 237                 Rectangle selectionEndBounds = component.modelToView(selectionEnd);
 238 
 239                 Rectangle selectionBounds = null;
 240 
 241                 // Single-line selection:
 242                 if (selectionStartBounds.y == selectionEndBounds.y) {
 243                     selectionBounds = new Rectangle(selectionStartBounds.x, selectionStartBounds.y,
 244                         selectionEndBounds.x - selectionStartBounds.x + selectionEndBounds.width,
 245                         selectionEndBounds.y - selectionStartBounds.y + selectionEndBounds.height);
 246                 }
 247 
 248                 // Multi-line selection:
 249                 else {
 250                     AccessibleContext ctx = component.getAccessibleContext();
 251                     AccessibleText at = (AccessibleText) ctx;
 252 
 253                     selectionBounds = component.modelToView(selectionStart);
 254                     for (int i = selectionStart + 1; i <= selectionEnd; i++) {
 255                                             Rectangle charBounds = at.getCharacterBounds(i);
 256                                             // Invalid index returns null Rectangle
 257                                             // Note that this goes against jdk doc - should be empty, but is null instead
 258                                             if (charBounds != null) {
 259                                                 selectionBounds.add(charBounds);
 260                                             }
 261                     }
 262                 }
 263 
 264                 this.setOutlineDragImage(selectionBounds);
 265                 handled = true;
 266             }
 267 
 268             catch (BadLocationException exc) {
 269                 // Default the drag image to component bounds.
 270             }
 271         }
 272 
 273         if (handled == false)
 274             this.setDefaultDragImage();
 275     }
 276 
 277 
 278     private void setDefaultDragImage(JTree component) {
 279         Rectangle selectedOutline = null;
 280 
 281         int[] selectedRows = component.getSelectionRows();
 282         for (int i=0; i<selectedRows.length; i++) {
 283             Rectangle r = component.getRowBounds(selectedRows[i]);
 284             if (selectedOutline == null)
 285                 selectedOutline = r;
 286             else
 287                 selectedOutline.add(r);
 288         }
 289 
 290         if (selectedOutline != null) {
 291             this.setOutlineDragImage(selectedOutline);
 292         } else {
 293             this.setDefaultDragImage();
 294         }
 295     }
 296 
 297     private void setDefaultDragImage(JTable component) {
 298         Rectangle selectedOutline = null;
 299 
 300         // This code will likely break once multiple selections works (3645873)
 301         int[] selectedRows = component.getSelectedRows();
 302         int[] selectedColumns = component.getSelectedColumns();
 303         for (int row=0; row<selectedRows.length; row++) {
 304             for (int col=0; col<selectedColumns.length; col++) {
 305                 Rectangle r = component.getCellRect(selectedRows[row], selectedColumns[col], true);
 306                 if (selectedOutline == null)
 307                     selectedOutline = r;
 308                 else
 309                     selectedOutline.add(r);
 310             }
 311         }
 312 
 313         if (selectedOutline != null) {
 314             this.setOutlineDragImage(selectedOutline);
 315         } else {
 316             this.setDefaultDragImage();
 317         }
 318     }
 319 
 320     private void setDefaultDragImage(JList component) {
 321         Rectangle selectedOutline = null;
 322 
 323         // This code actually works, even under the (non-existant) multiple-selections, because we only draw a union outline
 324         int[] selectedIndices = component.getSelectedIndices();
 325         if (selectedIndices.length > 0)
 326             selectedOutline = component.getCellBounds(selectedIndices[0], selectedIndices[selectedIndices.length-1]);
 327 
 328         if (selectedOutline != null) {
 329             this.setOutlineDragImage(selectedOutline);
 330         } else {
 331             this.setDefaultDragImage();
 332         }
 333     }
 334 
 335 
 336     private void setDefaultDragImage() {
 337         DragGestureEvent trigger = this.getTrigger();
 338         Component comp = trigger.getComponent();
 339 
 340         setOutlineDragImage(new Rectangle(0, 0, comp.getWidth(), comp.getHeight()), true);
 341     }
 342 
 343     private void setOutlineDragImage(Rectangle outline) {
 344         setOutlineDragImage(outline, false);
 345     }
 346 
 347     private void setOutlineDragImage(Rectangle outline, Boolean shouldScale) {
 348         int width = (int)outline.getWidth();
 349         int height = (int)outline.getHeight();
 350 
 351         double scale = 1.0;
 352         if (shouldScale) {
 353             final int area = width * height;
 354             final int maxArea = (int)(fMaxImageSize * fMaxImageSize);
 355 
 356             if (area > maxArea) {
 357                 scale = (double)area / (double)maxArea;
 358                 width /= scale;
 359                 height /= scale;
 360             }
 361         }
 362 
 363         if (width <=0) width = 1;
 364         if (height <=0) height = 1;
 365 
 366         DragGestureEvent trigger = this.getTrigger();
 367         Component comp = trigger.getComponent();
 368         Point compOffset = comp.getLocation();
 369 
 370         // For lightweight components add some special treatment:
 371         if (comp instanceof JComponent) {
 372             // Intersect requested bounds with visible bounds:
 373             Rectangle visibleBounds = ((JComponent) comp).getVisibleRect();
 374             Rectangle clipedOutline = outline.intersection(visibleBounds);
 375             if (clipedOutline.isEmpty() == false)
 376                 outline = clipedOutline;
 377 
 378             // Compensate for the component offset (e.g. when contained in a JScrollPane):
 379             outline.translate(compOffset.x, compOffset.y);
 380         }
 381 
 382         GraphicsConfiguration config = comp.getGraphicsConfiguration();
 383         BufferedImage dragImage = config.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
 384 
 385         Color paint = Color.gray;
 386         BasicStroke stroke = new BasicStroke(2.0f);
 387         int halfLineWidth = (int) (stroke.getLineWidth() + 1) / 2; // Rounded up.
 388 
 389         Graphics2D g2 = (Graphics2D) dragImage.getGraphics();
 390         g2.setPaint(paint);
 391         g2.setStroke(stroke);
 392         g2.drawRect(halfLineWidth, halfLineWidth, width - 2 * halfLineWidth - 1, height - 2 * halfLineWidth - 1);
 393         g2.dispose();
 394 
 395         fDragImage = dragImage;
 396 
 397 
 398         Point dragOrigin = trigger.getDragOrigin();
 399         Point dragImageOffset = new Point(outline.x - dragOrigin.x, outline.y - dragOrigin.y);
 400         if (comp instanceof JComponent) {
 401             dragImageOffset.translate(-compOffset.x, -compOffset.y);
 402         }
 403 
 404         if (shouldScale) {
 405             dragImageOffset.x /= scale;
 406             dragImageOffset.y /= scale;
 407         }
 408 
 409         fDragImageOffset = dragImageOffset;
 410     }
 411 
 412     /**
 413      * upcall from native code
 414      */
 415     private void dragMouseMoved(final int targetActions,
 416                                 final int modifiers,
 417                                 final int x, final int y) {
 418 
 419         CCursorManager.getInstance().updateDragPosition(x, y);
 420 
 421         Component rootComponent = SwingUtilities.getRoot(getComponent());
 422         if(rootComponent != null) {
 423             Point componentPoint = new Point(x, y);
 424             SwingUtilities.convertPointFromScreen(componentPoint, rootComponent);
 425             Component componentAt = SwingUtilities.getDeepestComponentAt(rootComponent, componentPoint.x, componentPoint.y);
 426             if(componentAt != hoveringComponent) {
 427                 if(hoveringComponent != null) {
 428                     dragExit(x, y);
 429                 }
 430                 if(componentAt != null) {
 431                     dragEnter(targetActions, modifiers, x, y);
 432                 }
 433                 hoveringComponent = componentAt;
 434             }
 435         }
 436         postDragSourceDragEvent(targetActions, modifiers, x, y,
 437                                 DISPATCH_MOUSE_MOVED);
 438     }
 439 
 440     /**
 441      * upcall from native code
 442      */
 443     private void dragEnter(final int targetActions,
 444                            final int modifiers,
 445                            final int x, final int y) {
 446         CCursorManager.getInstance().updateDragPosition(x, y);
 447 
 448         postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_ENTER);
 449     }
 450 
 451     /**
 452      * upcall from native code - reset hovering component
 453      */
 454     private void resetHovering() {
 455         hoveringComponent = null;
 456     }
 457 
 458     public void setCursor(Cursor c) throws InvalidDnDOperationException {
 459         // TODO : BG
 460         //AWTLockAccess.awtLock();
 461         super.setCursor(c);
 462         //AWTLockAccess.awtUnlock();
 463     }
 464 
 465     protected native void setNativeCursor(long nativeCtxt, Cursor c, int cType);
 466 
 467     // Native support:
 468     private native long createNativeDragSource(Component component, ComponentPeer peer, long nativePeer, Transferable transferable,
 469         InputEvent triggerEvent, int dragPosX, int dragPosY, int extModifiers, int clickCount, long timestamp,
 470         Cursor cursor, CImage nsDragImage, int dragImageOffsetX, int dragImageOffsetY,
 471         int sourceActions, long[] formats, Map formatMap);
 472 
 473     private native void doDragging(long nativeDragSource);
 474 
 475     private native void releaseNativeDragSource(long nativeDragSource);
 476 }