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