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