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