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 long nativeViewPtr = ((LWComponentPeer)rootComponent.getPeer()).getPlatformWindow().getViewPtr(); 112 if (nativeViewPtr == 0) { 113 throw new InvalidDnDOperationException("Unsupported PlatformWindow implementation"); 114 } 115 116 // If there isn't any drag image make one of default appearance: 117 if (fDragImage == null) 118 this.setDefaultDragImage(component); 119 120 // Get drag image (if any) as BufferedImage and convert that to CImage: 121 Point dragImageOffset; 122 123 if (fDragImage != null) { 124 try { 125 fDragCImage = CImage.getCreator().createFromImageImmediately(fDragImage); 126 } catch(Exception e) { 127 // image creation may fail for any reason 128 throw new InvalidDnDOperationException("Drag image can not be created."); 129 } 130 if (fDragCImage == null) { 131 throw new InvalidDnDOperationException("Drag image is not ready."); 132 } 133 134 dragImageOffset = fDragImageOffset; 135 } else { 136 137 fDragCImage = null; 138 dragImageOffset = new Point(0, 0); 139 } 140 141 try { 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, 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 Point rootLocation = root.getLocationOnScreen(); 425 return getDropTargetAt(root, x - rootLocation.x, y - rootLocation.y); 426 } 427 }, getComponent()); 428 429 if(componentAt != hoveringComponent) { 430 if(hoveringComponent != null) { 431 dragExit(x, y); 432 } 433 if(componentAt != null) { 434 dragEnter(targetActions, modifiers, x, y); 435 } 436 hoveringComponent = componentAt; 437 } 438 439 postDragSourceDragEvent(targetActions, modifiers, x, y, 440 DISPATCH_MOUSE_MOVED); 441 } catch (Exception e) { 442 throw new InvalidDnDOperationException("Failed to handle DragMouseMoved event"); 443 } 444 } 445 446 //Returns the first Component which has a dropTarget associated with it 447 //Should be called from the EventDispatchThread 448 private static Component getDropTargetAt(Component root, int x, int y) { 449 if (!(root.contains(x, y) && root.isVisible() && root.isEnabled())) { 450 return null; 451 } 452 453 if (root.getDropTarget() != null) { 454 return root; 455 } 456 457 if (root instanceof Container) { 458 for (Component comp : ((Container) root).getComponents()) { 459 Point loc = comp.getLocation(); 460 Component dropTarget = getDropTargetAt(comp, x - loc.x, y - loc.y); 461 if (dropTarget != null) { 462 return dropTarget; 463 } 464 } 465 } 466 467 return null; 468 } 469 470 /** 471 * upcall from native code - reset hovering component 472 */ 473 private void resetHovering() { 474 hoveringComponent = null; 475 } 476 477 @Override 478 protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) { 479 CCursorManager.getInstance().setCursor(c); 480 } 481 482 // Native support: 483 private native long createNativeDragSource(Component component, long nativePeer, Transferable transferable, 484 InputEvent triggerEvent, int dragPosX, int dragPosY, int extModifiers, int clickCount, long timestamp, 485 CImage nsDragImage, int dragImageOffsetX, int dragImageOffsetY, 486 int sourceActions, long[] formats, Map formatMap); 487 488 private native void doDragging(long nativeDragSource); 489 490 private native void releaseNativeDragSource(long nativeDragSource); 491 }