mirror of
https://github.com/gryf/wmaker.git
synced 2025-12-19 12:28:22 +01:00
1315 lines
34 KiB
C
1315 lines
34 KiB
C
#include "../src/config.h"
|
|
|
|
#include <X11/Xatom.h>
|
|
#include <X11/cursorfont.h>
|
|
#ifdef SHAPE
|
|
#include <X11/extensions/shape.h>
|
|
#endif
|
|
|
|
#include "WINGsP.h"
|
|
|
|
|
|
#define XDND_DESTINATION_RESPONSE_MAX_DELAY 10000
|
|
#define MIN_X_MOVE_OFFSET 5
|
|
#define MIN_Y_MOVE_OFFSET 5
|
|
#define MAX_SLIDEBACK_ITER 15
|
|
|
|
#define VERSION_INFO(dragInfo) dragInfo->protocolVersion
|
|
#define XDND_PROPERTY_FORMAT 32
|
|
#define XDND_ACTION_DESCRIPTION_FORMAT 8
|
|
|
|
#define XDND_SOURCE_INFO(dragInfo) dragInfo->sourceInfo
|
|
#define XDND_DEST_WIN(dragInfo) dragInfo->sourceInfo->destinationWindow
|
|
#define XDND_SOURCE_ACTION(dragInfo) dragInfo->sourceAction
|
|
#define XDND_DEST_ACTION(dragInfo) dragInfo->destinationAction
|
|
#define XDND_SOURCE_VIEW(dragInfo) dragInfo->sourceInfo->sourceView
|
|
#define XDND_SOURCE_STATE(dragInfo) dragInfo->sourceInfo->state
|
|
#define XDND_SELECTION_PROCS(dragInfo) dragInfo->sourceInfo->selectionProcs
|
|
#define XDND_DRAG_ICON(dragInfo) dragInfo->sourceInfo->icon
|
|
#define XDND_MOUSE_OFFSET(dragInfo) dragInfo->sourceInfo->mouseOffset
|
|
#define XDND_DRAG_CURSOR(dragInfo) dragInfo->sourceInfo->dragCursor
|
|
#define XDND_DRAG_ICON_POS(dragInfo) dragInfo->sourceInfo->imageLocation
|
|
#define XDND_NO_POS_ZONE(dragInfo) dragInfo->sourceInfo->noPositionMessageZone
|
|
#define XDND_TIMESTAMP(dragInfo) dragInfo->timestamp
|
|
#define XDND_3_TYPES(dragInfo) dragInfo->sourceInfo->firstThreeTypes
|
|
#define XDND_SOURCE_VIEW_STORED(dragInfo) dragInfo->sourceInfo != NULL \
|
|
&& dragInfo->sourceInfo->sourceView != NULL
|
|
|
|
|
|
static WMHandlerID dndSourceTimer = NULL;
|
|
|
|
|
|
static void* idleState(WMView *srcView, XClientMessageEvent *event,
|
|
WMDraggingInfo *info);
|
|
static void* dropAllowedState(WMView *srcView, XClientMessageEvent *event,
|
|
WMDraggingInfo *info);
|
|
static void* finishDropState(WMView *srcView, XClientMessageEvent *event,
|
|
WMDraggingInfo *info);
|
|
|
|
#ifdef XDND_DEBUG
|
|
static const char*
|
|
stateName(W_DndState *state)
|
|
{
|
|
if (state == NULL)
|
|
return "no state defined";
|
|
|
|
if (state == idleState)
|
|
return "idleState";
|
|
|
|
if (state == dropAllowedState)
|
|
return "dropAllowedState";
|
|
|
|
if (state == finishDropState)
|
|
return "finishDropState";
|
|
|
|
return "unknown state";
|
|
}
|
|
#endif
|
|
|
|
|
|
static WMScreen*
|
|
sourceScreen(WMDraggingInfo *info)
|
|
{
|
|
return W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
|
|
}
|
|
|
|
|
|
static void
|
|
endDragProcess(WMDraggingInfo *info, Bool deposited)
|
|
{
|
|
WMView *view = XDND_SOURCE_VIEW(info);
|
|
WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
|
|
|
|
/* free selection handler while view exists */
|
|
WMDeleteSelectionHandler(view,
|
|
scr->xdndSelectionAtom,
|
|
CurrentTime);
|
|
wfree(XDND_SELECTION_PROCS(info));
|
|
|
|
if (XDND_DRAG_CURSOR(info) != None) {
|
|
XFreeCursor(scr->display,
|
|
XDND_DRAG_CURSOR(info));
|
|
XDND_DRAG_CURSOR(info) = None;
|
|
}
|
|
|
|
if (view->dragSourceProcs->endedDrag != NULL) {
|
|
/* this can destroy source view (with a "move" action for example) */
|
|
view->dragSourceProcs->endedDrag(view, &XDND_DRAG_ICON_POS(info),
|
|
deposited);
|
|
}
|
|
|
|
/* clear remaining draggging infos */
|
|
wfree(XDND_SOURCE_INFO(info));
|
|
XDND_SOURCE_INFO(info) = NULL;
|
|
}
|
|
|
|
|
|
/* ----- drag cursor ----- */
|
|
static void
|
|
initDragCursor(WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
XColor cursorFgColor, cursorBgColor;
|
|
|
|
/* green */
|
|
cursorFgColor.red = 0x4500;
|
|
cursorFgColor.green = 0xb000;
|
|
cursorFgColor.blue = 0x4500;
|
|
|
|
/* white */
|
|
cursorBgColor.red = 0xffff;
|
|
cursorBgColor.green = 0xffff;
|
|
cursorBgColor.blue = 0xffff;
|
|
|
|
XDND_DRAG_CURSOR(info) = XCreateFontCursor(scr->display, XC_left_ptr);
|
|
XRecolorCursor(scr->display,
|
|
XDND_DRAG_CURSOR(info),
|
|
&cursorFgColor,
|
|
&cursorBgColor);
|
|
|
|
XFlush(scr->display);
|
|
}
|
|
|
|
static void
|
|
recolorCursor(WMDraggingInfo *info, Bool dropIsAllowed)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
|
|
if (dropIsAllowed) {
|
|
XDefineCursor(scr->display,
|
|
scr->rootWin,
|
|
XDND_DRAG_CURSOR(info));
|
|
} else {
|
|
XDefineCursor(scr->display,
|
|
scr->rootWin,
|
|
scr->defaultCursor);
|
|
}
|
|
|
|
XFlush(scr->display);
|
|
}
|
|
/* ----- end of drag cursor ----- */
|
|
|
|
|
|
/* ----- selection procs ----- */
|
|
static WMData*
|
|
convertSelection(WMView *view, Atom selection, Atom target,
|
|
void *cdata, Atom *type)
|
|
{
|
|
WMScreen *scr;
|
|
WMData *data;
|
|
char *typeName;
|
|
|
|
scr = W_VIEW_SCREEN(view);
|
|
typeName = XGetAtomName(scr->display, target);
|
|
|
|
*type = target;
|
|
|
|
if (view->dragSourceProcs->fetchDragData != NULL) {
|
|
data = view->dragSourceProcs->fetchDragData(
|
|
view,
|
|
typeName);
|
|
} else {
|
|
data = NULL;
|
|
}
|
|
|
|
if (typeName != NULL)
|
|
XFree(typeName);
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
static void
|
|
selectionLost(WMView *view, Atom selection, void *cdata)
|
|
{
|
|
wwarning("DND selection lost during drag operation...");
|
|
}
|
|
|
|
|
|
static void
|
|
selectionDone(WMView *view, Atom selection, Atom target, void *cdata)
|
|
{
|
|
#ifdef XDND_DEBUG
|
|
printf("selection done\n");
|
|
#endif
|
|
}
|
|
/* ----- end of selection procs ----- */
|
|
|
|
|
|
/* ----- visual part ----- */
|
|
|
|
static Window
|
|
makeDragIcon(WMScreen *scr, WMPixmap *pixmap)
|
|
{
|
|
Window window;
|
|
WMSize size;
|
|
unsigned long flags;
|
|
XSetWindowAttributes attribs;
|
|
Pixmap pix, mask;
|
|
|
|
if (!pixmap) {
|
|
pixmap = scr->defaultObjectIcon;
|
|
}
|
|
|
|
size = WMGetPixmapSize(pixmap);
|
|
pix = pixmap->pixmap;
|
|
mask = pixmap->mask;
|
|
|
|
flags = CWSaveUnder|CWBackPixmap|CWOverrideRedirect|CWColormap;
|
|
attribs.save_under = True;
|
|
attribs.background_pixmap = pix;
|
|
attribs.override_redirect = True;
|
|
attribs.colormap = scr->colormap;
|
|
|
|
window = XCreateWindow(scr->display, scr->rootWin, 0, 0, size.width,
|
|
size.height, 0, scr->depth, InputOutput,
|
|
scr->visual, flags, &attribs);
|
|
|
|
#ifdef SHAPE
|
|
|
|
if (mask) {
|
|
XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, mask,
|
|
ShapeSet);
|
|
}
|
|
#endif
|
|
|
|
return window;
|
|
}
|
|
|
|
|
|
static void
|
|
slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
|
|
{
|
|
double x, y, dx, dy;
|
|
int i;
|
|
int iterations;
|
|
|
|
iterations = WMIN(MAX_SLIDEBACK_ITER,
|
|
WMAX(abs(dstX-srcX), abs(dstY-srcY)));
|
|
|
|
x = srcX;
|
|
y = srcY;
|
|
|
|
dx = (double)(dstX-srcX)/iterations;
|
|
dy = (double)(dstY-srcY)/iterations;
|
|
|
|
for (i = 0; i <= iterations; i++) {
|
|
XMoveWindow(dpy, win, x, y);
|
|
XFlush(dpy);
|
|
|
|
wusleep(800);
|
|
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
getInitialDragImageCoord(int viewCoord, int mouseCoord,
|
|
int viewSize, int iconSize)
|
|
{
|
|
if (iconSize >= viewSize) {
|
|
/* center icon coord on view */
|
|
return viewCoord - iconSize/2;
|
|
} else {
|
|
/* try to center icon on mouse pos */
|
|
|
|
if (mouseCoord - iconSize/2 <= viewCoord)
|
|
/* if icon was centered on mouse, it would be off view
|
|
thus, put icon left (resp. top) side
|
|
at view (resp. top) side */
|
|
return viewCoord;
|
|
|
|
else if (mouseCoord + iconSize/2 >= viewCoord + viewSize)
|
|
/* if icon was centered on mouse, it would be off view
|
|
thus, put icon right (resp. bottom) side
|
|
at view right (resp. bottom) side */
|
|
return viewCoord + viewSize - iconSize;
|
|
|
|
else
|
|
return mouseCoord - iconSize/2;
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
initDragImagePos(WMView *view, WMDraggingInfo *info, XEvent *event)
|
|
{
|
|
WMSize iconSize = WMGetPixmapSize(view->dragImage);
|
|
WMSize viewSize = WMGetViewSize(view);
|
|
WMPoint viewPos;
|
|
Window foo;
|
|
|
|
XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
|
|
WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
|
|
0, 0, &(viewPos.x), &(viewPos.y),
|
|
&foo);
|
|
|
|
/* set icon pos */
|
|
XDND_DRAG_ICON_POS(info).x =
|
|
getInitialDragImageCoord(viewPos.x, event->xmotion.x_root,
|
|
viewSize.width, iconSize.width);
|
|
|
|
XDND_DRAG_ICON_POS(info).y =
|
|
getInitialDragImageCoord(viewPos.y, event->xmotion.y_root,
|
|
viewSize.height, iconSize.height);
|
|
|
|
/* set mouse offset relative to icon */
|
|
XDND_MOUSE_OFFSET(info).x =
|
|
event->xmotion.x_root - XDND_DRAG_ICON_POS(info).x;
|
|
XDND_MOUSE_OFFSET(info).y =
|
|
event->xmotion.y_root - XDND_DRAG_ICON_POS(info).y;
|
|
}
|
|
|
|
|
|
static void
|
|
refreshDragImage(WMView *view, WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = W_VIEW_SCREEN(view);
|
|
|
|
XMoveWindow(scr->display, XDND_DRAG_ICON(info),
|
|
XDND_DRAG_ICON_POS(info).x,
|
|
XDND_DRAG_ICON_POS(info).y);
|
|
}
|
|
|
|
|
|
static void
|
|
startDragImage(WMView *view, WMDraggingInfo *info, XEvent* event)
|
|
{
|
|
WMScreen *scr = W_VIEW_SCREEN(view);
|
|
|
|
XDND_DRAG_ICON(info) = makeDragIcon(scr, view->dragImage);
|
|
initDragImagePos(view, info, event);
|
|
refreshDragImage(view, info);
|
|
XMapRaised(scr->display, XDND_DRAG_ICON(info));
|
|
|
|
initDragCursor(info);
|
|
}
|
|
|
|
|
|
static void
|
|
endDragImage(WMDraggingInfo *info, Bool slideBack)
|
|
{
|
|
WMView *view = XDND_SOURCE_VIEW(info);
|
|
Display *dpy = W_VIEW_SCREEN(view)->display;
|
|
|
|
if (slideBack) {
|
|
WMPoint toLocation;
|
|
Window foo;
|
|
|
|
XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
|
|
WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
|
|
0, 0, &(toLocation.x), &(toLocation.y),
|
|
&foo);
|
|
|
|
slideWindow(dpy, XDND_DRAG_ICON(info),
|
|
XDND_DRAG_ICON_POS(info).x,
|
|
XDND_DRAG_ICON_POS(info).y,
|
|
toLocation.x,
|
|
toLocation.y);
|
|
}
|
|
|
|
XDestroyWindow(dpy, XDND_DRAG_ICON(info));
|
|
}
|
|
|
|
/* ----- end of visual part ----- */
|
|
|
|
|
|
/* ----- messages ----- */
|
|
|
|
/* send a DnD message to the destination window */
|
|
static Bool
|
|
sendDnDClientMessage(WMDraggingInfo *info, Atom message,
|
|
unsigned long data1,
|
|
unsigned long data2,
|
|
unsigned long data3,
|
|
unsigned long data4)
|
|
{
|
|
Display *dpy = sourceScreen(info)->display;
|
|
Window srcWin = WMViewXID(XDND_SOURCE_VIEW(info));
|
|
Window destWin = XDND_DEST_WIN(info);
|
|
|
|
if (! W_SendDnDClientMessage(dpy,
|
|
destWin,
|
|
message,
|
|
srcWin,
|
|
data1,
|
|
data2,
|
|
data3,
|
|
data4)) {
|
|
/* drop failed */
|
|
recolorCursor(info, False);
|
|
endDragImage(info, True);
|
|
endDragProcess(info, False);
|
|
return False;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
|
|
static Bool
|
|
sendEnterMessage(WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
unsigned long data1;
|
|
|
|
data1 = (VERSION_INFO(info) << 24)|1; /* 1: support of type list */
|
|
|
|
return sendDnDClientMessage(info, scr->xdndEnterAtom,
|
|
data1,
|
|
XDND_3_TYPES(info)[0],
|
|
XDND_3_TYPES(info)[1],
|
|
XDND_3_TYPES(info)[2]);
|
|
}
|
|
|
|
|
|
// this functon doesn't return something in all cases.
|
|
// control reaches end of non-void function. fix this -Dan
|
|
static Bool
|
|
sendPositionMessage(WMDraggingInfo *info, WMPoint *mousePos)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));
|
|
|
|
if (noPosZone->size.width != 0 || noPosZone->size.height != 0) {
|
|
if (mousePos->x < noPosZone->pos.x
|
|
|| mousePos->x > (noPosZone->pos.x + noPosZone->size.width)
|
|
|| mousePos->y < noPosZone->pos.y
|
|
|| mousePos->y > (noPosZone->pos.y + noPosZone->size.width)) {
|
|
/* send position if out of zone defined by destination */
|
|
return sendDnDClientMessage(info, scr->xdndPositionAtom,
|
|
0,
|
|
mousePos->x<<16|mousePos->y,
|
|
XDND_TIMESTAMP(info),
|
|
XDND_SOURCE_ACTION(info));
|
|
}
|
|
} else {
|
|
/* send position on each move */
|
|
return sendDnDClientMessage(info, scr->xdndPositionAtom,
|
|
0,
|
|
mousePos->x<<16|mousePos->y,
|
|
XDND_TIMESTAMP(info),
|
|
XDND_SOURCE_ACTION(info));
|
|
}
|
|
}
|
|
|
|
|
|
static Bool
|
|
sendLeaveMessage(WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
|
|
return sendDnDClientMessage(info, scr->xdndLeaveAtom,
|
|
0, 0, 0, 0);
|
|
}
|
|
|
|
|
|
static Bool
|
|
sendDropMessage(WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
|
|
return sendDnDClientMessage(info,
|
|
scr->xdndDropAtom,
|
|
0,
|
|
XDND_TIMESTAMP(info),
|
|
0, 0);
|
|
}
|
|
|
|
/* ----- end of messages ----- */
|
|
|
|
|
|
static Atom*
|
|
getTypeAtomList(WMScreen *scr, WMView *view, int* count)
|
|
{
|
|
WMArray* types;
|
|
Atom* typeAtoms;
|
|
int i;
|
|
|
|
types = view->dragSourceProcs->dropDataTypes(view);
|
|
|
|
if (types != NULL) {
|
|
*count = WMGetArrayItemCount(types);
|
|
if (*count > 0) {
|
|
typeAtoms = wmalloc((*count)*sizeof(Atom));
|
|
for (i=0; i < *count; i++) {
|
|
typeAtoms[i] = XInternAtom(scr->display,
|
|
WMGetFromArray(types, i),
|
|
False);
|
|
}
|
|
|
|
/* WMFreeArray(types); */
|
|
return typeAtoms;
|
|
}
|
|
|
|
/* WMFreeArray(types); */
|
|
}
|
|
|
|
*count = 1;
|
|
typeAtoms = wmalloc(sizeof(Atom));
|
|
*typeAtoms = None;
|
|
|
|
return typeAtoms;
|
|
}
|
|
|
|
|
|
static void
|
|
registerDropTypes(WMScreen *scr, WMView *view, WMDraggingInfo *info)
|
|
{
|
|
Atom* typeList;
|
|
int i, count;
|
|
|
|
typeList = getTypeAtomList(scr, view, &count);
|
|
|
|
/* store the first 3 types */
|
|
for(i=0; i < 3 && i < count; i++)
|
|
XDND_3_TYPES(info)[i] = typeList[i];
|
|
|
|
for(; i < 3; i++)
|
|
XDND_3_TYPES(info)[i] = None;
|
|
|
|
|
|
/* store the entire type list */
|
|
XChangeProperty(scr->display,
|
|
WMViewXID(view),
|
|
scr->xdndTypeListAtom,
|
|
XA_ATOM,
|
|
XDND_PROPERTY_FORMAT,
|
|
PropModeReplace,
|
|
(unsigned char*) typeList,
|
|
count);
|
|
}
|
|
|
|
|
|
static void
|
|
registerOperationList(WMScreen *scr, WMView *view, WMArray* operationArray)
|
|
{
|
|
Atom* actionList;
|
|
WMDragOperationType operation;
|
|
int count = WMGetArrayItemCount(operationArray);
|
|
int i;
|
|
|
|
actionList = wmalloc(sizeof(Atom)*count);
|
|
|
|
for(i=0; i < count; i++) {
|
|
operation = WMGetDragOperationItemType(
|
|
WMGetFromArray(operationArray, i));
|
|
actionList[i] = W_OperationToAction(scr, operation);
|
|
}
|
|
|
|
XChangeProperty(scr->display,
|
|
WMViewXID(view),
|
|
scr->xdndActionListAtom,
|
|
XA_ATOM,
|
|
XDND_PROPERTY_FORMAT,
|
|
PropModeReplace,
|
|
(unsigned char*) actionList,
|
|
count);
|
|
}
|
|
|
|
static void
|
|
registerDescriptionList(WMScreen *scr, WMView *view, WMArray* operationArray)
|
|
{
|
|
char *text, *textListItem, *textList;
|
|
int count = WMGetArrayItemCount(operationArray);
|
|
int i;
|
|
int size = 0;
|
|
|
|
/* size of XA_STRING info */
|
|
for(i=0; i < count; i++) {
|
|
size += strlen(WMGetDragOperationItemText(
|
|
WMGetFromArray(operationArray, i))) + 1; /* +1 = +NULL */
|
|
}
|
|
|
|
/* create text list */
|
|
textList = wmalloc(size);
|
|
textListItem = textList;
|
|
|
|
for(i=0; i < count; i++) {
|
|
text = WMGetDragOperationItemText(WMGetFromArray(operationArray, i));
|
|
strcpy(textListItem, text);
|
|
|
|
/* to next text offset */
|
|
textListItem = &(textListItem[strlen(textListItem) + 1]);
|
|
}
|
|
|
|
XChangeProperty(scr->display,
|
|
WMViewXID(view),
|
|
scr->xdndActionDescriptionAtom,
|
|
XA_STRING,
|
|
XDND_ACTION_DESCRIPTION_FORMAT,
|
|
PropModeReplace,
|
|
(unsigned char*) textList,
|
|
size);
|
|
}
|
|
|
|
/* called if wanted operation is WDOperationAsk */
|
|
static void
|
|
registerSupportedOperations(WMView *view)
|
|
{
|
|
WMScreen *scr = W_VIEW_SCREEN(view);
|
|
WMArray* operationArray;
|
|
|
|
operationArray = view->dragSourceProcs->askedOperations(view);
|
|
|
|
registerOperationList(scr, view, operationArray);
|
|
registerDescriptionList(scr, view, operationArray);
|
|
|
|
/* WMFreeArray(operationArray); */
|
|
}
|
|
|
|
|
|
static void
|
|
initSourceDragInfo(WMView *sourceView, WMDraggingInfo *info)
|
|
{
|
|
WMRect emptyZone;
|
|
|
|
XDND_SOURCE_INFO(info) = (W_DragSourceInfo*) wmalloc(sizeof(W_DragSourceInfo));
|
|
|
|
XDND_SOURCE_VIEW(info) = sourceView;
|
|
XDND_DEST_WIN(info) = None;
|
|
XDND_DRAG_ICON(info) = None;
|
|
|
|
XDND_SOURCE_ACTION(info) = W_OperationToAction(
|
|
W_VIEW_SCREEN(sourceView),
|
|
sourceView->dragSourceProcs->wantedDropOperation(sourceView));
|
|
|
|
XDND_DEST_ACTION(info) = None;
|
|
|
|
XDND_SOURCE_STATE(info) = idleState;
|
|
|
|
emptyZone.pos = wmkpoint(0, 0);
|
|
emptyZone.size = wmksize(0, 0);
|
|
XDND_NO_POS_ZONE(info) = emptyZone;
|
|
}
|
|
|
|
|
|
/*
|
|
Returned array is destroyed after dropDataTypes call
|
|
*/
|
|
static WMArray*
|
|
defDropDataTypes(WMView *self)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static WMDragOperationType
|
|
defWantedDropOperation(WMView *self)
|
|
{
|
|
return WDOperationNone;
|
|
}
|
|
|
|
|
|
/*
|
|
Must be defined if wantedDropOperation return WDOperationAsk
|
|
(useless otherwise).
|
|
Return a WMDragOperationItem array (destroyed after call).
|
|
A WMDragOperationItem links a label to an operation.
|
|
static WMArray*
|
|
defAskedOperations(WMView *self); */
|
|
|
|
|
|
static Bool
|
|
defAcceptDropOperation(WMView *self, WMDragOperationType allowedOperation)
|
|
{
|
|
return False;
|
|
}
|
|
|
|
|
|
static void
|
|
defBeganDrag(WMView *self, WMPoint *point)
|
|
{
|
|
}
|
|
|
|
|
|
static void
|
|
defEndedDrag(WMView *self, WMPoint *point, Bool deposited)
|
|
{
|
|
}
|
|
|
|
|
|
/*
|
|
Returned data is not destroyed
|
|
*/
|
|
static WMData*
|
|
defFetchDragData(WMView *self, char *type)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
WMSetViewDragSourceProcs(WMView *view, WMDragSourceProcs *procs)
|
|
{
|
|
if (view->dragSourceProcs)
|
|
wfree(view->dragSourceProcs);
|
|
view->dragSourceProcs = wmalloc(sizeof(WMDragSourceProcs));
|
|
|
|
*view->dragSourceProcs = *procs;
|
|
|
|
if (procs->dropDataTypes == NULL)
|
|
view->dragSourceProcs->dropDataTypes = defDropDataTypes;
|
|
|
|
if (procs->wantedDropOperation == NULL)
|
|
view->dragSourceProcs->wantedDropOperation = defWantedDropOperation;
|
|
|
|
/*
|
|
Note: askedOperations can be NULL, if wantedDropOperation never returns
|
|
WDOperationAsk.
|
|
*/
|
|
|
|
if (procs->acceptDropOperation == NULL)
|
|
view->dragSourceProcs->acceptDropOperation = defAcceptDropOperation;
|
|
|
|
if (procs->beganDrag == NULL)
|
|
view->dragSourceProcs->beganDrag = defBeganDrag;
|
|
|
|
if (procs->endedDrag == NULL)
|
|
view->dragSourceProcs->endedDrag = defEndedDrag;
|
|
|
|
if (procs->fetchDragData == NULL)
|
|
view->dragSourceProcs->fetchDragData = defFetchDragData;
|
|
}
|
|
|
|
|
|
static Bool
|
|
isXdndAware(WMScreen *scr, Window win)
|
|
{
|
|
Atom type;
|
|
int format;
|
|
unsigned long count, remain;
|
|
unsigned char *winXdndVersion;
|
|
|
|
if (win == None)
|
|
return False;
|
|
|
|
XGetWindowProperty(scr->display, win, scr->xdndAwareAtom,
|
|
0, 1, False, XA_ATOM, &type, &format,
|
|
&count, &remain, &winXdndVersion);
|
|
|
|
if (type != XA_ATOM
|
|
|| format != XDND_PROPERTY_FORMAT
|
|
|| count == 0 || !winXdndVersion) {
|
|
if (winXdndVersion)
|
|
XFree(winXdndVersion);
|
|
return False;
|
|
}
|
|
|
|
XFree(winXdndVersion);
|
|
return (count == 1); /* xdnd version is set */
|
|
}
|
|
|
|
|
|
static Window*
|
|
windowChildren(Display *dpy, Window win, unsigned *nchildren)
|
|
{
|
|
Window *children;
|
|
Window foo, bar;
|
|
|
|
if (!XQueryTree(dpy, win, &foo, &bar, &children, nchildren)) {
|
|
*nchildren = 0;
|
|
return NULL;
|
|
} else
|
|
return children;
|
|
}
|
|
|
|
static Window
|
|
lookForAwareWindow(WMScreen *scr, WMPoint *mousePos, Window win)
|
|
{
|
|
int tmpx, tmpy;
|
|
Window child;
|
|
|
|
/* Since xdnd v3, only the toplevel window should be aware */
|
|
if (isXdndAware(scr, win))
|
|
return win;
|
|
|
|
/* inspect child under pointer */
|
|
if (XTranslateCoordinates(scr->display, scr->rootWin, win,
|
|
mousePos->x, mousePos->y, &tmpx, &tmpy,
|
|
&child)) {
|
|
if (child == None)
|
|
return None;
|
|
else
|
|
return lookForAwareWindow(scr, mousePos, child);
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
|
|
static Window
|
|
findDestination(WMDraggingInfo *info, WMPoint *mousePos)
|
|
{
|
|
WMScreen *scr = sourceScreen(info);
|
|
unsigned nchildren;
|
|
Window *children = windowChildren(scr->display, scr->rootWin, &nchildren);
|
|
int i;
|
|
XWindowAttributes attr;
|
|
|
|
if (isXdndAware(scr, scr->rootWin))
|
|
return scr->rootWin;
|
|
|
|
/* exclude drag icon (and upper) from search */
|
|
for (i = nchildren-1; i >= 0; i--) {
|
|
if (children[i] == XDND_DRAG_ICON(info)) {
|
|
i--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < 0) {
|
|
/* root window has no child under drag icon, and is not xdnd aware. */
|
|
return None;
|
|
}
|
|
|
|
/* inspecting children, from upper to lower */
|
|
for (; i >= 0; i--) {
|
|
if (XGetWindowAttributes(scr->display, children[i], &attr)
|
|
&& attr.map_state == IsViewable
|
|
&& mousePos->x >= attr.x
|
|
&& mousePos->y >= attr.y
|
|
&& mousePos->x < attr.x + attr.width
|
|
&& mousePos->y < attr.y + attr.height) {
|
|
return lookForAwareWindow(scr, mousePos, children[i]);
|
|
}
|
|
}
|
|
|
|
/* No child window under drag pointer */
|
|
return None;
|
|
}
|
|
|
|
|
|
static void
|
|
initMotionProcess(WMView *view, WMDraggingInfo *info,
|
|
XEvent *event, WMPoint *startLocation)
|
|
{
|
|
WMScreen *scr = W_VIEW_SCREEN(view);
|
|
|
|
/* take ownership of XdndSelection */
|
|
XDND_SELECTION_PROCS(info) =
|
|
(WMSelectionProcs*) wmalloc(sizeof(WMSelectionProcs));
|
|
XDND_SELECTION_PROCS(info)->convertSelection = convertSelection;
|
|
XDND_SELECTION_PROCS(info)->selectionLost = selectionLost;
|
|
XDND_SELECTION_PROCS(info)->selectionDone = selectionDone;
|
|
XDND_TIMESTAMP(info) = event->xmotion.time;
|
|
|
|
if (!WMCreateSelectionHandler(view, scr->xdndSelectionAtom,
|
|
CurrentTime,
|
|
XDND_SELECTION_PROCS(info), NULL)) {
|
|
wwarning("could not get ownership or DND selection");
|
|
return;
|
|
}
|
|
|
|
registerDropTypes(scr, view, info);
|
|
|
|
if (XDND_SOURCE_ACTION(info) == W_VIEW_SCREEN(view)->xdndActionAsk)
|
|
registerSupportedOperations(view);
|
|
|
|
if (view->dragSourceProcs->beganDrag != NULL) {
|
|
view->dragSourceProcs->beganDrag(view, startLocation);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
processMotion(WMDraggingInfo *info, Window windowUnderDrag, WMPoint *mousePos)
|
|
{
|
|
/* // WMScreen *scr = sourceScreen(info); */
|
|
Window newDestination = findDestination(info, mousePos);
|
|
|
|
W_DragSourceStopTimer();
|
|
|
|
if (newDestination != XDND_DEST_WIN(info)) {
|
|
recolorCursor(info, False);
|
|
|
|
if (XDND_DEST_WIN(info) != None) {
|
|
/* leaving a xdnd window */
|
|
sendLeaveMessage(info);
|
|
}
|
|
|
|
XDND_DEST_WIN(info) = newDestination;
|
|
XDND_SOURCE_STATE(info) = idleState;
|
|
XDND_DEST_ACTION(info) = None;
|
|
XDND_NO_POS_ZONE(info).size.width = 0;
|
|
XDND_NO_POS_ZONE(info).size.height = 0;
|
|
|
|
if (newDestination != None) {
|
|
/* entering a xdnd window */
|
|
if (! sendEnterMessage(info)) {
|
|
XDND_DEST_WIN(info) = None;
|
|
return;
|
|
}
|
|
|
|
W_DragSourceStartTimer(info);
|
|
}
|
|
} else {
|
|
if (XDND_DEST_WIN(info) != None) {
|
|
if (! sendPositionMessage(info, mousePos)) {
|
|
XDND_DEST_WIN(info) = None;
|
|
return;
|
|
}
|
|
|
|
W_DragSourceStartTimer(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static Bool
|
|
processButtonRelease(WMDraggingInfo *info)
|
|
{
|
|
if (XDND_SOURCE_STATE(info) == dropAllowedState) {
|
|
/* begin drop */
|
|
W_DragSourceStopTimer();
|
|
|
|
if (! sendDropMessage(info))
|
|
return False;
|
|
|
|
W_DragSourceStartTimer(info);
|
|
return True;
|
|
} else {
|
|
if (XDND_DEST_WIN(info) != None)
|
|
sendLeaveMessage(info);
|
|
|
|
W_DragSourceStopTimer();
|
|
return False;
|
|
}
|
|
}
|
|
|
|
|
|
Bool
|
|
WMIsDraggingFromView(WMView *view)
|
|
{
|
|
WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
|
|
|
|
return ( XDND_SOURCE_INFO(info) != NULL
|
|
&& XDND_SOURCE_STATE(info) != finishDropState);
|
|
/* return W_VIEW_SCREEN(view)->dragInfo.sourceInfo != NULL; */
|
|
}
|
|
|
|
|
|
void
|
|
WMDragImageFromView(WMView *view, XEvent *event)
|
|
{
|
|
WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
|
|
WMPoint mouseLocation;
|
|
|
|
switch(event->type) {
|
|
case ButtonPress:
|
|
if (event->xbutton.button == Button1) {
|
|
XEvent nextEvent;
|
|
|
|
XPeekEvent(event->xbutton.display, &nextEvent);
|
|
|
|
/* Initialize only if a drag really begins (avoid clicks) */
|
|
if (nextEvent.type == MotionNotify) {
|
|
initSourceDragInfo(view, info);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
if (WMIsDraggingFromView(view)) {
|
|
Bool dropBegan = processButtonRelease(info);
|
|
|
|
recolorCursor(info, False);
|
|
if (dropBegan) {
|
|
endDragImage(info, False);
|
|
XDND_SOURCE_STATE(info) = finishDropState;
|
|
} else {
|
|
/* drop failed */
|
|
endDragImage(info, True);
|
|
endDragProcess(info,False);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MotionNotify:
|
|
if (WMIsDraggingFromView(view)) {
|
|
mouseLocation = wmkpoint(event->xmotion.x_root,
|
|
event->xmotion.y_root);
|
|
|
|
if (abs(XDND_DRAG_ICON_POS(info).x - mouseLocation.x) >=
|
|
MIN_X_MOVE_OFFSET
|
|
|| abs(XDND_DRAG_ICON_POS(info).y - mouseLocation.y) >=
|
|
MIN_Y_MOVE_OFFSET) {
|
|
if (XDND_DRAG_ICON(info) == None) {
|
|
initMotionProcess(view, info, event, &mouseLocation);
|
|
startDragImage(view, info, event);
|
|
} else {
|
|
XDND_DRAG_ICON_POS(info).x =
|
|
mouseLocation.x - XDND_MOUSE_OFFSET(info).x;
|
|
XDND_DRAG_ICON_POS(info).y =
|
|
mouseLocation.y - XDND_MOUSE_OFFSET(info).y;
|
|
|
|
refreshDragImage(view, info);
|
|
processMotion(info,
|
|
event->xmotion.window,
|
|
&mouseLocation);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Minimal mouse events handler: no right or double-click detection,
|
|
only drag is supported */
|
|
static void
|
|
dragImageHandler(XEvent *event, void *cdata)
|
|
{
|
|
WMView *view = (WMView*)cdata;
|
|
|
|
WMDragImageFromView(view, event);
|
|
}
|
|
|
|
|
|
/* ----- source states ----- */
|
|
|
|
#ifdef XDND_DEBUG
|
|
static void
|
|
traceStatusMsg(Display *dpy, XClientMessageEvent *statusEvent)
|
|
{
|
|
printf("Xdnd status message:\n");
|
|
|
|
if (statusEvent->data.l[1] & 0x2UL)
|
|
printf("send position on every move\n");
|
|
else {
|
|
int x, y, w, h;
|
|
x = statusEvent->data.l[2] >> 16;
|
|
y = statusEvent->data.l[2] & 0xFFFFL;
|
|
w = statusEvent->data.l[3] >> 16;
|
|
h = statusEvent->data.l[3] & 0xFFFFL;
|
|
|
|
printf("send position out of ((%d,%d) , (%d,%d))\n",
|
|
x, y, x+w, y+h);
|
|
}
|
|
|
|
if (statusEvent->data.l[1] & 0x1L)
|
|
printf("allowed action: %s\n",
|
|
XGetAtomName(dpy, statusEvent->data.l[4]));
|
|
else
|
|
printf("no action allowed\n");
|
|
}
|
|
#endif
|
|
|
|
|
|
static void
|
|
storeDropAction(WMDraggingInfo *info, Atom destAction)
|
|
{
|
|
WMView* sourceView = XDND_SOURCE_VIEW(info);
|
|
WMScreen *scr = W_VIEW_SCREEN(sourceView);
|
|
|
|
if (sourceView->dragSourceProcs->acceptDropOperation != NULL) {
|
|
if (sourceView->dragSourceProcs->acceptDropOperation(
|
|
sourceView,
|
|
W_ActionToOperation(scr, destAction)))
|
|
XDND_DEST_ACTION(info) = destAction;
|
|
else
|
|
XDND_DEST_ACTION(info) = None;
|
|
} else {
|
|
XDND_DEST_ACTION(info) = destAction;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
storeStatusMessageInfos(WMDraggingInfo *info, XClientMessageEvent *statusEvent)
|
|
{
|
|
WMRect* noPosZone = &(XDND_NO_POS_ZONE(info));
|
|
|
|
#ifdef XDND_DEBUG
|
|
|
|
traceStatusMsg(sourceScreen(info)->display, statusEvent);
|
|
#endif
|
|
|
|
if (statusEvent->data.l[1] & 0x2UL) {
|
|
/* bit 1 set: destination wants position messages on every move */
|
|
noPosZone->size.width = 0;
|
|
noPosZone->size.height = 0;
|
|
} else {
|
|
/* don't send another position message while in given rectangle */
|
|
noPosZone->pos.x = statusEvent->data.l[2] >> 16;
|
|
noPosZone->pos.y = statusEvent->data.l[2] & 0xFFFFL;
|
|
noPosZone->size.width = statusEvent->data.l[3] >> 16;
|
|
noPosZone->size.height = statusEvent->data.l[3] & 0xFFFFL;
|
|
}
|
|
|
|
if ((statusEvent->data.l[1] & 0x1L) || statusEvent->data.l[4] != None) {
|
|
/* destination accept drop */
|
|
storeDropAction(info, statusEvent->data.l[4]);
|
|
} else {
|
|
XDND_DEST_ACTION(info) = None;
|
|
}
|
|
}
|
|
|
|
|
|
static void*
|
|
idleState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr;
|
|
Atom destMsg = event->message_type;
|
|
|
|
scr = W_VIEW_SCREEN(view);
|
|
|
|
if (destMsg == scr->xdndStatusAtom) {
|
|
storeStatusMessageInfos(info, event);
|
|
|
|
if (XDND_DEST_ACTION(info) != None) {
|
|
recolorCursor(info, True);
|
|
W_DragSourceStartTimer(info);
|
|
return dropAllowedState;
|
|
} else {
|
|
/* drop denied */
|
|
recolorCursor(info, False);
|
|
return idleState;
|
|
}
|
|
}
|
|
|
|
if (destMsg == scr->xdndFinishedAtom) {
|
|
wwarning("received xdndFinishedAtom before drop began");
|
|
}
|
|
|
|
W_DragSourceStartTimer(info);
|
|
return idleState;
|
|
}
|
|
|
|
|
|
static void*
|
|
dropAllowedState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = W_VIEW_SCREEN(view);
|
|
Atom destMsg = event->message_type;
|
|
|
|
if (destMsg == scr->xdndStatusAtom) {
|
|
storeStatusMessageInfos(info, event);
|
|
|
|
if (XDND_DEST_ACTION(info) == None) {
|
|
/* drop denied */
|
|
recolorCursor(info, False);
|
|
return idleState;
|
|
}
|
|
}
|
|
|
|
W_DragSourceStartTimer(info);
|
|
return dropAllowedState;
|
|
}
|
|
|
|
|
|
static void*
|
|
finishDropState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
|
|
{
|
|
WMScreen *scr = W_VIEW_SCREEN(view);
|
|
Atom destMsg = event->message_type;
|
|
|
|
if (destMsg == scr->xdndFinishedAtom) {
|
|
endDragProcess(info, True);
|
|
return NULL;
|
|
}
|
|
|
|
W_DragSourceStartTimer(info);
|
|
return finishDropState;
|
|
}
|
|
/* ----- end of source states ----- */
|
|
|
|
|
|
/* ----- source timer ----- */
|
|
static void
|
|
dragSourceResponseTimeOut(void *source)
|
|
{
|
|
WMView *view = (WMView*)source;
|
|
WMDraggingInfo *info = &(W_VIEW_SCREEN(view)->dragInfo);
|
|
|
|
wwarning("delay for drag destination response expired");
|
|
sendLeaveMessage(info);
|
|
|
|
recolorCursor(info, False);
|
|
if (XDND_SOURCE_STATE(info) == finishDropState) {
|
|
/* drop failed */
|
|
endDragImage(info, True);
|
|
endDragProcess(info, False);
|
|
} else {
|
|
XDND_SOURCE_STATE(info) = idleState;
|
|
}
|
|
}
|
|
|
|
void
|
|
W_DragSourceStopTimer()
|
|
{
|
|
if (dndSourceTimer != NULL) {
|
|
WMDeleteTimerHandler(dndSourceTimer);
|
|
dndSourceTimer = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
W_DragSourceStartTimer(WMDraggingInfo *info)
|
|
{
|
|
W_DragSourceStopTimer();
|
|
|
|
dndSourceTimer = WMAddTimerHandler(
|
|
XDND_DESTINATION_RESPONSE_MAX_DELAY,
|
|
dragSourceResponseTimeOut,
|
|
XDND_SOURCE_VIEW(info));
|
|
}
|
|
|
|
/* ----- End of Destination timer ----- */
|
|
|
|
|
|
void
|
|
W_DragSourceStateHandler(WMDraggingInfo *info, XClientMessageEvent *event)
|
|
{
|
|
WMView *view;
|
|
W_DndState* newState;
|
|
|
|
if (XDND_SOURCE_VIEW_STORED(info)) {
|
|
view = XDND_SOURCE_VIEW(info);
|
|
#ifdef XDND_DEBUG
|
|
|
|
printf("current source state: %s\n",
|
|
stateName(XDND_SOURCE_STATE(info)));
|
|
#endif
|
|
|
|
newState = (W_DndState*) XDND_SOURCE_STATE(info)(view, event, info);
|
|
|
|
#ifdef XDND_DEBUG
|
|
|
|
printf("new source state: %s\n", stateName(newState));
|
|
#endif
|
|
|
|
if (newState != NULL)
|
|
XDND_SOURCE_STATE(info) = newState;
|
|
/* else drop finished, and info has been flushed */
|
|
}
|
|
}
|
|
|
|
|
|
void WMSetViewDragImage(WMView* view, WMPixmap *dragImage)
|
|
{
|
|
if (view->dragImage != NULL)
|
|
WMReleasePixmap(view->dragImage);
|
|
|
|
view->dragImage = WMRetainPixmap(dragImage);
|
|
}
|
|
|
|
|
|
void WMReleaseViewDragImage(WMView* view)
|
|
{
|
|
if (view->dragImage != NULL)
|
|
WMReleasePixmap(view->dragImage);
|
|
}
|
|
|
|
|
|
/* Create a drag handler, associating drag event masks with dragEventProc */
|
|
void
|
|
WMCreateDragHandler(WMView *view, WMEventProc *dragEventProc, void *clientData)
|
|
{
|
|
WMCreateEventHandler(view,
|
|
ButtonPressMask|ButtonReleaseMask|Button1MotionMask,
|
|
dragEventProc, clientData);
|
|
}
|
|
|
|
|
|
void
|
|
WMDeleteDragHandler(WMView *view, WMEventProc *dragEventProc, void *clientData)
|
|
{
|
|
WMDeleteEventHandler(view,
|
|
ButtonPressMask|ButtonReleaseMask|Button1MotionMask,
|
|
dragEventProc, clientData);
|
|
}
|
|
|
|
|
|
/* set default drag handler for view */
|
|
void
|
|
WMSetViewDraggable(WMView *view, WMDragSourceProcs *dragSourceProcs,
|
|
WMPixmap *dragImage)
|
|
{
|
|
wassertr(dragImage != NULL);
|
|
view->dragImage = WMRetainPixmap(dragImage);
|
|
|
|
WMSetViewDragSourceProcs(view, dragSourceProcs);
|
|
|
|
WMCreateDragHandler(view, dragImageHandler, view);
|
|
}
|
|
|
|
|
|
void
|
|
WMUnsetViewDraggable(WMView *view)
|
|
{
|
|
if (view->dragSourceProcs) {
|
|
wfree(view->dragSourceProcs);
|
|
view->dragSourceProcs = NULL;
|
|
}
|
|
|
|
WMReleaseViewDragImage(view);
|
|
|
|
WMDeleteDragHandler(view, dragImageHandler, view);
|
|
}
|
|
|
|
|