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