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 sun.awt.dnd.*; 42 import sun.lwawt.LWComponentPeer; 43 44 45 public final class CDragSourceContextPeer extends SunDragSourceContextPeer { 46 47 private static final CDragSourceContextPeer fInstance = new CDragSourceContextPeer(null); 48 49 private Image fDragImage; 50 private CImage fDragCImage; 51 private Point fDragImageOffset; 52 53 private static Component hoveringComponent = null; 54 55 private static double fMaxImageSize = 128.0; 56 57 static { 58 String propValue = java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction("apple.awt.dnd.defaultDragImageSize")); 59 if (propValue != null) { 60 try { 61 double value = Double.parseDouble(propValue); 62 if (value > 0) { 63 fMaxImageSize = value; 64 } 65 } catch(NumberFormatException e) {} 66 } 67 } 68 69 private CDragSourceContextPeer(DragGestureEvent dge) { 70 super(dge); 71 } 72 73 public static CDragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException { 74 fInstance.setTrigger(dge); 75 76 return fInstance; 77 } 78 79 // 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: 80 public void startDrag(DragSourceContext dsc, Cursor cursor, Image dragImage, Point dragImageOffset) throws InvalidDnDOperationException { 81 fDragImage = dragImage; 82 fDragImageOffset = dragImageOffset; 83 84 super.startDrag(dsc, cursor, dragImage, dragImageOffset); 85 } 86 87 protected void startDrag(Transferable transferable, long[] formats, Map formatMap) { 88 DragGestureEvent trigger = getTrigger(); 89 InputEvent triggerEvent = trigger.getTriggerEvent(); 90 91 Point dragOrigin = trigger.getDragOrigin(); 92 int extModifiers = (triggerEvent.getModifiers() | triggerEvent.getModifiersEx()); 93 long timestamp = triggerEvent.getWhen(); 94 int clickCount = ((triggerEvent instanceof MouseEvent) ? (((MouseEvent) triggerEvent).getClickCount()) : 1); 95 96 // Get drag source component and its peer: 97 Component component = trigger.getComponent(); 98 Point componentOffset = new Point(); 99 ComponentPeer peer = component.getPeer(); 100 101 102 // For a component traverse up the hierarchy to the root 103 Point loc = new Point(); 104 for (Component loopComponent = component; loopComponent != null; loopComponent = loopComponent.getParent()) { 105 peer = loopComponent.getPeer(); 106 componentOffset.translate(loc.x, loc.y); 107 loc = loopComponent.getLocation(); 108 } 109 110 //Get a pointer to a native window 111 LWComponentPeer model = (LWComponentPeer) peer; 112 CPlatformWindow platformWindow = (CPlatformWindow) model.getPlatformWindow(); 113 long nativeWindowPtr = platformWindow.getNSWindowPtr(); 114 115 // Get drag cursor: 116 Cursor cursor = this.getCursor(); 117 118 // If there isn't any drag image make one of default appearance: 119 if (fDragImage == null) 120 this.setDefaultDragImage(component); 121 122 // Get drag image (if any) as BufferedImage and convert that to CImage: 123 Point dragImageOffset; 124 125 if (fDragImage != null) { 126 try { 127 fDragCImage = CImage.getCreator().createFromImageImmediately(fDragImage); 128 } catch(Exception e) { 129 // image creation may fail for any reason 130 throw new InvalidDnDOperationException("Drag image can not be created."); 131 } 132 if (fDragCImage == null) { 133 throw new InvalidDnDOperationException("Drag image is not ready."); 134 } 135 136 dragImageOffset = fDragImageOffset; 137 } else { 138 139 fDragCImage = null; 140 dragImageOffset = new Point(0, 0); 141 } 142 143 try { 144 // Create native dragging source: 145 final long nativeDragSource = createNativeDragSource(component, peer, nativeWindowPtr, transferable, triggerEvent, 146 (int) (dragOrigin.getX() + componentOffset.x), (int) (dragOrigin.getY() + componentOffset.y), extModifiers, 147 clickCount, timestamp, cursor, fDragCImage, dragImageOffset.x, dragImageOffset.y, 148 getDragSourceContext().getSourceActions(), formats, formatMap); 149 150 if (nativeDragSource == 0) 151 throw new InvalidDnDOperationException(""); 152 153 setNativeContext(nativeDragSource); 154 155 CCursorManager.getInstance().startDrag( 156 (int) (dragOrigin.getX() + componentOffset.x), 157 (int) (dragOrigin.getY() + componentOffset.y)); 158 } 159 160 catch (Exception e) { 161 throw new InvalidDnDOperationException("failed to create native peer: " + e); 162 } 163 164 SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(transferable); 165 166 // Create a new thread to run the dragging operation since it's synchronous, only coming back 167 // after dragging is finished. This leaves the AWT event thread free to handle AWT events which 168 // are posted during dragging by native event handlers. 169 170 try { 171 Thread dragThread = new Thread() { 172 public void run() { 173 final long nativeDragSource = getNativeContext(); 174 try { 175 doDragging(nativeDragSource); 176 } catch (Exception e) { 177 e.printStackTrace(); 178 } finally { 179 CCursorManager.getInstance().stopDrag(); 180 181 releaseNativeDragSource(nativeDragSource); 182 fDragImage = null; 183 if (fDragCImage != null) { 184 fDragCImage.dispose(); 185 fDragCImage = null; 186 } 187 } 188 } 189 }; 190 191 dragThread.start(); 192 } 193 194 catch (Exception e) { 195 CCursorManager.getInstance().stopDrag(); 196 197 final long nativeDragSource = getNativeContext(); 198 setNativeContext(0); 199 releaseNativeDragSource(nativeDragSource); 200 SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(null); 201 throw new InvalidDnDOperationException("failed to start dragging thread: " + e); 202 } 203 } 204 205 private void setDefaultDragImage(Component component) { 206 boolean handled = false; 207 208 // Special-case default drag image, depending on the drag source type: 209 if (component.isLightweight()) { 210 if (component instanceof JTextComponent) { 211 this.setDefaultDragImage((JTextComponent) component); 212 handled = true; 213 } else if (component instanceof JTree) { 214 this.setDefaultDragImage((JTree) component); 215 handled = true; 216 } else if (component instanceof JTable) { 217 this.setDefaultDragImage((JTable) component); 218 handled = true; 219 } else if (component instanceof JList) { 220 this.setDefaultDragImage((JList) component); 221 handled = true; 222 } 223 } 224 225 if (handled == false) 226 this.setDefaultDragImage(); 227 } 228 229 private void setDefaultDragImage(JTextComponent component) { 230 DragGestureEvent trigger = getTrigger(); 231 int selectionStart = component.getSelectionStart(); 232 int selectionEnd = component.getSelectionEnd(); 233 boolean handled = false; 234 235 // Make sure we're dragging current selection: 236 int index = component.viewToModel(trigger.getDragOrigin()); 237 if ((selectionStart < selectionEnd) && (index >= selectionStart) && (index <= selectionEnd)) { 238 try { 239 Rectangle selectionStartBounds = component.modelToView(selectionStart); 240 Rectangle selectionEndBounds = component.modelToView(selectionEnd); 241 242 Rectangle selectionBounds = null; 243 244 // Single-line selection: 245 if (selectionStartBounds.y == selectionEndBounds.y) { 246 selectionBounds = new Rectangle(selectionStartBounds.x, selectionStartBounds.y, 247 selectionEndBounds.x - selectionStartBounds.x + selectionEndBounds.width, 248 selectionEndBounds.y - selectionStartBounds.y + selectionEndBounds.height); 249 } 250 251 // Multi-line selection: 252 else { 253 AccessibleContext ctx = component.getAccessibleContext(); 254 AccessibleText at = (AccessibleText) ctx; 255 256 selectionBounds = component.modelToView(selectionStart); 257 for (int i = selectionStart + 1; i <= selectionEnd; i++) { 258 Rectangle charBounds = at.getCharacterBounds(i); 259 // Invalid index returns null Rectangle 260 // Note that this goes against jdk doc - should be empty, but is null instead 261 if (charBounds != null) { 262 selectionBounds.add(charBounds); 263 } 264 } 265 } 266 267 this.setOutlineDragImage(selectionBounds); 268 handled = true; 269 } 270 271 catch (BadLocationException exc) { 272 // Default the drag image to component bounds. 273 } 274 } 275 276 if (handled == false) 277 this.setDefaultDragImage(); 278 } 279 280 281 private void setDefaultDragImage(JTree component) { 282 Rectangle selectedOutline = null; 283 284 int[] selectedRows = component.getSelectionRows(); 285 for (int i=0; i<selectedRows.length; i++) { 286 Rectangle r = component.getRowBounds(selectedRows[i]); 287 if (selectedOutline == null) 288 selectedOutline = r; 289 else 290 selectedOutline.add(r); 291 } 292 293 if (selectedOutline != null) { 294 this.setOutlineDragImage(selectedOutline); 295 } else { 296 this.setDefaultDragImage(); 297 } 298 } 299 300 private void setDefaultDragImage(JTable component) { 301 Rectangle selectedOutline = null; 302 303 // This code will likely break once multiple selections works (3645873) 304 int[] selectedRows = component.getSelectedRows(); 305 int[] selectedColumns = component.getSelectedColumns(); 306 for (int row=0; row<selectedRows.length; row++) { 307 for (int col=0; col<selectedColumns.length; col++) { 308 Rectangle r = component.getCellRect(selectedRows[row], selectedColumns[col], true); 309 if (selectedOutline == null) 310 selectedOutline = r; 311 else 312 selectedOutline.add(r); 313 } 314 } 315 316 if (selectedOutline != null) { 317 this.setOutlineDragImage(selectedOutline); 318 } else { 319 this.setDefaultDragImage(); 320 } 321 } 322 323 private void setDefaultDragImage(JList component) { 324 Rectangle selectedOutline = null; 325 326 // This code actually works, even under the (non-existant) multiple-selections, because we only draw a union outline 327 int[] selectedIndices = component.getSelectedIndices(); 328 if (selectedIndices.length > 0) 329 selectedOutline = component.getCellBounds(selectedIndices[0], selectedIndices[selectedIndices.length-1]); 330 331 if (selectedOutline != null) { 332 this.setOutlineDragImage(selectedOutline); 333 } else { 334 this.setDefaultDragImage(); 335 } 336 } 337 338 339 private void setDefaultDragImage() { 340 DragGestureEvent trigger = this.getTrigger(); 341 Component comp = trigger.getComponent(); 342 343 setOutlineDragImage(new Rectangle(0, 0, comp.getWidth(), comp.getHeight()), true); 344 } 345 346 private void setOutlineDragImage(Rectangle outline) { 347 setOutlineDragImage(outline, false); 348 } 349 350 private void setOutlineDragImage(Rectangle outline, Boolean shouldScale) { 351 int width = (int)outline.getWidth(); 352 int height = (int)outline.getHeight(); 353 354 double scale = 1.0; 355 if (shouldScale) { 356 final int area = width * height; 357 final int maxArea = (int)(fMaxImageSize * fMaxImageSize); 358 359 if (area > maxArea) { 360 scale = (double)area / (double)maxArea; 361 width /= scale; 362 height /= scale; 363 } 364 } 365 366 if (width <=0) width = 1; 367 if (height <=0) height = 1; 368 369 DragGestureEvent trigger = this.getTrigger(); 370 Component comp = trigger.getComponent(); 371 Point compOffset = comp.getLocation(); 372 373 // For lightweight components add some special treatment: 374 if (comp instanceof JComponent) { 375 // Intersect requested bounds with visible bounds: 376 Rectangle visibleBounds = ((JComponent) comp).getVisibleRect(); 377 Rectangle clipedOutline = outline.intersection(visibleBounds); 378 if (clipedOutline.isEmpty() == false) 379 outline = clipedOutline; 380 381 // Compensate for the component offset (e.g. when contained in a JScrollPane): 382 outline.translate(compOffset.x, compOffset.y); 383 } 384 385 GraphicsConfiguration config = comp.getGraphicsConfiguration(); 386 BufferedImage dragImage = config.createCompatibleImage(width, height, Transparency.TRANSLUCENT); 387 388 Color paint = Color.gray; 389 BasicStroke stroke = new BasicStroke(2.0f); 390 int halfLineWidth = (int) (stroke.getLineWidth() + 1) / 2; // Rounded up. 391 392 Graphics2D g2 = (Graphics2D) dragImage.getGraphics(); 393 g2.setPaint(paint); 394 g2.setStroke(stroke); 395 g2.drawRect(halfLineWidth, halfLineWidth, width - 2 * halfLineWidth - 1, height - 2 * halfLineWidth - 1); 396 g2.dispose(); 397 398 fDragImage = dragImage; 399 400 401 Point dragOrigin = trigger.getDragOrigin(); 402 Point dragImageOffset = new Point(outline.x - dragOrigin.x, outline.y - dragOrigin.y); 403 if (comp instanceof JComponent) { 404 dragImageOffset.translate(-compOffset.x, -compOffset.y); 405 } 406 407 if (shouldScale) { 408 dragImageOffset.x /= scale; 409 dragImageOffset.y /= scale; 410 } 411 412 fDragImageOffset = dragImageOffset; 413 } 414 415 /** 416 * upcall from native code 417 */ 418 private void dragMouseMoved(final int targetActions, 419 final int modifiers, 420 final int x, final int y) { 421 422 CCursorManager.getInstance().updateDragPosition(x, y); 423 424 Component rootComponent = SwingUtilities.getRoot(getComponent()); 425 if(rootComponent != null) { 426 Point componentPoint = new Point(x, y); 427 SwingUtilities.convertPointFromScreen(componentPoint, rootComponent); 428 Component componentAt = SwingUtilities.getDeepestComponentAt(rootComponent, componentPoint.x, componentPoint.y); 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 } 442 443 /** 444 * upcall from native code 445 */ 446 private void dragEnter(final int targetActions, 447 final int modifiers, 448 final int x, final int y) { 449 CCursorManager.getInstance().updateDragPosition(x, y); 450 451 postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_ENTER); 452 } 453 454 /** 455 * upcall from native code - reset hovering component 456 */ 457 private void resetHovering() { 458 hoveringComponent = null; 459 } 460 461 public void setCursor(Cursor c) throws InvalidDnDOperationException { 462 // TODO : BG 463 //AWTLockAccess.awtLock(); 464 super.setCursor(c); 465 //AWTLockAccess.awtUnlock(); 466 } 467 468 protected native void setNativeCursor(long nativeCtxt, Cursor c, int cType); 469 470 // Native support: 471 private native long createNativeDragSource(Component component, ComponentPeer peer, long nativePeer, Transferable transferable, 472 InputEvent triggerEvent, int dragPosX, int dragPosY, int extModifiers, int clickCount, long timestamp, 473 Cursor cursor, CImage nsDragImage, int dragImageOffsetX, int dragImageOffsetY, 474 int sourceActions, long[] formats, Map formatMap); 475 476 private native void doDragging(long nativeDragSource); 477 478 private native void releaseNativeDragSource(long nativeDragSource); 479 }