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