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 import sun.misc.ManagedLocalsThread; 48 49 50 public final class CDragSourceContextPeer extends SunDragSourceContextPeer { 51 52 private static final CDragSourceContextPeer fInstance = new CDragSourceContextPeer(null); 53 54 private Image fDragImage; 55 private CImage fDragCImage; 56 private Point fDragImageOffset; 57 58 private static Component hoveringComponent = null; 59 60 private static double fMaxImageSize = 128.0; 61 62 static { 63 String propValue = java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction("apple.awt.dnd.defaultDragImageSize")); 64 if (propValue != null) { 65 try { 66 double value = Double.parseDouble(propValue); 67 if (value > 0) { 68 fMaxImageSize = value; 69 } 70 } catch(NumberFormatException e) {} 71 } 72 } 73 74 private CDragSourceContextPeer(DragGestureEvent dge) { 75 super(dge); 76 } 77 78 public static CDragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException { 79 fInstance.setTrigger(dge); 80 81 return fInstance; 82 } 83 84 // 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: 85 public void startDrag(DragSourceContext dsc, Cursor cursor, Image dragImage, Point dragImageOffset) throws InvalidDnDOperationException { 86 fDragImage = dragImage; 87 fDragImageOffset = dragImageOffset; 88 89 super.startDrag(dsc, cursor, dragImage, dragImageOffset); 90 } 91 92 protected void startDrag(Transferable transferable, long[] formats, Map<Long, DataFlavor> formatMap) { 93 DragGestureEvent trigger = getTrigger(); 94 InputEvent triggerEvent = trigger.getTriggerEvent(); 95 96 Point dragOrigin = new Point(trigger.getDragOrigin()); 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 ManagedLocalsThread(dragRunnable).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 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 private void setDefaultDragImage(JTextComponent component) { 219 DragGestureEvent trigger = getTrigger(); 220 int selectionStart = component.getSelectionStart(); 221 int selectionEnd = component.getSelectionEnd(); 222 boolean handled = false; 223 224 // Make sure we're dragging current selection: 225 int index = component.viewToModel(trigger.getDragOrigin()); 226 if ((selectionStart < selectionEnd) && (index >= selectionStart) && (index <= selectionEnd)) { 227 try { 228 Rectangle selectionStartBounds = component.modelToView(selectionStart); 229 Rectangle selectionEndBounds = component.modelToView(selectionEnd); 230 231 Rectangle selectionBounds = null; 232 233 // Single-line selection: 234 if (selectionStartBounds.y == selectionEndBounds.y) { 235 selectionBounds = new Rectangle(selectionStartBounds.x, selectionStartBounds.y, 236 selectionEndBounds.x - selectionStartBounds.x + selectionEndBounds.width, 237 selectionEndBounds.y - selectionStartBounds.y + selectionEndBounds.height); 238 } 239 240 // Multi-line selection: 241 else { 242 AccessibleContext ctx = component.getAccessibleContext(); 243 AccessibleText at = (AccessibleText) ctx; 244 245 selectionBounds = component.modelToView(selectionStart); 246 for (int i = selectionStart + 1; i <= selectionEnd; i++) { 247 Rectangle charBounds = at.getCharacterBounds(i); 248 // Invalid index returns null Rectangle 249 // Note that this goes against jdk doc - should be empty, but is null instead 250 if (charBounds != null) { 251 selectionBounds.add(charBounds); 252 } 253 } 254 } 255 256 this.setOutlineDragImage(selectionBounds); 257 handled = true; 258 } 259 260 catch (BadLocationException exc) { 261 // Default the drag image to component bounds. 262 } 263 } 264 265 if (handled == false) 266 this.setDefaultDragImage(); 267 } 268 269 270 private void setDefaultDragImage(JTree component) { 271 Rectangle selectedOutline = null; 272 273 int[] selectedRows = component.getSelectionRows(); 274 for (int i=0; i<selectedRows.length; i++) { 275 Rectangle r = component.getRowBounds(selectedRows[i]); 276 if (selectedOutline == null) 277 selectedOutline = r; 278 else 279 selectedOutline.add(r); 280 } 281 282 if (selectedOutline != null) { 283 this.setOutlineDragImage(selectedOutline); 284 } else { 285 this.setDefaultDragImage(); 286 } 287 } 288 289 private void setDefaultDragImage(JTable component) { 290 Rectangle selectedOutline = null; 291 292 // This code will likely break once multiple selections works (3645873) 293 int[] selectedRows = component.getSelectedRows(); 294 int[] selectedColumns = component.getSelectedColumns(); 295 for (int row=0; row<selectedRows.length; row++) { 296 for (int col=0; col<selectedColumns.length; col++) { 297 Rectangle r = component.getCellRect(selectedRows[row], selectedColumns[col], true); 298 if (selectedOutline == null) 299 selectedOutline = r; 300 else 301 selectedOutline.add(r); 302 } 303 } 304 305 if (selectedOutline != null) { 306 this.setOutlineDragImage(selectedOutline); 307 } else { 308 this.setDefaultDragImage(); 309 } 310 } 311 312 private void setDefaultDragImage(JList<?> component) { 313 Rectangle selectedOutline = null; 314 315 // This code actually works, even under the (non-existant) multiple-selections, because we only draw a union outline 316 int[] selectedIndices = component.getSelectedIndices(); 317 if (selectedIndices.length > 0) 318 selectedOutline = component.getCellBounds(selectedIndices[0], selectedIndices[selectedIndices.length-1]); 319 320 if (selectedOutline != null) { 321 this.setOutlineDragImage(selectedOutline); 322 } else { 323 this.setDefaultDragImage(); 324 } 325 } 326 327 328 private void setDefaultDragImage() { 329 DragGestureEvent trigger = this.getTrigger(); 330 Component comp = trigger.getComponent(); 331 332 setOutlineDragImage(new Rectangle(0, 0, comp.getWidth(), comp.getHeight()), true); 333 } 334 335 private void setOutlineDragImage(Rectangle outline) { 336 setOutlineDragImage(outline, false); 337 } 338 339 private void setOutlineDragImage(Rectangle outline, Boolean shouldScale) { 340 int width = (int)outline.getWidth(); 341 int height = (int)outline.getHeight(); 342 343 double scale = 1.0; 344 if (shouldScale) { 345 final int area = width * height; 346 final int maxArea = (int)(fMaxImageSize * fMaxImageSize); 347 348 if (area > maxArea) { 349 scale = (double)area / (double)maxArea; 350 width /= scale; 351 height /= scale; 352 } 353 } 354 355 if (width <=0) width = 1; 356 if (height <=0) height = 1; 357 358 DragGestureEvent trigger = this.getTrigger(); 359 Component comp = trigger.getComponent(); 360 Point compOffset = comp.getLocation(); 361 362 // For lightweight components add some special treatment: 363 if (comp instanceof JComponent) { 364 // Intersect requested bounds with visible bounds: 365 Rectangle visibleBounds = ((JComponent) comp).getVisibleRect(); 366 Rectangle clipedOutline = outline.intersection(visibleBounds); 367 if (clipedOutline.isEmpty() == false) 368 outline = clipedOutline; 369 370 // Compensate for the component offset (e.g. when contained in a JScrollPane): 371 outline.translate(compOffset.x, compOffset.y); 372 } 373 374 GraphicsConfiguration config = comp.getGraphicsConfiguration(); 375 BufferedImage dragImage = config.createCompatibleImage(width, height, Transparency.TRANSLUCENT); 376 377 Color paint = Color.gray; 378 BasicStroke stroke = new BasicStroke(2.0f); 379 int halfLineWidth = (int) (stroke.getLineWidth() + 1) / 2; // Rounded up. 380 381 Graphics2D g2 = (Graphics2D) dragImage.getGraphics(); 382 g2.setPaint(paint); 383 g2.setStroke(stroke); 384 g2.drawRect(halfLineWidth, halfLineWidth, width - 2 * halfLineWidth - 1, height - 2 * halfLineWidth - 1); 385 g2.dispose(); 386 387 fDragImage = dragImage; 388 389 390 Point dragOrigin = trigger.getDragOrigin(); 391 Point dragImageOffset = new Point(outline.x - dragOrigin.x, outline.y - dragOrigin.y); 392 if (comp instanceof JComponent) { 393 dragImageOffset.translate(-compOffset.x, -compOffset.y); 394 } 395 396 if (shouldScale) { 397 dragImageOffset.x /= scale; 398 dragImageOffset.y /= scale; 399 } 400 401 fDragImageOffset = dragImageOffset; 402 } 403 404 /** 405 * upcall from native code 406 */ 407 private void dragMouseMoved(final int targetActions, 408 final int modifiers, 409 final int x, final int y) { 410 411 try { 412 Component componentAt = LWCToolkit.invokeAndWait( 413 new Callable<Component>() { 414 @Override 415 public Component call() { 416 LWWindowPeer mouseEventComponent = LWWindowPeer.getWindowUnderCursor(); 417 if (mouseEventComponent == null) { 418 return null; 419 } 420 Component root = SwingUtilities.getRoot(mouseEventComponent.getTarget()); 421 if (root == null) { 422 return null; 423 } 424 Point rootLocation = root.getLocationOnScreen(); 425 return getDropTargetAt(root, x - rootLocation.x, y - rootLocation.y); 426 } 427 }, getComponent()); 428 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 } catch (Exception e) { 442 throw new InvalidDnDOperationException("Failed to handle DragMouseMoved event"); 443 } 444 } 445 446 //Returns the first lightweight or heavyweight Component which has a dropTarget ready to accept the drag 447 //Should be called from the EventDispatchThread 448 private static Component getDropTargetAt(Component root, int x, int y) { 449 if (!root.contains(x, y) || !root.isEnabled() || !root.isVisible()) { 450 return null; 451 } 452 453 if (root.getDropTarget() != null && root.getDropTarget().isActive()) { 454 return root; 455 } 456 457 if (root instanceof Container) { 458 for (Component comp : ((Container) root).getComponents()) { 459 Point loc = comp.getLocation(); 460 Component dropTarget = getDropTargetAt(comp, x - loc.x, y - loc.y); 461 if (dropTarget != null) { 462 return dropTarget; 463 } 464 } 465 } 466 467 return null; 468 } 469 470 /** 471 * upcall from native code - reset hovering component 472 */ 473 private void resetHovering() { 474 hoveringComponent = null; 475 } 476 477 @Override 478 protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) { 479 CCursorManager.getInstance().setCursor(c); 480 } 481 482 // Native support: 483 private native long createNativeDragSource(Component component, long nativePeer, Transferable transferable, 484 InputEvent triggerEvent, int dragPosX, int dragPosY, int extModifiers, int clickCount, long timestamp, 485 long nsDragImagePtr, int dragImageOffsetX, int dragImageOffsetY, 486 int sourceActions, long[] formats, Map<Long, DataFlavor> formatMap); 487 488 private native void doDragging(long nativeDragSource); 489 490 private native void releaseNativeDragSource(long nativeDragSource); 491 }