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