Fix periodic focus bug
[wmaker-crm.git] / WINGs / dragsource.c
blob04baa23b5874c7b29d2be40455971d542d23b2ac
3 #include "wconfig.h"
4 #include "WINGsP.h"
6 #include <X11/Xatom.h>
7 #include <X11/cursorfont.h>
8 #ifdef SHAPE
9 #include <X11/extensions/shape.h>
10 #endif
14 #define XDND_DESTINATION_RESPONSE_MAX_DELAY 10000
15 #define MIN_X_MOVE_OFFSET 5
16 #define MIN_Y_MOVE_OFFSET 5
17 #define MAX_SLIDEBACK_ITER 15
19 #define XDND_PROPERTY_FORMAT 32
20 #define XDND_ACTION_DESCRIPTION_FORMAT 8
22 #define XDND_DEST_VERSION(dragInfo) dragInfo->protocolVersion
23 #define XDND_SOURCE_INFO(dragInfo) dragInfo->sourceInfo
24 #define XDND_DEST_WIN(dragInfo) dragInfo->sourceInfo->destinationWindow
25 #define XDND_SOURCE_ACTION(dragInfo) dragInfo->sourceAction
26 #define XDND_DEST_ACTION(dragInfo) dragInfo->destinationAction
27 #define XDND_SOURCE_VIEW(dragInfo) dragInfo->sourceInfo->sourceView
28 #define XDND_SOURCE_STATE(dragInfo) dragInfo->sourceInfo->state
29 #define XDND_SELECTION_PROCS(dragInfo) dragInfo->sourceInfo->selectionProcs
30 #define XDND_DRAG_ICON(dragInfo) dragInfo->sourceInfo->icon
31 #define XDND_MOUSE_OFFSET(dragInfo) dragInfo->sourceInfo->mouseOffset
32 #define XDND_DRAG_CURSOR(dragInfo) dragInfo->sourceInfo->dragCursor
33 #define XDND_DRAG_ICON_POS(dragInfo) dragInfo->sourceInfo->imageLocation
34 #define XDND_NO_POS_ZONE(dragInfo) dragInfo->sourceInfo->noPositionMessageZone
35 #define XDND_TIMESTAMP(dragInfo) dragInfo->timestamp
36 #define XDND_3_TYPES(dragInfo) dragInfo->sourceInfo->firstThreeTypes
37 #define XDND_SOURCE_VIEW_STORED(dragInfo) dragInfo->sourceInfo != NULL \
38 && dragInfo->sourceInfo->sourceView != NULL
41 static WMHandlerID dndSourceTimer = NULL;
44 static void* idleState(WMView *srcView, XClientMessageEvent *event,
45 WMDraggingInfo *info);
46 static void* dropAllowedState(WMView *srcView, XClientMessageEvent *event,
47 WMDraggingInfo *info);
48 static void* finishDropState(WMView *srcView, XClientMessageEvent *event,
49 WMDraggingInfo *info);
51 #ifdef XDND_DEBUG
52 static const char*
53 stateName(W_DndState *state)
55 if (state == NULL)
56 return "no state defined";
58 if (state == idleState)
59 return "idleState";
61 if (state == dropAllowedState)
62 return "dropAllowedState";
64 if (state == finishDropState)
65 return "finishDropState";
67 return "unknown state";
69 #endif
72 static WMScreen*
73 sourceScreen(WMDraggingInfo *info)
75 return W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
79 static void
80 endDragProcess(WMDraggingInfo *info, Bool deposited)
82 WMView *view = XDND_SOURCE_VIEW(info);
83 WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
85 /* free selection handler while view exists */
86 WMDeleteSelectionHandler(view,
87 scr->xdndSelectionAtom,
88 CurrentTime);
89 wfree(XDND_SELECTION_PROCS(info));
91 if (XDND_DRAG_CURSOR(info) != None) {
92 XFreeCursor(scr->display,
93 XDND_DRAG_CURSOR(info));
94 XDND_DRAG_CURSOR(info) = None;
97 if (view->dragSourceProcs->endedDrag != NULL) {
98 /* this can destroy source view (with a "move" action for example) */
99 view->dragSourceProcs->endedDrag(view, &XDND_DRAG_ICON_POS(info),
100 deposited);
103 /* clear remaining draggging infos */
104 wfree(XDND_SOURCE_INFO(info));
105 XDND_SOURCE_INFO(info) = NULL;
109 /* ----- drag cursor ----- */
110 static void
111 initDragCursor(WMDraggingInfo *info)
113 WMScreen *scr = sourceScreen(info);
114 XColor cursorFgColor, cursorBgColor;
116 /* green */
117 cursorFgColor.red = 0x4500;
118 cursorFgColor.green = 0xb000;
119 cursorFgColor.blue = 0x4500;
121 /* white */
122 cursorBgColor.red = 0xffff;
123 cursorBgColor.green = 0xffff;
124 cursorBgColor.blue = 0xffff;
126 XDND_DRAG_CURSOR(info) = XCreateFontCursor(scr->display, XC_left_ptr);
127 XRecolorCursor(scr->display,
128 XDND_DRAG_CURSOR(info),
129 &cursorFgColor,
130 &cursorBgColor);
132 XFlush(scr->display);
135 static void
136 recolorCursor(WMDraggingInfo *info, Bool dropIsAllowed)
138 WMScreen *scr = sourceScreen(info);
140 if (dropIsAllowed) {
141 XDefineCursor(scr->display,
142 scr->rootWin,
143 XDND_DRAG_CURSOR(info));
144 } else {
145 XDefineCursor(scr->display,
146 scr->rootWin,
147 scr->defaultCursor);
150 XFlush(scr->display);
152 /* ----- end of drag cursor ----- */
155 /* ----- selection procs ----- */
156 static WMData*
157 convertSelection(WMView *view, Atom selection, Atom target,
158 void *cdata, Atom *type)
160 WMScreen *scr;
161 WMData *data;
162 char *typeName;
164 scr = W_VIEW_SCREEN(view);
165 typeName = XGetAtomName(scr->display, target);
167 *type = target;
169 if (view->dragSourceProcs->fetchDragData != NULL) {
170 data = view->dragSourceProcs->fetchDragData(
171 view,
172 typeName);
173 } else {
174 data = NULL;
177 if (typeName != NULL)
178 XFree(typeName);
180 return data;
184 static void
185 selectionLost(WMView *view, Atom selection, void *cdata)
187 wwarning("DND selection lost during drag operation...");
191 static void
192 selectionDone(WMView *view, Atom selection, Atom target, void *cdata)
194 #ifdef XDND_DEBUG
195 printf("selection done\n");
196 #endif
198 /* ----- end of selection procs ----- */
201 /* ----- visual part ----- */
203 static Window
204 makeDragIcon(WMScreen *scr, WMPixmap *pixmap)
206 Window window;
207 WMSize size;
208 unsigned long flags;
209 XSetWindowAttributes attribs;
210 Pixmap pix, mask;
212 if (!pixmap) {
213 pixmap = scr->defaultObjectIcon;
216 size = WMGetPixmapSize(pixmap);
217 pix = pixmap->pixmap;
218 mask = pixmap->mask;
220 flags = CWSaveUnder|CWBackPixmap|CWOverrideRedirect|CWColormap;
221 attribs.save_under = True;
222 attribs.background_pixmap = pix;
223 attribs.override_redirect = True;
224 attribs.colormap = scr->colormap;
226 window = XCreateWindow(scr->display, scr->rootWin, 0, 0, size.width,
227 size.height, 0, scr->depth, InputOutput,
228 scr->visual, flags, &attribs);
230 #ifdef SHAPE
232 if (mask) {
233 XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, mask,
234 ShapeSet);
236 #endif
238 return window;
242 static void
243 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
245 double x, y, dx, dy;
246 int i;
247 int iterations;
249 iterations = WMIN(MAX_SLIDEBACK_ITER,
250 WMAX(abs(dstX-srcX), abs(dstY-srcY)));
252 x = srcX;
253 y = srcY;
255 dx = (double)(dstX-srcX)/iterations;
256 dy = (double)(dstY-srcY)/iterations;
258 for (i = 0; i <= iterations; i++) {
259 XMoveWindow(dpy, win, x, y);
260 XFlush(dpy);
262 wusleep(800);
264 x += dx;
265 y += dy;
270 static int
271 getInitialDragImageCoord(int viewCoord, int mouseCoord,
272 int viewSize, int iconSize)
274 if (iconSize >= viewSize) {
275 /* center icon coord on view */
276 return viewCoord - iconSize/2;
277 } else {
278 /* try to center icon on mouse pos */
280 if (mouseCoord - iconSize/2 <= viewCoord)
281 /* if icon was centered on mouse, it would be off view
282 thus, put icon left (resp. top) side
283 at view (resp. top) side */
284 return viewCoord;
286 else if (mouseCoord + iconSize/2 >= viewCoord + viewSize)
287 /* if icon was centered on mouse, it would be off view
288 thus, put icon right (resp. bottom) side
289 at view right (resp. bottom) side */
290 return viewCoord + viewSize - iconSize;
292 else
293 return mouseCoord - iconSize/2;
298 static void
299 initDragImagePos(WMView *view, WMDraggingInfo *info, XEvent *event)
301 WMSize iconSize = WMGetPixmapSize(view->dragImage);
302 WMSize viewSize = WMGetViewSize(view);
303 WMPoint viewPos;
304 Window foo;
306 XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
307 WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
308 0, 0, &(viewPos.x), &(viewPos.y),
309 &foo);
311 /* set icon pos */
312 XDND_DRAG_ICON_POS(info).x =
313 getInitialDragImageCoord(viewPos.x, event->xmotion.x_root,
314 viewSize.width, iconSize.width);
316 XDND_DRAG_ICON_POS(info).y =
317 getInitialDragImageCoord(viewPos.y, event->xmotion.y_root,
318 viewSize.height, iconSize.height);
320 /* set mouse offset relative to icon */
321 XDND_MOUSE_OFFSET(info).x =
322 event->xmotion.x_root - XDND_DRAG_ICON_POS(info).x;
323 XDND_MOUSE_OFFSET(info).y =
324 event->xmotion.y_root - XDND_DRAG_ICON_POS(info).y;
328 static void
329 refreshDragImage(WMView *view, WMDraggingInfo *info)
331 WMScreen *scr = W_VIEW_SCREEN(view);
333 XMoveWindow(scr->display, XDND_DRAG_ICON(info),
334 XDND_DRAG_ICON_POS(info).x,
335 XDND_DRAG_ICON_POS(info).y);
339 static void
340 startDragImage(WMView *view, WMDraggingInfo *info, XEvent* event)
342 WMScreen *scr = W_VIEW_SCREEN(view);
344 XDND_DRAG_ICON(info) = makeDragIcon(scr, view->dragImage);
345 initDragImagePos(view, info, event);
346 refreshDragImage(view, info);
347 XMapRaised(scr->display, XDND_DRAG_ICON(info));
349 initDragCursor(info);
353 static void
354 endDragImage(WMDraggingInfo *info, Bool slideBack)
356 WMView *view = XDND_SOURCE_VIEW(info);
357 Display *dpy = W_VIEW_SCREEN(view)->display;
359 if (slideBack) {
360 WMPoint toLocation;
361 Window foo;
363 XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
364 WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
365 0, 0, &(toLocation.x), &(toLocation.y),
366 &foo);
368 slideWindow(dpy, XDND_DRAG_ICON(info),
369 XDND_DRAG_ICON_POS(info).x,
370 XDND_DRAG_ICON_POS(info).y,
371 toLocation.x,
372 toLocation.y);
375 XDestroyWindow(dpy, XDND_DRAG_ICON(info));
378 /* ----- end of visual part ----- */
381 /* ----- messages ----- */
383 /* send a DnD message to the destination window */
384 static Bool
385 sendDnDClientMessage(WMDraggingInfo *info, Atom message,
386 unsigned long data1,
387 unsigned long data2,
388 unsigned long data3,
389 unsigned long data4)
391 Display *dpy = sourceScreen(info)->display;
392 Window srcWin = WMViewXID(XDND_SOURCE_VIEW(info));
393 Window destWin = XDND_DEST_WIN(info);
395 if (! W_SendDnDClientMessage(dpy,
396 destWin,
397 message,
398 srcWin,
399 data1,
400 data2,
401 data3,
402 data4)) {
403 /* drop failed */
404 recolorCursor(info, False);
405 endDragImage(info, True);
406 endDragProcess(info, False);
407 return False;
410 return True;
414 static Bool
415 sendEnterMessage(WMDraggingInfo *info)
417 WMScreen *scr = sourceScreen(info);
418 unsigned long version;
420 if (XDND_DEST_VERSION(info) > 2) {
421 if (XDND_DEST_VERSION(info) < XDND_VERSION)
422 version = XDND_DEST_VERSION(info);
423 else
424 version = XDND_VERSION;
425 } else {
426 version = 3;
429 return sendDnDClientMessage(info, scr->xdndEnterAtom,
430 (version << 24) | 1, /* 1: support of type list */
431 XDND_3_TYPES(info)[0],
432 XDND_3_TYPES(info)[1],
433 XDND_3_TYPES(info)[2]);
437 static Bool
438 sendPositionMessage(WMDraggingInfo *info, WMPoint *mousePos)
440 WMScreen *scr = sourceScreen(info);
441 WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));
443 if (noPosZone->size.width != 0 || noPosZone->size.height != 0) {
444 if (mousePos->x < noPosZone->pos.x
445 || mousePos->x > (noPosZone->pos.x + noPosZone->size.width)
446 || mousePos->y < noPosZone->pos.y
447 || mousePos->y > (noPosZone->pos.y + noPosZone->size.height)) {
448 /* send position if out of zone defined by destination */
449 return sendDnDClientMessage(info, scr->xdndPositionAtom,
451 mousePos->x<<16|mousePos->y,
452 XDND_TIMESTAMP(info),
453 XDND_SOURCE_ACTION(info));
456 /* Nothing to send, always succeed */
457 return True;
461 /* send position on each move */
462 return sendDnDClientMessage(info, scr->xdndPositionAtom,
464 mousePos->x<<16|mousePos->y,
465 XDND_TIMESTAMP(info),
466 XDND_SOURCE_ACTION(info));
470 static Bool
471 sendLeaveMessage(WMDraggingInfo *info)
473 WMScreen *scr = sourceScreen(info);
475 return sendDnDClientMessage(info, scr->xdndLeaveAtom,
476 0, 0, 0, 0);
480 static Bool
481 sendDropMessage(WMDraggingInfo *info)
483 WMScreen *scr = sourceScreen(info);
485 return sendDnDClientMessage(info,
486 scr->xdndDropAtom,
488 XDND_TIMESTAMP(info),
489 0, 0);
492 /* ----- end of messages ----- */
495 static Atom*
496 getTypeAtomList(WMScreen *scr, WMView *view, int* count)
498 WMArray* types;
499 Atom* typeAtoms;
500 int i;
502 types = view->dragSourceProcs->dropDataTypes(view);
504 if (types != NULL) {
505 *count = WMGetArrayItemCount(types);
506 if (*count > 0) {
507 typeAtoms = wmalloc((*count)*sizeof(Atom));
508 for (i=0; i < *count; i++) {
509 typeAtoms[i] = XInternAtom(scr->display,
510 WMGetFromArray(types, i),
511 False);
514 /* WMFreeArray(types); */
515 return typeAtoms;
518 /* WMFreeArray(types); */
521 *count = 1;
522 typeAtoms = wmalloc(sizeof(Atom));
523 *typeAtoms = None;
525 return typeAtoms;
529 static void
530 registerDropTypes(WMScreen *scr, WMView *view, WMDraggingInfo *info)
532 Atom* typeList;
533 int i, count;
535 typeList = getTypeAtomList(scr, view, &count);
537 /* store the first 3 types */
538 for(i=0; i < 3 && i < count; i++)
539 XDND_3_TYPES(info)[i] = typeList[i];
541 for(; i < 3; i++)
542 XDND_3_TYPES(info)[i] = None;
545 /* store the entire type list */
546 XChangeProperty(scr->display,
547 WMViewXID(view),
548 scr->xdndTypeListAtom,
549 XA_ATOM,
550 XDND_PROPERTY_FORMAT,
551 PropModeReplace,
552 (unsigned char*) typeList,
553 count);
557 static void
558 registerOperationList(WMScreen *scr, WMView *view, WMArray* operationArray)
560 Atom* actionList;
561 WMDragOperationType operation;
562 int count = WMGetArrayItemCount(operationArray);
563 int i;
565 actionList = wmalloc(sizeof(Atom)*count);
567 for(i=0; i < count; i++) {
568 operation = WMGetDragOperationItemType(
569 WMGetFromArray(operationArray, i));
570 actionList[i] = W_OperationToAction(scr, operation);
573 XChangeProperty(scr->display,
574 WMViewXID(view),
575 scr->xdndActionListAtom,
576 XA_ATOM,
577 XDND_PROPERTY_FORMAT,
578 PropModeReplace,
579 (unsigned char*) actionList,
580 count);
583 static void
584 registerDescriptionList(WMScreen *scr, WMView *view, WMArray* operationArray)
586 char *text, *textListItem, *textList;
587 int count = WMGetArrayItemCount(operationArray);
588 int i;
589 int size = 0;
591 /* size of XA_STRING info */
592 for(i=0; i < count; i++) {
593 size += strlen(WMGetDragOperationItemText(
594 WMGetFromArray(operationArray, i))) + 1; /* +1 = +NULL */
597 /* create text list */
598 textList = wmalloc(size);
599 textListItem = textList;
601 for(i=0; i < count; i++) {
602 text = WMGetDragOperationItemText(WMGetFromArray(operationArray, i));
603 strcpy(textListItem, text);
605 /* to next text offset */
606 textListItem = &(textListItem[strlen(textListItem) + 1]);
609 XChangeProperty(scr->display,
610 WMViewXID(view),
611 scr->xdndActionDescriptionAtom,
612 XA_STRING,
613 XDND_ACTION_DESCRIPTION_FORMAT,
614 PropModeReplace,
615 (unsigned char*) textList,
616 size);
619 /* called if wanted operation is WDOperationAsk */
620 static void
621 registerSupportedOperations(WMView *view)
623 WMScreen *scr = W_VIEW_SCREEN(view);
624 WMArray* operationArray;
626 operationArray = view->dragSourceProcs->askedOperations(view);
628 registerOperationList(scr, view, operationArray);
629 registerDescriptionList(scr, view, operationArray);
631 /* WMFreeArray(operationArray); */
635 static void
636 initSourceDragInfo(WMView *sourceView, WMDraggingInfo *info)
638 WMRect emptyZone;
640 XDND_SOURCE_INFO(info) = (W_DragSourceInfo*) wmalloc(sizeof(W_DragSourceInfo));
642 XDND_SOURCE_VIEW(info) = sourceView;
643 XDND_DEST_WIN(info) = None;
644 XDND_DRAG_ICON(info) = None;
646 XDND_SOURCE_ACTION(info) = W_OperationToAction(
647 W_VIEW_SCREEN(sourceView),
648 sourceView->dragSourceProcs->wantedDropOperation(sourceView));
650 XDND_DEST_ACTION(info) = None;
652 XDND_SOURCE_STATE(info) = idleState;
654 emptyZone.pos = wmkpoint(0, 0);
655 emptyZone.size = wmksize(0, 0);
656 XDND_NO_POS_ZONE(info) = emptyZone;
661 Returned array is destroyed after dropDataTypes call
663 static WMArray*
664 defDropDataTypes(WMView *self)
666 return NULL;
670 static WMDragOperationType
671 defWantedDropOperation(WMView *self)
673 return WDOperationNone;
678 Must be defined if wantedDropOperation return WDOperationAsk
679 (useless otherwise).
680 Return a WMDragOperationItem array (destroyed after call).
681 A WMDragOperationItem links a label to an operation.
682 static WMArray*
683 defAskedOperations(WMView *self); */
686 static Bool
687 defAcceptDropOperation(WMView *self, WMDragOperationType allowedOperation)
689 return False;
693 static void
694 defBeganDrag(WMView *self, WMPoint *point)
699 static void
700 defEndedDrag(WMView *self, WMPoint *point, Bool deposited)
706 Returned data is not destroyed
708 static WMData*
709 defFetchDragData(WMView *self, char *type)
711 return NULL;
715 void
716 WMSetViewDragSourceProcs(WMView *view, WMDragSourceProcs *procs)
718 if (view->dragSourceProcs)
719 wfree(view->dragSourceProcs);
720 view->dragSourceProcs = wmalloc(sizeof(WMDragSourceProcs));
722 *view->dragSourceProcs = *procs;
724 if (procs->dropDataTypes == NULL)
725 view->dragSourceProcs->dropDataTypes = defDropDataTypes;
727 if (procs->wantedDropOperation == NULL)
728 view->dragSourceProcs->wantedDropOperation = defWantedDropOperation;
731 Note: askedOperations can be NULL, if wantedDropOperation never returns
732 WDOperationAsk.
735 if (procs->acceptDropOperation == NULL)
736 view->dragSourceProcs->acceptDropOperation = defAcceptDropOperation;
738 if (procs->beganDrag == NULL)
739 view->dragSourceProcs->beganDrag = defBeganDrag;
741 if (procs->endedDrag == NULL)
742 view->dragSourceProcs->endedDrag = defEndedDrag;
744 if (procs->fetchDragData == NULL)
745 view->dragSourceProcs->fetchDragData = defFetchDragData;
749 static Bool
750 isXdndAware(WMScreen *scr, Window win)
752 Atom type;
753 int format;
754 unsigned long count, remain;
755 unsigned char *winXdndVersion;
757 if (win == None)
758 return False;
760 XGetWindowProperty(scr->display, win, scr->xdndAwareAtom,
761 0, 1, False, XA_ATOM, &type, &format,
762 &count, &remain, &winXdndVersion);
764 if (type != XA_ATOM
765 || format != XDND_PROPERTY_FORMAT
766 || count == 0 || !winXdndVersion) {
767 if (winXdndVersion)
768 XFree(winXdndVersion);
769 return False;
772 XFree(winXdndVersion);
773 return (count == 1); /* xdnd version is set */
777 static Window*
778 windowChildren(Display *dpy, Window win, unsigned *nchildren)
780 Window *children;
781 Window foo, bar;
783 if (!XQueryTree(dpy, win, &foo, &bar, &children, nchildren)) {
784 *nchildren = 0;
785 return NULL;
786 } else
787 return children;
790 static Window
791 lookForAwareWindow(WMScreen *scr, WMPoint *mousePos, Window win)
793 int tmpx, tmpy;
794 Window child;
796 /* Since xdnd v3, only the toplevel window should be aware */
797 if (isXdndAware(scr, win))
798 return win;
800 /* inspect child under pointer */
801 if (XTranslateCoordinates(scr->display, scr->rootWin, win,
802 mousePos->x, mousePos->y, &tmpx, &tmpy,
803 &child)) {
804 if (child == None)
805 return None;
806 else
807 return lookForAwareWindow(scr, mousePos, child);
810 return None;
814 static Window
815 findDestination(WMDraggingInfo *info, WMPoint *mousePos)
817 WMScreen *scr = sourceScreen(info);
818 unsigned nchildren;
819 Window *children = windowChildren(scr->display, scr->rootWin, &nchildren);
820 int i;
821 XWindowAttributes attr;
823 if (isXdndAware(scr, scr->rootWin))
824 return scr->rootWin;
826 /* exclude drag icon (and upper) from search */
827 for (i = nchildren-1; i >= 0; i--) {
828 if (children[i] == XDND_DRAG_ICON(info)) {
829 i--;
830 break;
834 if (i < 0) {
835 /* root window has no child under drag icon, and is not xdnd aware. */
836 return None;
839 /* inspecting children, from upper to lower */
840 for (; i >= 0; i--) {
841 if (XGetWindowAttributes(scr->display, children[i], &attr)
842 && attr.map_state == IsViewable
843 && mousePos->x >= attr.x
844 && mousePos->y >= attr.y
845 && mousePos->x < attr.x + attr.width
846 && mousePos->y < attr.y + attr.height) {
847 return lookForAwareWindow(scr, mousePos, children[i]);
851 /* No child window under drag pointer */
852 return None;
856 static void
857 storeDestinationProtocolVersion(WMDraggingInfo *info)
859 Atom type;
860 int format;
861 unsigned long count, remain;
862 unsigned char *winXdndVersion;
863 WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
865 wassertr(XDND_DEST_WIN(info) != None);
867 if (XGetWindowProperty(scr->display, XDND_DEST_WIN(info),
868 scr->xdndAwareAtom,
869 0, 1, False, XA_ATOM, &type, &format,
870 &count, &remain, &winXdndVersion) == Success) {
871 XDND_DEST_VERSION(info) = *winXdndVersion;
872 XFree(winXdndVersion);
873 } else {
874 XDND_DEST_VERSION(info) = 0;
875 wwarning("failed to read XDND version of drop target");
880 static void
881 initMotionProcess(WMView *view, WMDraggingInfo *info,
882 XEvent *event, WMPoint *startLocation)
884 WMScreen *scr = W_VIEW_SCREEN(view);
886 /* take ownership of XdndSelection */
887 XDND_SELECTION_PROCS(info) =
888 (WMSelectionProcs*) wmalloc(sizeof(WMSelectionProcs));
889 XDND_SELECTION_PROCS(info)->convertSelection = convertSelection;
890 XDND_SELECTION_PROCS(info)->selectionLost = selectionLost;
891 XDND_SELECTION_PROCS(info)->selectionDone = selectionDone;
892 XDND_TIMESTAMP(info) = event->xmotion.time;
894 if (!WMCreateSelectionHandler(view, scr->xdndSelectionAtom,
895 CurrentTime,
896 XDND_SELECTION_PROCS(info), NULL)) {
897 wwarning("could not get ownership or DND selection");
898 return;
901 registerDropTypes(scr, view, info);
903 if (XDND_SOURCE_ACTION(info) == W_VIEW_SCREEN(view)->xdndActionAsk)
904 registerSupportedOperations(view);
906 if (view->dragSourceProcs->beganDrag != NULL) {
907 view->dragSourceProcs->beganDrag(view, startLocation);
912 static void
913 processMotion(WMDraggingInfo *info, WMPoint *mousePos)
915 Window newDestination = findDestination(info, mousePos);
917 W_DragSourceStopTimer();
919 if (newDestination != XDND_DEST_WIN(info)) {
920 recolorCursor(info, False);
922 if (XDND_DEST_WIN(info) != None) {
923 /* leaving a xdnd window */
924 sendLeaveMessage(info);
927 XDND_DEST_WIN(info) = newDestination;
928 XDND_DEST_ACTION(info) = None;
929 XDND_NO_POS_ZONE(info).size.width = 0;
930 XDND_NO_POS_ZONE(info).size.height = 0;
932 if (newDestination != None) {
933 /* entering a xdnd window */
934 XDND_SOURCE_STATE(info) = idleState;
935 storeDestinationProtocolVersion(info);
937 if (! sendEnterMessage(info)) {
938 XDND_DEST_WIN(info) = None;
939 return;
942 W_DragSourceStartTimer(info);
943 } else {
944 XDND_SOURCE_STATE(info) = NULL;
946 } else {
947 if (XDND_DEST_WIN(info) != None) {
948 if (! sendPositionMessage(info, mousePos)) {
949 XDND_DEST_WIN(info) = None;
950 return;
953 W_DragSourceStartTimer(info);
959 static Bool
960 processButtonRelease(WMDraggingInfo *info)
962 if (XDND_SOURCE_STATE(info) == dropAllowedState) {
963 /* begin drop */
964 W_DragSourceStopTimer();
966 if (! sendDropMessage(info))
967 return False;
969 W_DragSourceStartTimer(info);
970 return True;
971 } else {
972 if (XDND_DEST_WIN(info) != None)
973 sendLeaveMessage(info);
975 W_DragSourceStopTimer();
976 return False;
981 Bool
982 WMIsDraggingFromView(WMView *view)
984 WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
986 return ( XDND_SOURCE_INFO(info) != NULL
987 && XDND_SOURCE_STATE(info) != finishDropState);
988 /* return W_VIEW_SCREEN(view)->dragInfo.sourceInfo != NULL; */
992 void
993 WMDragImageFromView(WMView *view, XEvent *event)
995 WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
996 WMPoint mouseLocation;
998 switch(event->type) {
999 case ButtonPress:
1000 if (event->xbutton.button == Button1) {
1001 XEvent nextEvent;
1003 XPeekEvent(event->xbutton.display, &nextEvent);
1005 /* Initialize only if a drag really begins (avoid clicks) */
1006 if (nextEvent.type == MotionNotify) {
1007 initSourceDragInfo(view, info);
1011 break;
1013 case ButtonRelease:
1014 if (WMIsDraggingFromView(view)) {
1015 Bool dropBegan = processButtonRelease(info);
1017 recolorCursor(info, False);
1018 if (dropBegan) {
1019 endDragImage(info, False);
1020 XDND_SOURCE_STATE(info) = finishDropState;
1021 } else {
1022 /* drop failed */
1023 endDragImage(info, True);
1024 endDragProcess(info,False);
1027 break;
1029 case MotionNotify:
1030 if (WMIsDraggingFromView(view)) {
1031 mouseLocation = wmkpoint(event->xmotion.x_root,
1032 event->xmotion.y_root);
1034 if (abs(XDND_DRAG_ICON_POS(info).x - mouseLocation.x) >=
1035 MIN_X_MOVE_OFFSET
1036 || abs(XDND_DRAG_ICON_POS(info).y - mouseLocation.y) >=
1037 MIN_Y_MOVE_OFFSET) {
1038 if (XDND_DRAG_ICON(info) == None) {
1039 initMotionProcess(view, info, event, &mouseLocation);
1040 startDragImage(view, info, event);
1041 } else {
1042 XDND_DRAG_ICON_POS(info).x =
1043 mouseLocation.x - XDND_MOUSE_OFFSET(info).x;
1044 XDND_DRAG_ICON_POS(info).y =
1045 mouseLocation.y - XDND_MOUSE_OFFSET(info).y;
1047 refreshDragImage(view, info);
1048 processMotion(info, &mouseLocation);
1052 break;
1057 /* Minimal mouse events handler: no right or double-click detection,
1058 only drag is supported */
1059 static void
1060 dragImageHandler(XEvent *event, void *cdata)
1062 WMView *view = (WMView*)cdata;
1064 WMDragImageFromView(view, event);
1068 /* ----- source states ----- */
1070 #ifdef XDND_DEBUG
1071 static void
1072 traceStatusMsg(Display *dpy, XClientMessageEvent *statusEvent)
1074 printf("Xdnd status message:\n");
1076 if (statusEvent->data.l[1] & 0x2UL)
1077 printf("\tsend position on every move\n");
1078 else {
1079 int x, y, w, h;
1080 x = statusEvent->data.l[2] >> 16;
1081 y = statusEvent->data.l[2] & 0xFFFFL;
1082 w = statusEvent->data.l[3] >> 16;
1083 h = statusEvent->data.l[3] & 0xFFFFL;
1085 printf("\tsend position out of ((%d,%d) , (%d,%d))\n",
1086 x, y, x+w, y+h);
1089 if (statusEvent->data.l[1] & 0x1L)
1090 printf("\tallowed action: %s\n",
1091 XGetAtomName(dpy, statusEvent->data.l[4]));
1092 else
1093 printf("\tno action allowed\n");
1095 #endif
1098 static void
1099 storeDropAction(WMDraggingInfo *info, Atom destAction)
1101 WMView* sourceView = XDND_SOURCE_VIEW(info);
1102 WMScreen *scr = W_VIEW_SCREEN(sourceView);
1104 if (sourceView->dragSourceProcs->acceptDropOperation != NULL) {
1105 if (sourceView->dragSourceProcs->acceptDropOperation(
1106 sourceView,
1107 W_ActionToOperation(scr, destAction)))
1108 XDND_DEST_ACTION(info) = destAction;
1109 else
1110 XDND_DEST_ACTION(info) = None;
1111 } else {
1112 XDND_DEST_ACTION(info) = destAction;
1117 static void
1118 storeStatusMessageInfos(WMDraggingInfo *info, XClientMessageEvent *statusEvent)
1120 WMRect* noPosZone = &(XDND_NO_POS_ZONE(info));
1122 #ifdef XDND_DEBUG
1124 traceStatusMsg(sourceScreen(info)->display, statusEvent);
1125 #endif
1127 if (statusEvent->data.l[1] & 0x2UL) {
1128 /* bit 1 set: destination wants position messages on every move */
1129 noPosZone->size.width = 0;
1130 noPosZone->size.height = 0;
1131 } else {
1132 /* don't send another position message while in given rectangle */
1133 noPosZone->pos.x = statusEvent->data.l[2] >> 16;
1134 noPosZone->pos.y = statusEvent->data.l[2] & 0xFFFFL;
1135 noPosZone->size.width = statusEvent->data.l[3] >> 16;
1136 noPosZone->size.height = statusEvent->data.l[3] & 0xFFFFL;
1139 if ((statusEvent->data.l[1] & 0x1L) || statusEvent->data.l[4] != None) {
1140 /* destination accept drop */
1141 storeDropAction(info, statusEvent->data.l[4]);
1142 } else {
1143 XDND_DEST_ACTION(info) = None;
1148 static void*
1149 idleState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
1151 WMScreen *scr;
1152 Atom destMsg = event->message_type;
1154 scr = W_VIEW_SCREEN(view);
1156 if (destMsg == scr->xdndStatusAtom) {
1157 storeStatusMessageInfos(info, event);
1159 if (XDND_DEST_ACTION(info) != None) {
1160 recolorCursor(info, True);
1161 W_DragSourceStartTimer(info);
1162 return dropAllowedState;
1163 } else {
1164 /* drop denied */
1165 recolorCursor(info, False);
1166 return idleState;
1170 if (destMsg == scr->xdndFinishedAtom) {
1171 wwarning("received xdndFinishedAtom before drop began");
1174 W_DragSourceStartTimer(info);
1175 return idleState;
1179 static void*
1180 dropAllowedState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
1182 WMScreen *scr = W_VIEW_SCREEN(view);
1183 Atom destMsg = event->message_type;
1185 if (destMsg == scr->xdndStatusAtom) {
1186 storeStatusMessageInfos(info, event);
1188 if (XDND_DEST_ACTION(info) == None) {
1189 /* drop denied */
1190 recolorCursor(info, False);
1191 return idleState;
1195 W_DragSourceStartTimer(info);
1196 return dropAllowedState;
1200 static void*
1201 finishDropState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
1203 WMScreen *scr = W_VIEW_SCREEN(view);
1204 Atom destMsg = event->message_type;
1206 if (destMsg == scr->xdndFinishedAtom) {
1207 endDragProcess(info, True);
1208 return NULL;
1211 W_DragSourceStartTimer(info);
1212 return finishDropState;
1214 /* ----- end of source states ----- */
1217 /* ----- source timer ----- */
1218 static void
1219 dragSourceResponseTimeOut(void *source)
1221 WMView *view = (WMView*)source;
1222 WMDraggingInfo *info = &(W_VIEW_SCREEN(view)->dragInfo);
1224 wwarning("delay for drag destination response expired");
1225 sendLeaveMessage(info);
1227 recolorCursor(info, False);
1228 if (XDND_SOURCE_STATE(info) == finishDropState) {
1229 /* drop failed */
1230 endDragImage(info, True);
1231 endDragProcess(info, False);
1232 } else {
1233 XDND_SOURCE_STATE(info) = idleState;
1237 void
1238 W_DragSourceStopTimer()
1240 if (dndSourceTimer != NULL) {
1241 WMDeleteTimerHandler(dndSourceTimer);
1242 dndSourceTimer = NULL;
1246 void
1247 W_DragSourceStartTimer(WMDraggingInfo *info)
1249 W_DragSourceStopTimer();
1251 dndSourceTimer = WMAddTimerHandler(
1252 XDND_DESTINATION_RESPONSE_MAX_DELAY,
1253 dragSourceResponseTimeOut,
1254 XDND_SOURCE_VIEW(info));
1257 /* ----- End of Destination timer ----- */
1260 void
1261 W_DragSourceStateHandler(WMDraggingInfo *info, XClientMessageEvent *event)
1263 WMView *view;
1264 W_DndState* newState;
1266 if (XDND_SOURCE_VIEW_STORED(info)) {
1267 if (XDND_SOURCE_STATE(info) != NULL) {
1268 view = XDND_SOURCE_VIEW(info);
1269 #ifdef XDND_DEBUG
1271 printf("current source state: %s\n",
1272 stateName(XDND_SOURCE_STATE(info)));
1273 #endif
1275 newState = (W_DndState*) XDND_SOURCE_STATE(info)(view, event, info);
1277 #ifdef XDND_DEBUG
1279 printf("new source state: %s\n", stateName(newState));
1280 #endif
1282 if (newState != NULL)
1283 XDND_SOURCE_STATE(info) = newState;
1284 /* else drop finished, and info has been flushed */
1287 } else {
1288 wwarning("received DnD message without having a target");
1293 void WMSetViewDragImage(WMView* view, WMPixmap *dragImage)
1295 if (view->dragImage != NULL)
1296 WMReleasePixmap(view->dragImage);
1298 view->dragImage = WMRetainPixmap(dragImage);
1302 void WMReleaseViewDragImage(WMView* view)
1304 if (view->dragImage != NULL)
1305 WMReleasePixmap(view->dragImage);
1309 /* Create a drag handler, associating drag event masks with dragEventProc */
1310 void
1311 WMCreateDragHandler(WMView *view, WMEventProc *dragEventProc, void *clientData)
1313 WMCreateEventHandler(view,
1314 ButtonPressMask|ButtonReleaseMask|Button1MotionMask,
1315 dragEventProc, clientData);
1319 void
1320 WMDeleteDragHandler(WMView *view, WMEventProc *dragEventProc, void *clientData)
1322 WMDeleteEventHandler(view,
1323 ButtonPressMask|ButtonReleaseMask|Button1MotionMask,
1324 dragEventProc, clientData);
1328 /* set default drag handler for view */
1329 void
1330 WMSetViewDraggable(WMView *view, WMDragSourceProcs *dragSourceProcs,
1331 WMPixmap *dragImage)
1333 wassertr(dragImage != NULL);
1334 view->dragImage = WMRetainPixmap(dragImage);
1336 WMSetViewDragSourceProcs(view, dragSourceProcs);
1338 WMCreateDragHandler(view, dragImageHandler, view);
1342 void
1343 WMUnsetViewDraggable(WMView *view)
1345 if (view->dragSourceProcs) {
1346 wfree(view->dragSourceProcs);
1347 view->dragSourceProcs = NULL;
1350 WMReleaseViewDragImage(view);
1352 WMDeleteDragHandler(view, dragImageHandler, view);