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