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