/* * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.lwawt.macosx; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; import javax.swing.text.*; import javax.accessibility.*; import java.util.Map; import java.util.concurrent.Callable; import sun.awt.AWTAccessor; import sun.awt.dnd.*; import sun.lwawt.LWComponentPeer; import sun.lwawt.LWWindowPeer; import sun.lwawt.PlatformWindow; public final class CDragSourceContextPeer extends SunDragSourceContextPeer { private static final CDragSourceContextPeer fInstance = new CDragSourceContextPeer(null); private Image fDragImage; private CImage fDragCImage; private Point fDragImageOffset; private static Component hoveringComponent = null; private static double fMaxImageSize = 128.0; static { String propValue = java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction("apple.awt.dnd.defaultDragImageSize")); if (propValue != null) { try { double value = Double.parseDouble(propValue); if (value > 0) { fMaxImageSize = value; } } catch(NumberFormatException e) {} } } private CDragSourceContextPeer(DragGestureEvent dge) { super(dge); } public static CDragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException { fInstance.setTrigger(dge); return fInstance; } // 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: public void startDrag(DragSourceContext dsc, Cursor cursor, Image dragImage, Point dragImageOffset) throws InvalidDnDOperationException { fDragImage = dragImage; fDragImageOffset = dragImageOffset; super.startDrag(dsc, cursor, dragImage, dragImageOffset); } protected void startDrag(Transferable transferable, long[] formats, Map formatMap) { DragGestureEvent trigger = getTrigger(); InputEvent triggerEvent = trigger.getTriggerEvent(); Point dragOrigin = new Point(trigger.getDragOrigin()); @SuppressWarnings("deprecation") int extModifiers = (triggerEvent.getModifiers() | triggerEvent.getModifiersEx()); long timestamp = triggerEvent.getWhen(); int clickCount = ((triggerEvent instanceof MouseEvent) ? (((MouseEvent) triggerEvent).getClickCount()) : 1); Component component = trigger.getComponent(); // For a lightweight component traverse up the hierarchy to the root Point loc = component.getLocation(); Component rootComponent = component; while (!(rootComponent instanceof Window)) { dragOrigin.translate(loc.x, loc.y); rootComponent = rootComponent.getParent(); loc = rootComponent.getLocation(); } // If there isn't any drag image make one of default appearance: if (fDragImage == null) this.setDefaultDragImage(component); // Get drag image (if any) as BufferedImage and convert that to CImage: Point dragImageOffset; if (fDragImage != null) { try { fDragCImage = CImage.getCreator().createFromImageImmediately(fDragImage); } catch(Exception e) { // image creation may fail for any reason throw new InvalidDnDOperationException("Drag image can not be created."); } if (fDragCImage == null) { throw new InvalidDnDOperationException("Drag image is not ready."); } dragImageOffset = fDragImageOffset; } else { fDragCImage = null; dragImageOffset = new Point(0, 0); } try { //It sure will be LWComponentPeer instance as rootComponent is a Window LWComponentPeer peer = AWTAccessor.getComponentAccessor() .getPeer(rootComponent); PlatformWindow platformWindow = peer.getPlatformWindow(); long nativeViewPtr = CPlatformWindow.getNativeViewPtr(platformWindow); if (nativeViewPtr == 0L) throw new InvalidDnDOperationException("Unsupported platform window implementation"); // Create native dragging source: final long nativeDragSource = createNativeDragSource(component, nativeViewPtr, transferable, triggerEvent, (int) (dragOrigin.getX()), (int) (dragOrigin.getY()), extModifiers, clickCount, timestamp, fDragCImage != null ? fDragCImage.ptr : 0L, dragImageOffset.x, dragImageOffset.y, getDragSourceContext().getSourceActions(), formats, formatMap); if (nativeDragSource == 0) throw new InvalidDnDOperationException(""); setNativeContext(nativeDragSource); } catch (Exception e) { throw new InvalidDnDOperationException("failed to create native peer: " + e); } SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(transferable); CCursorManager.getInstance().setCursor(getCursor()); // Create a new thread to run the dragging operation since it's synchronous, only coming back // after dragging is finished. This leaves the AWT event thread free to handle AWT events which // are posted during dragging by native event handlers. try { Runnable dragRunnable = () -> { final long nativeDragSource = getNativeContext(); try { doDragging(nativeDragSource); } catch (Exception e) { e.printStackTrace(); } finally { releaseNativeDragSource(nativeDragSource); fDragImage = null; if (fDragCImage != null) { fDragCImage.dispose(); fDragCImage = null; } } }; new Thread(null, dragRunnable, "Drag", 0, false).start(); } catch (Exception e) { final long nativeDragSource = getNativeContext(); setNativeContext(0); releaseNativeDragSource(nativeDragSource); SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(null); throw new InvalidDnDOperationException("failed to start dragging thread: " + e); } } private void setDefaultDragImage(Component component) { boolean handled = false; // Special-case default drag image, depending on the drag source type: if (component.isLightweight()) { if (component instanceof JTextComponent) { this.setDefaultDragImage((JTextComponent) component); handled = true; } else if (component instanceof JTree) { this.setDefaultDragImage((JTree) component); handled = true; } else if (component instanceof JTable) { this.setDefaultDragImage((JTable) component); handled = true; } else if (component instanceof JList) { this.setDefaultDragImage((JList) component); handled = true; } } if (handled == false) this.setDefaultDragImage(); } @SuppressWarnings("deprecation") private void setDefaultDragImage(JTextComponent component) { DragGestureEvent trigger = getTrigger(); int selectionStart = component.getSelectionStart(); int selectionEnd = component.getSelectionEnd(); boolean handled = false; // Make sure we're dragging current selection: int index = component.viewToModel(trigger.getDragOrigin()); if ((selectionStart < selectionEnd) && (index >= selectionStart) && (index <= selectionEnd)) { try { Rectangle selectionStartBounds = component.modelToView(selectionStart); Rectangle selectionEndBounds = component.modelToView(selectionEnd); Rectangle selectionBounds = null; // Single-line selection: if (selectionStartBounds.y == selectionEndBounds.y) { selectionBounds = new Rectangle(selectionStartBounds.x, selectionStartBounds.y, selectionEndBounds.x - selectionStartBounds.x + selectionEndBounds.width, selectionEndBounds.y - selectionStartBounds.y + selectionEndBounds.height); } // Multi-line selection: else { AccessibleContext ctx = component.getAccessibleContext(); AccessibleText at = (AccessibleText) ctx; selectionBounds = component.modelToView(selectionStart); for (int i = selectionStart + 1; i <= selectionEnd; i++) { Rectangle charBounds = at.getCharacterBounds(i); // Invalid index returns null Rectangle // Note that this goes against jdk doc - should be empty, but is null instead if (charBounds != null) { selectionBounds.add(charBounds); } } } this.setOutlineDragImage(selectionBounds); handled = true; } catch (BadLocationException exc) { // Default the drag image to component bounds. } } if (handled == false) this.setDefaultDragImage(); } private void setDefaultDragImage(JTree component) { Rectangle selectedOutline = null; int[] selectedRows = component.getSelectionRows(); for (int i=0; i component) { Rectangle selectedOutline = null; // This code actually works, even under the (non-existant) multiple-selections, because we only draw a union outline int[] selectedIndices = component.getSelectedIndices(); if (selectedIndices.length > 0) selectedOutline = component.getCellBounds(selectedIndices[0], selectedIndices[selectedIndices.length-1]); if (selectedOutline != null) { this.setOutlineDragImage(selectedOutline); } else { this.setDefaultDragImage(); } } private void setDefaultDragImage() { DragGestureEvent trigger = this.getTrigger(); Component comp = trigger.getComponent(); setOutlineDragImage(new Rectangle(0, 0, comp.getWidth(), comp.getHeight()), true); } private void setOutlineDragImage(Rectangle outline) { setOutlineDragImage(outline, false); } private void setOutlineDragImage(Rectangle outline, Boolean shouldScale) { int width = (int)outline.getWidth(); int height = (int)outline.getHeight(); double scale = 1.0; if (shouldScale) { final int area = width * height; final int maxArea = (int)(fMaxImageSize * fMaxImageSize); if (area > maxArea) { scale = (double)area / (double)maxArea; width /= scale; height /= scale; } } if (width <=0) width = 1; if (height <=0) height = 1; DragGestureEvent trigger = this.getTrigger(); Component comp = trigger.getComponent(); Point compOffset = comp.getLocation(); // For lightweight components add some special treatment: if (comp instanceof JComponent) { // Intersect requested bounds with visible bounds: Rectangle visibleBounds = ((JComponent) comp).getVisibleRect(); Rectangle clipedOutline = outline.intersection(visibleBounds); if (clipedOutline.isEmpty() == false) outline = clipedOutline; // Compensate for the component offset (e.g. when contained in a JScrollPane): outline.translate(compOffset.x, compOffset.y); } GraphicsConfiguration config = comp.getGraphicsConfiguration(); BufferedImage dragImage = config.createCompatibleImage(width, height, Transparency.TRANSLUCENT); Color paint = Color.gray; BasicStroke stroke = new BasicStroke(2.0f); int halfLineWidth = (int) (stroke.getLineWidth() + 1) / 2; // Rounded up. Graphics2D g2 = (Graphics2D) dragImage.getGraphics(); g2.setPaint(paint); g2.setStroke(stroke); g2.drawRect(halfLineWidth, halfLineWidth, width - 2 * halfLineWidth - 1, height - 2 * halfLineWidth - 1); g2.dispose(); fDragImage = dragImage; Point dragOrigin = trigger.getDragOrigin(); Point dragImageOffset = new Point(outline.x - dragOrigin.x, outline.y - dragOrigin.y); if (comp instanceof JComponent) { dragImageOffset.translate(-compOffset.x, -compOffset.y); } if (shouldScale) { dragImageOffset.x /= scale; dragImageOffset.y /= scale; } fDragImageOffset = dragImageOffset; } /** * upcall from native code */ private void dragMouseMoved(final int targetActions, final int modifiers, final int x, final int y) { try { Component componentAt = LWCToolkit.invokeAndWait( new Callable() { @Override public Component call() { LWWindowPeer mouseEventComponent = LWWindowPeer.getWindowUnderCursor(); if (mouseEventComponent == null) { return null; } Component root = SwingUtilities.getRoot(mouseEventComponent.getTarget()); if (root == null) { return null; } Point rootLocation = root.getLocationOnScreen(); return getDropTargetAt(root, x - rootLocation.x, y - rootLocation.y); } }, getComponent()); if(componentAt != hoveringComponent) { if(hoveringComponent != null) { dragExit(x, y); } if(componentAt != null) { dragEnter(targetActions, modifiers, x, y); } hoveringComponent = componentAt; } postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_MOUSE_MOVED); } catch (Exception e) { throw new InvalidDnDOperationException("Failed to handle DragMouseMoved event"); } } //Returns the first lightweight or heavyweight Component which has a dropTarget ready to accept the drag //Should be called from the EventDispatchThread private static Component getDropTargetAt(Component root, int x, int y) { if (!root.contains(x, y) || !root.isEnabled() || !root.isVisible()) { return null; } if (root.getDropTarget() != null && root.getDropTarget().isActive()) { return root; } if (root instanceof Container) { for (Component comp : ((Container) root).getComponents()) { Point loc = comp.getLocation(); Component dropTarget = getDropTargetAt(comp, x - loc.x, y - loc.y); if (dropTarget != null) { return dropTarget; } } } return null; } /** * upcall from native code - reset hovering component */ private void resetHovering() { hoveringComponent = null; } @Override protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) { CCursorManager.getInstance().setCursor(c); } // Native support: private native long createNativeDragSource(Component component, long nativePeer, Transferable transferable, InputEvent triggerEvent, int dragPosX, int dragPosY, int extModifiers, int clickCount, long timestamp, long nsDragImagePtr, int dragImageOffsetX, int dragImageOffsetY, int sourceActions, long[] formats, Map formatMap); private native void doDragging(long nativeDragSource); private native void releaseNativeDragSource(long nativeDragSource); }