blob: 92f2b6df7d5507a20bf190227828f41dff6c81ad [file] [log] [blame]
/*
* Copyright (c) 2011, 2013, 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 java.awt.peer.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.accessibility.*;
import java.util.Map;
import java.util.concurrent.Callable;
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());
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
PlatformWindow platformWindow = ((LWComponentPeer)rootComponent.getPeer()).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, 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 {
Thread dragThread = new Thread() {
public void run() {
final long nativeDragSource = getNativeContext();
try {
doDragging(nativeDragSource);
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseNativeDragSource(nativeDragSource);
fDragImage = null;
if (fDragCImage != null) {
fDragCImage.dispose();
fDragCImage = null;
}
}
}
};
dragThread.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();
}
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<selectedRows.length; i++) {
Rectangle r = component.getRowBounds(selectedRows[i]);
if (selectedOutline == null)
selectedOutline = r;
else
selectedOutline.add(r);
}
if (selectedOutline != null) {
this.setOutlineDragImage(selectedOutline);
} else {
this.setDefaultDragImage();
}
}
private void setDefaultDragImage(JTable component) {
Rectangle selectedOutline = null;
// This code will likely break once multiple selections works (3645873)
int[] selectedRows = component.getSelectedRows();
int[] selectedColumns = component.getSelectedColumns();
for (int row=0; row<selectedRows.length; row++) {
for (int col=0; col<selectedColumns.length; col++) {
Rectangle r = component.getCellRect(selectedRows[row], selectedColumns[col], true);
if (selectedOutline == null)
selectedOutline = r;
else
selectedOutline.add(r);
}
}
if (selectedOutline != null) {
this.setOutlineDragImage(selectedOutline);
} else {
this.setDefaultDragImage();
}
}
private void setDefaultDragImage(JList 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<Component>() {
@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,
CImage nsDragImage, int dragImageOffsetX, int dragImageOffsetY,
int sourceActions, long[] formats, Map formatMap);
private native void doDragging(long nativeDragSource);
private native void releaseNativeDragSource(long nativeDragSource);
}