blob: c4ec7f766a183f6849b6da817ed21138e177b295 [file] [log] [blame]
/*
* Copyright (c) 2003, 2008, 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.awt.X11;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Window;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.InvalidDnDOperationException;
import java.util.*;
import sun.util.logging.PlatformLogger;
import sun.awt.dnd.SunDragSourceContextPeer;
import sun.awt.dnd.SunDropTargetContextPeer;
import sun.awt.SunToolkit;
import sun.awt.AWTAccessor;
/**
* The XDragSourceContextPeer class is the class responsible for handling
* the interaction between the XDnD/Motif DnD subsystem and Java drag sources.
*
* @since 1.5
*/
public final class XDragSourceContextPeer
extends SunDragSourceContextPeer implements XDragSourceProtocolListener {
private static final PlatformLogger logger =
PlatformLogger.getLogger("sun.awt.X11.xembed.xdnd.XDragSourceContextPeer");
/* The events selected on the root window when the drag begins. */
private static final int ROOT_EVENT_MASK = (int)XConstants.ButtonMotionMask |
(int)XConstants.KeyPressMask | (int)XConstants.KeyReleaseMask;
/* The events to be delivered during grab. */
private static final int GRAB_EVENT_MASK = (int)XConstants.ButtonPressMask |
(int)XConstants.ButtonMotionMask | (int)XConstants.ButtonReleaseMask;
/* The event mask of the root window before the drag operation starts. */
private long rootEventMask = 0;
private boolean dndInProgress = false;
private boolean dragInProgress = false;
private long dragRootWindow = 0;
/* The protocol chosen for the communication with the current drop target. */
private XDragSourceProtocol dragProtocol = null;
/* The drop action chosen by the current drop target. */
private int targetAction = DnDConstants.ACTION_NONE;
/* The set of drop actions supported by the drag source. */
private int sourceActions = DnDConstants.ACTION_NONE;
/* The drop action selected by the drag source based on the modifiers state
and the action selected by the current drop target. */
private int sourceAction = DnDConstants.ACTION_NONE;
/* The data formats supported by the drag source for the current drag
operation. */
private long[] sourceFormats = null;
/* The XID of the root subwindow that contains the current target. */
private long targetRootSubwindow = 0;
/* The pointer location. */
private int xRoot = 0;
private int yRoot = 0;
/* Keyboard modifiers state. */
private int eventState = 0;
/* XEmbed DnD support. We act as a proxy between source and target. */
private long proxyModeSourceWindow = 0;
/* The singleton instance. */
private static final XDragSourceContextPeer theInstance =
new XDragSourceContextPeer(null);
private XDragSourceContextPeer(DragGestureEvent dge) {
super(dge);
}
static XDragSourceProtocolListener getXDragSourceProtocolListener() {
return theInstance;
}
static XDragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge)
throws InvalidDnDOperationException {
theInstance.setTrigger(dge);
return theInstance;
}
protected void startDrag(Transferable transferable,
long[] formats, Map formatMap) {
Component component = getTrigger().getComponent();
Component c = null;
XWindowPeer wpeer = null;
for (c = component; c != null && !(c instanceof Window);
c = AWTAccessor.getComponentAccessor().getParent(c));
if (c instanceof Window) {
wpeer = (XWindowPeer)c.getPeer();
}
if (wpeer == null) {
throw new InvalidDnDOperationException(
"Cannot find top-level for the drag source component");
}
long xcursor = 0;
long rootWindow = 0;
long dragWindow = 0;
long timeStamp = 0;
/* Retrieve the X cursor for the drag operation. */
{
Cursor cursor = getCursor();
if (cursor != null) {
xcursor = XGlobalCursorManager.getCursor(cursor);
}
}
XToolkit.awtLock();
try {
if (proxyModeSourceWindow != 0) {
throw new InvalidDnDOperationException("Proxy drag in progress");
}
if (dndInProgress) {
throw new InvalidDnDOperationException("Drag in progress");
}
/* Determine the root window for the drag operation. */
{
long screen = XlibWrapper.XScreenNumberOfScreen(wpeer.getScreen());
rootWindow = XlibWrapper.RootWindow(XToolkit.getDisplay(), screen);
}
dragWindow = XWindow.getXAWTRootWindow().getWindow();
timeStamp = XToolkit.getCurrentServerTime();
int dropActions = getDragSourceContext().getSourceActions();
Iterator dragProtocols = XDragAndDropProtocols.getDragSourceProtocols();
while (dragProtocols.hasNext()) {
XDragSourceProtocol dragProtocol = (XDragSourceProtocol)dragProtocols.next();
try {
dragProtocol.initializeDrag(dropActions, transferable,
formatMap, formats);
} catch (XException xe) {
throw (InvalidDnDOperationException)
new InvalidDnDOperationException().initCause(xe);
}
}
/* Install X grabs. */
{
int status;
XWindowAttributes wattr = new XWindowAttributes();
try {
status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(),
rootWindow, wattr.pData);
if (status == 0) {
throw new InvalidDnDOperationException("XGetWindowAttributes failed");
}
rootEventMask = wattr.get_your_event_mask();
XlibWrapper.XSelectInput(XToolkit.getDisplay(), rootWindow,
rootEventMask | ROOT_EVENT_MASK);
} finally {
wattr.dispose();
}
XBaseWindow.ungrabInput();
status = XlibWrapper.XGrabPointer(XToolkit.getDisplay(), rootWindow,
0, GRAB_EVENT_MASK,
XConstants.GrabModeAsync,
XConstants.GrabModeAsync,
XConstants.None, xcursor, timeStamp);
if (status != XConstants.GrabSuccess) {
cleanup(timeStamp);
throwGrabFailureException("Cannot grab pointer", status);
return;
}
status = XlibWrapper.XGrabKeyboard(XToolkit.getDisplay(), rootWindow,
0,
XConstants.GrabModeAsync,
XConstants.GrabModeAsync,
timeStamp);
if (status != XConstants.GrabSuccess) {
cleanup(timeStamp);
throwGrabFailureException("Cannot grab keyboard", status);
return;
}
}
/* Update the global state. */
dndInProgress = true;
dragInProgress = true;
dragRootWindow = rootWindow;
sourceActions = dropActions;
sourceFormats = formats;
} finally {
XToolkit.awtUnlock();
}
/* This implementation doesn't use native context */
setNativeContext(0);
SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(transferable);
}
public long getProxyModeSourceWindow() {
return proxyModeSourceWindow;
}
private void setProxyModeSourceWindowImpl(long window) {
proxyModeSourceWindow = window;
}
public static void setProxyModeSourceWindow(long window) {
theInstance.setProxyModeSourceWindowImpl(window);
}
/**
* set cursor
*/
public void setCursor(Cursor c) throws InvalidDnDOperationException {
XToolkit.awtLock();
try {
super.setCursor(c);
} finally {
XToolkit.awtUnlock();
}
}
protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) {
assert XToolkit.isAWTLockHeldByCurrentThread();
if (c == null) {
return;
}
long xcursor = XGlobalCursorManager.getCursor(c);
if (xcursor == 0) {
return;
}
XlibWrapper.XChangeActivePointerGrab(XToolkit.getDisplay(),
GRAB_EVENT_MASK,
xcursor,
XConstants.CurrentTime);
}
protected boolean needsBogusExitBeforeDrop() {
return false;
}
private void throwGrabFailureException(String msg, int grabStatus)
throws InvalidDnDOperationException {
String msgCause = "";
switch (grabStatus) {
case XConstants.GrabNotViewable: msgCause = "not viewable"; break;
case XConstants.AlreadyGrabbed: msgCause = "already grabbed"; break;
case XConstants.GrabInvalidTime: msgCause = "invalid time"; break;
case XConstants.GrabFrozen: msgCause = "grab frozen"; break;
default: msgCause = "unknown failure"; break;
}
throw new InvalidDnDOperationException(msg + ": " + msgCause);
}
/**
* The caller must own awtLock.
*/
public void cleanup(long time) {
if (dndInProgress) {
if (dragProtocol != null) {
dragProtocol.sendLeaveMessage(time);
}
if (targetAction != DnDConstants.ACTION_NONE) {
dragExit(xRoot, yRoot);
}
dragDropFinished(false, DnDConstants.ACTION_NONE, xRoot, yRoot);
}
Iterator dragProtocols = XDragAndDropProtocols.getDragSourceProtocols();
while (dragProtocols.hasNext()) {
XDragSourceProtocol dragProtocol = (XDragSourceProtocol)dragProtocols.next();
try {
dragProtocol.cleanup();
} catch (XException xe) {
// Ignore the exception.
}
}
dndInProgress = false;
dragInProgress = false;
dragRootWindow = 0;
sourceFormats = null;
sourceActions = DnDConstants.ACTION_NONE;
sourceAction = DnDConstants.ACTION_NONE;
eventState = 0;
xRoot = 0;
yRoot = 0;
cleanupTargetInfo();
removeDnDGrab(time);
}
/**
* The caller must own awtLock.
*/
private void cleanupTargetInfo() {
targetAction = DnDConstants.ACTION_NONE;
dragProtocol = null;
targetRootSubwindow = 0;
}
private void removeDnDGrab(long time) {
assert XToolkit.isAWTLockHeldByCurrentThread();
XlibWrapper.XUngrabPointer(XToolkit.getDisplay(), time);
XlibWrapper.XUngrabKeyboard(XToolkit.getDisplay(), time);
/* Restore the root event mask if it was changed. */
if ((rootEventMask | ROOT_EVENT_MASK) != rootEventMask &&
dragRootWindow != 0) {
XlibWrapper.XSelectInput(XToolkit.getDisplay(),
dragRootWindow,
rootEventMask);
}
rootEventMask = 0;
dragRootWindow = 0;
}
private boolean processClientMessage(XClientMessageEvent xclient) {
if (dragProtocol != null) {
return dragProtocol.processClientMessage(xclient);
}
return false;
}
/**
* Updates the source action according to the specified state.
*
* @returns true if the source
*/
private boolean updateSourceAction(int state) {
int action = SunDragSourceContextPeer.convertModifiersToDropAction(XWindow.getModifiers(state, 0, 0),
sourceActions);
if (sourceAction == action) {
return false;
}
sourceAction = action;
return true;
}
/**
* Returns the client window under the specified root subwindow.
*/
private static long findClientWindow(long window) {
if (XlibUtil.isTrueToplevelWindow(window)) {
return window;
}
Set<Long> children = XlibUtil.getChildWindows(window);
for (Long child : children) {
long win = findClientWindow(child);
if (win != 0) {
return win;
}
}
return 0;
}
private void doUpdateTargetWindow(long subwindow, long time) {
long clientWindow = 0;
long proxyWindow = 0;
XDragSourceProtocol protocol = null;
boolean isReceiver = false;
if (subwindow != 0) {
clientWindow = findClientWindow(subwindow);
}
if (clientWindow != 0) {
Iterator dragProtocols = XDragAndDropProtocols.getDragSourceProtocols();
while (dragProtocols.hasNext()) {
XDragSourceProtocol dragProtocol = (XDragSourceProtocol)dragProtocols.next();
if (dragProtocol.attachTargetWindow(clientWindow, time)) {
protocol = dragProtocol;
break;
}
}
}
/* Update the global state. */
dragProtocol = protocol;
targetAction = DnDConstants.ACTION_NONE;
targetRootSubwindow = subwindow;
}
private void updateTargetWindow(XMotionEvent xmotion) {
assert XToolkit.isAWTLockHeldByCurrentThread();
int x = xmotion.get_x_root();
int y = xmotion.get_y_root();
long time = xmotion.get_time();
long subwindow = xmotion.get_subwindow();
/*
* If this event had occurred before the pointer was grabbed,
* query the server for the current root subwindow.
*/
if (xmotion.get_window() != xmotion.get_root()) {
XlibWrapper.XQueryPointer(XToolkit.getDisplay(),
xmotion.get_root(),
XlibWrapper.larg1, // root
XlibWrapper.larg2, // subwindow
XlibWrapper.larg3, // x_root
XlibWrapper.larg4, // y_root
XlibWrapper.larg5, // x
XlibWrapper.larg6, // y
XlibWrapper.larg7); // modifiers
subwindow = Native.getLong(XlibWrapper.larg2);
}
if (targetRootSubwindow != subwindow) {
if (dragProtocol != null) {
dragProtocol.sendLeaveMessage(time);
/*
* Neither Motif DnD nor XDnD provide a mean for the target
* to notify the source that the pointer exits the drop site
* that occupies the whole top level.
* We detect this situation and post dragExit.
*/
if (targetAction != DnDConstants.ACTION_NONE) {
dragExit(x, y);
}
}
/* Update the global state. */
doUpdateTargetWindow(subwindow, time);
if (dragProtocol != null) {
dragProtocol.sendEnterMessage(sourceFormats,
sourceAction,
sourceActions,
time);
}
}
}
/*
* DO NOT USE is_hint field of xmotion since it could not be set when we
* convert XKeyEvent or XButtonRelease to XMotionEvent.
*/
private void processMouseMove(XMotionEvent xmotion) {
if (!dragInProgress) {
return;
}
if (xRoot != xmotion.get_x_root() || yRoot != xmotion.get_y_root()) {
xRoot = xmotion.get_x_root();
yRoot = xmotion.get_y_root();
postDragSourceDragEvent(targetAction,
XWindow.getModifiers(xmotion.get_state(),0,0),
xRoot, yRoot, DISPATCH_MOUSE_MOVED);
}
if (eventState != xmotion.get_state()) {
if (updateSourceAction(xmotion.get_state()) && dragProtocol != null) {
postDragSourceDragEvent(targetAction,
XWindow.getModifiers(xmotion.get_state(),0,0),
xRoot, yRoot, DISPATCH_CHANGED);
}
eventState = xmotion.get_state();
}
updateTargetWindow(xmotion);
if (dragProtocol != null) {
dragProtocol.sendMoveMessage(xmotion.get_x_root(),
xmotion.get_y_root(),
sourceAction, sourceActions,
xmotion.get_time());
}
}
private void processDrop(XButtonEvent xbutton) {
try {
dragProtocol.initiateDrop(xbutton.get_x_root(),
xbutton.get_y_root(),
sourceAction, sourceActions,
xbutton.get_time());
} catch (XException e) {
cleanup(xbutton.get_time());
}
}
private boolean processProxyModeEvent(XEvent ev) {
if (getProxyModeSourceWindow() == 0) {
return false;
}
if (ev.get_type() != (int)XConstants.ClientMessage) {
return false;
}
if (logger.isLoggable(PlatformLogger.FINEST)) {
logger.finest(" proxyModeSourceWindow=" +
getProxyModeSourceWindow() +
" ev=" + ev);
}
XClientMessageEvent xclient = ev.get_xclient();
Iterator dragProtocols = XDragAndDropProtocols.getDragSourceProtocols();
while (dragProtocols.hasNext()) {
XDragSourceProtocol dragProtocol =
(XDragSourceProtocol)dragProtocols.next();
if (dragProtocol.processProxyModeEvent(xclient,
getProxyModeSourceWindow())) {
return true;
}
}
return false;
}
/**
* The caller must own awtLock.
*
* @returns true if the even was processed and shouldn't be passed along.
*/
private boolean doProcessEvent(XEvent ev) {
assert XToolkit.isAWTLockHeldByCurrentThread();
if (processProxyModeEvent(ev)) {
return true;
}
if (!dndInProgress) {
return false;
}
switch (ev.get_type()) {
case XConstants.ClientMessage: {
XClientMessageEvent xclient = ev.get_xclient();
return processClientMessage(xclient);
}
case XConstants.DestroyNotify: {
XDestroyWindowEvent xde = ev.get_xdestroywindow();
/* Target crashed during drop processing - cleanup. */
if (!dragInProgress &&
dragProtocol != null &&
xde.get_window() == dragProtocol.getTargetWindow()) {
cleanup(XConstants.CurrentTime);
return true;
}
/* Pass along */
return false;
}
}
if (!dragInProgress) {
return false;
}
/* Process drag-only messages. */
switch (ev.get_type()) {
case XConstants.KeyRelease:
case XConstants.KeyPress: {
XKeyEvent xkey = ev.get_xkey();
long keysym = XlibWrapper.XKeycodeToKeysym(XToolkit.getDisplay(),
xkey.get_keycode(), 0);
switch ((int)keysym) {
case (int)XKeySymConstants.XK_Escape: {
if (ev.get_type() == (int)XConstants.KeyRelease) {
cleanup(xkey.get_time());
}
break;
}
case (int)XKeySymConstants.XK_Control_R:
case (int)XKeySymConstants.XK_Control_L:
case (int)XKeySymConstants.XK_Shift_R:
case (int)XKeySymConstants.XK_Shift_L: {
XlibWrapper.XQueryPointer(XToolkit.getDisplay(),
xkey.get_root(),
XlibWrapper.larg1, // root
XlibWrapper.larg2, // subwindow
XlibWrapper.larg3, // x_root
XlibWrapper.larg4, // y_root
XlibWrapper.larg5, // x
XlibWrapper.larg6, // y
XlibWrapper.larg7); // modifiers
XMotionEvent xmotion = new XMotionEvent();
try {
xmotion.set_type(XConstants.MotionNotify);
xmotion.set_serial(xkey.get_serial());
xmotion.set_send_event(xkey.get_send_event());
xmotion.set_display(xkey.get_display());
xmotion.set_window(xkey.get_window());
xmotion.set_root(xkey.get_root());
xmotion.set_subwindow(xkey.get_subwindow());
xmotion.set_time(xkey.get_time());
xmotion.set_x(xkey.get_x());
xmotion.set_y(xkey.get_y());
xmotion.set_x_root(xkey.get_x_root());
xmotion.set_y_root(xkey.get_y_root());
xmotion.set_state((int)Native.getLong(XlibWrapper.larg7));
// we do not use this field, so it's unset for now
// xmotion.set_is_hint(???);
xmotion.set_same_screen(xkey.get_same_screen());
//It's safe to use key event as motion event since we use only their common fields.
processMouseMove(xmotion);
} finally {
xmotion.dispose();
}
break;
}
}
return true;
}
case XConstants.ButtonPress:
return true;
case XConstants.MotionNotify:
processMouseMove(ev.get_xmotion());
return true;
case XConstants.ButtonRelease: {
XButtonEvent xbutton = ev.get_xbutton();
/*
* Ignore the buttons above 20 due to the bit limit for
* InputEvent.BUTTON_DOWN_MASK.
* One more bit is reserved for FIRST_HIGH_BIT.
*/
if (xbutton.get_button() > SunToolkit.MAX_BUTTONS_SUPPORTED) {
return true;
}
/*
* On some X servers it could happen that ButtonRelease coordinates
* differ from the latest MotionNotify coordinates, so we need to
* process it as a mouse motion.
*/
XMotionEvent xmotion = new XMotionEvent();
try {
xmotion.set_type(XConstants.MotionNotify);
xmotion.set_serial(xbutton.get_serial());
xmotion.set_send_event(xbutton.get_send_event());
xmotion.set_display(xbutton.get_display());
xmotion.set_window(xbutton.get_window());
xmotion.set_root(xbutton.get_root());
xmotion.set_subwindow(xbutton.get_subwindow());
xmotion.set_time(xbutton.get_time());
xmotion.set_x(xbutton.get_x());
xmotion.set_y(xbutton.get_y());
xmotion.set_x_root(xbutton.get_x_root());
xmotion.set_y_root(xbutton.get_y_root());
xmotion.set_state(xbutton.get_state());
// we do not use this field, so it's unset for now
// xmotion.set_is_hint(???);
xmotion.set_same_screen(xbutton.get_same_screen());
//It's safe to use key event as motion event since we use only their common fields.
processMouseMove(xmotion);
} finally {
xmotion.dispose();
}
if (xbutton.get_button() == XConstants.buttons[0]
|| xbutton.get_button() == XConstants.buttons[1]) {
// drag is initiated with Button1 or Button2 pressed and
// ended on release of either of these buttons (as the same
// behavior was with our old Motif DnD-based implementation)
removeDnDGrab(xbutton.get_time());
dragInProgress = false;
if (dragProtocol != null && targetAction != DnDConstants.ACTION_NONE) {
/*
* ACTION_NONE indicates that either the drop target rejects the
* drop or it haven't responded yet. The latter could happen in
* case of fast drag, slow target-server connection or slow
* drag notifications processing on the target side.
*/
processDrop(xbutton);
} else {
cleanup(xbutton.get_time());
}
}
return true;
}
}
return false;
}
static boolean processEvent(XEvent ev) {
XToolkit.awtLock();
try {
try {
return theInstance.doProcessEvent(ev);
} catch (XException e) {
e.printStackTrace();
return false;
}
} finally {
XToolkit.awtUnlock();
}
}
/* XDragSourceProtocolListener implementation */
public void handleDragReply(int action) {
// NOTE: we have to use the current pointer location, since
// the target didn't specify the coordinates for the reply.
handleDragReply(action, xRoot, yRoot);
}
public void handleDragReply(int action, int x, int y) {
// NOTE: we have to use the current modifiers state, since
// the target didn't specify the modifiers state for the reply.
handleDragReply(action, xRoot, yRoot, XWindow.getModifiers(eventState,0,0));
}
public void handleDragReply(int action, int x, int y, int modifiers) {
if (action == DnDConstants.ACTION_NONE &&
targetAction != DnDConstants.ACTION_NONE) {
dragExit(x, y);
} else if (action != DnDConstants.ACTION_NONE) {
int type = 0;
if (targetAction == DnDConstants.ACTION_NONE) {
type = SunDragSourceContextPeer.DISPATCH_ENTER;
} else {
type = SunDragSourceContextPeer.DISPATCH_MOTION;
}
// Note that we use the modifiers state a
postDragSourceDragEvent(action, modifiers, x, y, type);
}
targetAction = action;
}
public void handleDragFinished() {
/* Assume that the drop was successful. */
handleDragFinished(true);
}
public void handleDragFinished(boolean success) {
/* Assume that the performed drop action is the latest drop action
accepted by the drop target. */
handleDragFinished(true, targetAction);
}
public void handleDragFinished(boolean success, int action) {
// NOTE: we have to use the current pointer location, since
// the target didn't specify the coordinates for the reply.
handleDragFinished(success, action, xRoot, yRoot);
}
public void handleDragFinished(boolean success, int action, int x, int y) {
dragDropFinished(success, action, x, y);
dndInProgress = false;
cleanup(XConstants.CurrentTime);
}
}