Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / dragsource.c
1
2 #include "wconfig.h"
3 #include "WINGsP.h"
4
5 #include <X11/Xatom.h>
6 #include <X11/cursorfont.h>
7 #ifdef SHAPE
8 #include <X11/extensions/shape.h>
9 #endif
10
11 #define XDND_DESTINATION_RESPONSE_MAX_DELAY 10000
12 #define MIN_X_MOVE_OFFSET 5
13 #define MIN_Y_MOVE_OFFSET 5
14 #define MAX_SLIDEBACK_ITER 15
15
16 #define XDND_PROPERTY_FORMAT 32
17 #define XDND_ACTION_DESCRIPTION_FORMAT 8
18
19 #define XDND_DEST_VERSION(dragInfo) dragInfo->protocolVersion
20 #define XDND_SOURCE_INFO(dragInfo) dragInfo->sourceInfo
21 #define XDND_DEST_WIN(dragInfo) dragInfo->sourceInfo->destinationWindow
22 #define XDND_SOURCE_ACTION(dragInfo) dragInfo->sourceAction
23 #define XDND_DEST_ACTION(dragInfo) dragInfo->destinationAction
24 #define XDND_SOURCE_VIEW(dragInfo) dragInfo->sourceInfo->sourceView
25 #define XDND_SOURCE_STATE(dragInfo) dragInfo->sourceInfo->state
26 #define XDND_SELECTION_PROCS(dragInfo) dragInfo->sourceInfo->selectionProcs
27 #define XDND_DRAG_ICON(dragInfo) dragInfo->sourceInfo->icon
28 #define XDND_MOUSE_OFFSET(dragInfo) dragInfo->sourceInfo->mouseOffset
29 #define XDND_DRAG_CURSOR(dragInfo) dragInfo->sourceInfo->dragCursor
30 #define XDND_DRAG_ICON_POS(dragInfo) dragInfo->sourceInfo->imageLocation
31 #define XDND_NO_POS_ZONE(dragInfo) dragInfo->sourceInfo->noPositionMessageZone
32 #define XDND_TIMESTAMP(dragInfo) dragInfo->timestamp
33 #define XDND_3_TYPES(dragInfo) dragInfo->sourceInfo->firstThreeTypes
34 #define XDND_SOURCE_VIEW_STORED(dragInfo) dragInfo->sourceInfo != NULL \
35     && dragInfo->sourceInfo->sourceView != NULL
36
37 static WMHandlerID dndSourceTimer = NULL;
38
39 static void *idleState(WMView * srcView, XClientMessageEvent * event, WMDraggingInfo * info);
40 static void *dropAllowedState(WMView * srcView, XClientMessageEvent * event, WMDraggingInfo * info);
41 static void *finishDropState(WMView * srcView, XClientMessageEvent * event, WMDraggingInfo * info);
42
43 #ifdef XDND_DEBUG
44 static const char *stateName(W_DndState * state)
45 {
46         if (state == NULL)
47                 return "no state defined";
48
49         if (state == idleState)
50                 return "idleState";
51
52         if (state == dropAllowedState)
53                 return "dropAllowedState";
54
55         if (state == finishDropState)
56                 return "finishDropState";
57
58         return "unknown state";
59 }
60 #endif
61
62 static WMScreen *sourceScreen(WMDraggingInfo * info)
63 {
64         return W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
65 }
66
67 static void endDragProcess(WMDraggingInfo * info, Bool deposited)
68 {
69         WMView *view = XDND_SOURCE_VIEW(info);
70         WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
71
72         /* free selection handler while view exists */
73         WMDeleteSelectionHandler(view, scr->xdndSelectionAtom, CurrentTime);
74         wfree(XDND_SELECTION_PROCS(info));
75
76         if (XDND_DRAG_CURSOR(info) != None) {
77                 XFreeCursor(scr->display, XDND_DRAG_CURSOR(info));
78                 XDND_DRAG_CURSOR(info) = None;
79         }
80
81         if (view->dragSourceProcs->endedDrag != NULL) {
82                 /* this can destroy source view (with a "move" action for example) */
83                 view->dragSourceProcs->endedDrag(view, &XDND_DRAG_ICON_POS(info), deposited);
84         }
85
86         /* clear remaining draggging infos */
87         wfree(XDND_SOURCE_INFO(info));
88         XDND_SOURCE_INFO(info) = NULL;
89 }
90
91 /* ----- drag cursor ----- */
92 static void initDragCursor(WMDraggingInfo * info)
93 {
94         WMScreen *scr = sourceScreen(info);
95         XColor cursorFgColor, cursorBgColor;
96
97         /* green */
98         cursorFgColor.red = 0x4500;
99         cursorFgColor.green = 0xb000;
100         cursorFgColor.blue = 0x4500;
101
102         /* white */
103         cursorBgColor.red = 0xffff;
104         cursorBgColor.green = 0xffff;
105         cursorBgColor.blue = 0xffff;
106
107         XDND_DRAG_CURSOR(info) = XCreateFontCursor(scr->display, XC_left_ptr);
108         XRecolorCursor(scr->display, XDND_DRAG_CURSOR(info), &cursorFgColor, &cursorBgColor);
109
110         XFlush(scr->display);
111 }
112
113 static void recolorCursor(WMDraggingInfo * info, Bool dropIsAllowed)
114 {
115         WMScreen *scr = sourceScreen(info);
116
117         if (dropIsAllowed) {
118                 XDefineCursor(scr->display, scr->rootWin, XDND_DRAG_CURSOR(info));
119         } else {
120                 XDefineCursor(scr->display, scr->rootWin, scr->defaultCursor);
121         }
122
123         XFlush(scr->display);
124 }
125
126 /* ----- end of drag cursor ----- */
127
128 /* ----- selection procs ----- */
129 static WMData *convertSelection(WMView * view, Atom selection, Atom target, void *cdata, Atom * type)
130 {
131         WMScreen *scr;
132         WMData *data;
133         char *typeName;
134
135         scr = W_VIEW_SCREEN(view);
136         typeName = XGetAtomName(scr->display, target);
137
138         *type = target;
139
140         if (view->dragSourceProcs->fetchDragData != NULL) {
141                 data = view->dragSourceProcs->fetchDragData(view, typeName);
142         } else {
143                 data = NULL;
144         }
145
146         if (typeName != NULL)
147                 XFree(typeName);
148
149         return data;
150 }
151
152 static void selectionLost(WMView * view, Atom selection, void *cdata)
153 {
154         wwarning("DND selection lost during drag operation...");
155 }
156
157 static void selectionDone(WMView * view, Atom selection, Atom target, void *cdata)
158 {
159 #ifdef XDND_DEBUG
160         printf("selection done\n");
161 #endif
162 }
163
164 /* ----- end of selection procs ----- */
165
166 /* ----- visual part ----- */
167
168 static Window makeDragIcon(WMScreen * scr, WMPixmap * pixmap)
169 {
170         Window window;
171         WMSize size;
172         unsigned long flags;
173         XSetWindowAttributes attribs;
174         Pixmap pix, mask;
175
176         if (!pixmap) {
177                 pixmap = scr->defaultObjectIcon;
178         }
179
180         size = WMGetPixmapSize(pixmap);
181         pix = pixmap->pixmap;
182         mask = pixmap->mask;
183
184         flags = CWSaveUnder | CWBackPixmap | CWOverrideRedirect | CWColormap;
185         attribs.save_under = True;
186         attribs.background_pixmap = pix;
187         attribs.override_redirect = True;
188         attribs.colormap = scr->colormap;
189
190         window = XCreateWindow(scr->display, scr->rootWin, 0, 0, size.width,
191                                size.height, 0, scr->depth, InputOutput, scr->visual, flags, &attribs);
192
193 #ifdef SHAPE
194
195         if (mask) {
196                 XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, mask, ShapeSet);
197         }
198 #endif
199
200         return window;
201 }
202
203 static void slideWindow(Display * dpy, Window win, int srcX, int srcY, int dstX, int dstY)
204 {
205         double x, y, dx, dy;
206         int i;
207         int iterations;
208
209         iterations = WMIN(MAX_SLIDEBACK_ITER, WMAX(abs(dstX - srcX), abs(dstY - srcY)));
210
211         x = srcX;
212         y = srcY;
213
214         dx = (double)(dstX - srcX) / iterations;
215         dy = (double)(dstY - srcY) / iterations;
216
217         for (i = 0; i <= iterations; i++) {
218                 XMoveWindow(dpy, win, x, y);
219                 XFlush(dpy);
220
221                 wusleep(800);
222
223                 x += dx;
224                 y += dy;
225         }
226 }
227
228 static int getInitialDragImageCoord(int viewCoord, int mouseCoord, int viewSize, int iconSize)
229 {
230         if (iconSize >= viewSize) {
231                 /* center icon coord on view */
232                 return viewCoord - iconSize / 2;
233         } else {
234                 /* try to center icon on mouse pos */
235
236                 if (mouseCoord - iconSize / 2 <= viewCoord)
237                         /* if icon was centered on mouse, it would be off view
238                            thus, put icon left (resp. top) side
239                            at view (resp. top) side */
240                         return viewCoord;
241
242                 else if (mouseCoord + iconSize / 2 >= viewCoord + viewSize)
243                         /* if icon was centered on mouse, it would be off view
244                            thus, put icon right (resp. bottom) side
245                            at view right (resp. bottom) side */
246                         return viewCoord + viewSize - iconSize;
247
248                 else
249                         return mouseCoord - iconSize / 2;
250         }
251
252 }
253
254 static void initDragImagePos(WMView * view, WMDraggingInfo * info, XEvent * event)
255 {
256         WMSize iconSize = WMGetPixmapSize(view->dragImage);
257         WMSize viewSize = WMGetViewSize(view);
258         WMPoint viewPos;
259         Window foo;
260
261         XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
262                               WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
263                               0, 0, &(viewPos.x), &(viewPos.y), &foo);
264
265         /* set icon pos */
266         XDND_DRAG_ICON_POS(info).x =
267             getInitialDragImageCoord(viewPos.x, event->xmotion.x_root, viewSize.width, iconSize.width);
268
269         XDND_DRAG_ICON_POS(info).y =
270             getInitialDragImageCoord(viewPos.y, event->xmotion.y_root, viewSize.height, iconSize.height);
271
272         /* set mouse offset relative to icon */
273         XDND_MOUSE_OFFSET(info).x = event->xmotion.x_root - XDND_DRAG_ICON_POS(info).x;
274         XDND_MOUSE_OFFSET(info).y = event->xmotion.y_root - XDND_DRAG_ICON_POS(info).y;
275 }
276
277 static void refreshDragImage(WMView * view, WMDraggingInfo * info)
278 {
279         WMScreen *scr = W_VIEW_SCREEN(view);
280
281         XMoveWindow(scr->display, XDND_DRAG_ICON(info), XDND_DRAG_ICON_POS(info).x, XDND_DRAG_ICON_POS(info).y);
282 }
283
284 static void startDragImage(WMView * view, WMDraggingInfo * info, XEvent * event)
285 {
286         WMScreen *scr = W_VIEW_SCREEN(view);
287
288         XDND_DRAG_ICON(info) = makeDragIcon(scr, view->dragImage);
289         initDragImagePos(view, info, event);
290         refreshDragImage(view, info);
291         XMapRaised(scr->display, XDND_DRAG_ICON(info));
292
293         initDragCursor(info);
294 }
295
296 static void endDragImage(WMDraggingInfo * info, Bool slideBack)
297 {
298         WMView *view = XDND_SOURCE_VIEW(info);
299         Display *dpy = W_VIEW_SCREEN(view)->display;
300
301         if (slideBack) {
302                 WMPoint toLocation;
303                 Window foo;
304
305                 XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
306                                       WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
307                                       0, 0, &(toLocation.x), &(toLocation.y), &foo);
308
309                 slideWindow(dpy, XDND_DRAG_ICON(info),
310                             XDND_DRAG_ICON_POS(info).x, XDND_DRAG_ICON_POS(info).y, toLocation.x, toLocation.y);
311         }
312
313         XDestroyWindow(dpy, XDND_DRAG_ICON(info));
314 }
315
316 /* ----- end of visual part ----- */
317
318 /* ----- messages ----- */
319
320 /* send a DnD message to the destination window */
321 static Bool
322 sendDnDClientMessage(WMDraggingInfo * info, Atom message,
323                      unsigned long data1, unsigned long data2, unsigned long data3, unsigned long data4)
324 {
325         Display *dpy = sourceScreen(info)->display;
326         Window srcWin = WMViewXID(XDND_SOURCE_VIEW(info));
327         Window destWin = XDND_DEST_WIN(info);
328
329         if (!W_SendDnDClientMessage(dpy, destWin, message, srcWin, data1, data2, data3, data4)) {
330                 /* drop failed */
331                 recolorCursor(info, False);
332                 endDragImage(info, True);
333                 endDragProcess(info, False);
334                 return False;
335         }
336
337         return True;
338 }
339
340 static Bool sendEnterMessage(WMDraggingInfo * info)
341 {
342         WMScreen *scr = sourceScreen(info);
343         unsigned long version;
344
345         if (XDND_DEST_VERSION(info) > 2) {
346                 if (XDND_DEST_VERSION(info) < XDND_VERSION)
347                         version = XDND_DEST_VERSION(info);
348                 else
349                         version = XDND_VERSION;
350         } else {
351                 version = 3;
352         }
353
354         return sendDnDClientMessage(info, scr->xdndEnterAtom, (version << 24) | 1,      /* 1: support of type list */
355                                     XDND_3_TYPES(info)[0], XDND_3_TYPES(info)[1], XDND_3_TYPES(info)[2]);
356 }
357
358 static Bool sendPositionMessage(WMDraggingInfo * info, WMPoint * mousePos)
359 {
360         WMScreen *scr = sourceScreen(info);
361         WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));
362
363         if (noPosZone->size.width != 0 || noPosZone->size.height != 0) {
364                 if (mousePos->x < noPosZone->pos.x || mousePos->x > (noPosZone->pos.x + noPosZone->size.width)
365                     || mousePos->y < noPosZone->pos.y || mousePos->y > (noPosZone->pos.y + noPosZone->size.height)) {
366                         /* send position if out of zone defined by destination */
367                         return sendDnDClientMessage(info, scr->xdndPositionAtom,
368                                                     0,
369                                                     mousePos->x << 16 | mousePos->y,
370                                                     XDND_TIMESTAMP(info), XDND_SOURCE_ACTION(info));
371                 }
372
373                 /* Nothing to send, always succeed */
374                 return True;
375
376         }
377
378         /* send position on each move */
379         return sendDnDClientMessage(info, scr->xdndPositionAtom,
380                                     0,
381                                     mousePos->x << 16 | mousePos->y,
382                                     XDND_TIMESTAMP(info), XDND_SOURCE_ACTION(info));
383 }
384
385 static Bool sendLeaveMessage(WMDraggingInfo * info)
386 {
387         WMScreen *scr = sourceScreen(info);
388
389         return sendDnDClientMessage(info, scr->xdndLeaveAtom, 0, 0, 0, 0);
390 }
391
392 static Bool sendDropMessage(WMDraggingInfo * info)
393 {
394         WMScreen *scr = sourceScreen(info);
395
396         return sendDnDClientMessage(info, scr->xdndDropAtom, 0, XDND_TIMESTAMP(info), 0, 0);
397 }
398
399 /* ----- end of messages ----- */
400
401 static Atom *getTypeAtomList(WMScreen * scr, WMView * view, int *count)
402 {
403         WMArray *types;
404         Atom *typeAtoms;
405         int i;
406
407         types = view->dragSourceProcs->dropDataTypes(view);
408
409         if (types != NULL) {
410                 *count = WMGetArrayItemCount(types);
411                 if (*count > 0) {
412                         typeAtoms = wmalloc((*count) * sizeof(Atom));
413                         for (i = 0; i < *count; i++) {
414                                 typeAtoms[i] = XInternAtom(scr->display, WMGetFromArray(types, i), False);
415                         }
416
417                         /* WMFreeArray(types); */
418                         return typeAtoms;
419                 }
420
421                 /* WMFreeArray(types); */
422         }
423
424         *count = 1;
425         typeAtoms = wmalloc(sizeof(Atom));
426         *typeAtoms = None;
427
428         return typeAtoms;
429 }
430
431 static void registerDropTypes(WMScreen * scr, WMView * view, WMDraggingInfo * info)
432 {
433         Atom *typeList;
434         int i, count;
435
436         typeList = getTypeAtomList(scr, view, &count);
437
438         /* store the first 3 types */
439         for (i = 0; i < 3 && i < count; i++)
440                 XDND_3_TYPES(info)[i] = typeList[i];
441
442         for (; i < 3; i++)
443                 XDND_3_TYPES(info)[i] = None;
444
445         /* store the entire type list */
446         XChangeProperty(scr->display,
447                         WMViewXID(view),
448                         scr->xdndTypeListAtom,
449                         XA_ATOM, XDND_PROPERTY_FORMAT, PropModeReplace, (unsigned char *)typeList, count);
450 }
451
452 static void registerOperationList(WMScreen * scr, WMView * view, WMArray * operationArray)
453 {
454         Atom *actionList;
455         WMDragOperationType operation;
456         int count = WMGetArrayItemCount(operationArray);
457         int i;
458
459         actionList = wmalloc(sizeof(Atom) * count);
460
461         for (i = 0; i < count; i++) {
462                 operation = WMGetDragOperationItemType(WMGetFromArray(operationArray, i));
463                 actionList[i] = W_OperationToAction(scr, operation);
464         }
465
466         XChangeProperty(scr->display,
467                         WMViewXID(view),
468                         scr->xdndActionListAtom,
469                         XA_ATOM, XDND_PROPERTY_FORMAT, PropModeReplace, (unsigned char *)actionList, count);
470 }
471
472 static void registerDescriptionList(WMScreen * scr, WMView * view, WMArray * operationArray)
473 {
474         char *text, *textListItem, *textList;
475         int count = WMGetArrayItemCount(operationArray);
476         int i;
477         int size = 0;
478
479         /* size of XA_STRING info */
480         for (i = 0; i < count; i++) {
481                 size += strlen(WMGetDragOperationItemText(WMGetFromArray(operationArray, i))) + 1;      /* +1 = +NULL */
482         }
483
484         /* create text list */
485         textList = wmalloc(size);
486         textListItem = textList;
487
488         for (i = 0; i < count; i++) {
489                 text = WMGetDragOperationItemText(WMGetFromArray(operationArray, i));
490                 strcpy(textListItem, text);
491
492                 /* to next text offset */
493                 textListItem = &(textListItem[strlen(textListItem) + 1]);
494         }
495
496         XChangeProperty(scr->display,
497                         WMViewXID(view),
498                         scr->xdndActionDescriptionAtom,
499                         XA_STRING,
500                         XDND_ACTION_DESCRIPTION_FORMAT, PropModeReplace, (unsigned char *)textList, size);
501 }
502
503 /* called if wanted operation is WDOperationAsk */
504 static void registerSupportedOperations(WMView * view)
505 {
506         WMScreen *scr = W_VIEW_SCREEN(view);
507         WMArray *operationArray;
508
509         operationArray = view->dragSourceProcs->askedOperations(view);
510
511         registerOperationList(scr, view, operationArray);
512         registerDescriptionList(scr, view, operationArray);
513
514         /* WMFreeArray(operationArray); */
515 }
516
517 static void initSourceDragInfo(WMView * sourceView, WMDraggingInfo * info)
518 {
519         WMRect emptyZone;
520
521         XDND_SOURCE_INFO(info) = (W_DragSourceInfo *) wmalloc(sizeof(W_DragSourceInfo));
522
523         XDND_SOURCE_VIEW(info) = sourceView;
524         XDND_DEST_WIN(info) = None;
525         XDND_DRAG_ICON(info) = None;
526
527         XDND_SOURCE_ACTION(info) = W_OperationToAction(W_VIEW_SCREEN(sourceView),
528                                                        sourceView->dragSourceProcs->
529                                                        wantedDropOperation(sourceView));
530
531         XDND_DEST_ACTION(info) = None;
532
533         XDND_SOURCE_STATE(info) = idleState;
534
535         emptyZone.pos = wmkpoint(0, 0);
536         emptyZone.size = wmksize(0, 0);
537         XDND_NO_POS_ZONE(info) = emptyZone;
538 }
539
540 /*
541  Returned array is destroyed after dropDataTypes call
542  */
543 static WMArray *defDropDataTypes(WMView * self)
544 {
545         return NULL;
546 }
547
548 static WMDragOperationType defWantedDropOperation(WMView * self)
549 {
550         return WDOperationNone;
551 }
552
553 /*
554  Must be defined if wantedDropOperation return WDOperationAsk
555  (useless otherwise).
556  Return a WMDragOperationItem array (destroyed after call).
557  A WMDragOperationItem links a label to an operation.
558  static WMArray*
559  defAskedOperations(WMView *self); */
560
561 static Bool defAcceptDropOperation(WMView * self, WMDragOperationType allowedOperation)
562 {
563         return False;
564 }
565
566 static void defBeganDrag(WMView * self, WMPoint * point)
567 {
568 }
569
570 static void defEndedDrag(WMView * self, WMPoint * point, Bool deposited)
571 {
572 }
573
574 /*
575  Returned data is not destroyed
576  */
577 static WMData *defFetchDragData(WMView * self, char *type)
578 {
579         return NULL;
580 }
581
582 void WMSetViewDragSourceProcs(WMView * view, WMDragSourceProcs * procs)
583 {
584         if (view->dragSourceProcs)
585                 wfree(view->dragSourceProcs);
586         view->dragSourceProcs = wmalloc(sizeof(WMDragSourceProcs));
587
588         *view->dragSourceProcs = *procs;
589
590         if (procs->dropDataTypes == NULL)
591                 view->dragSourceProcs->dropDataTypes = defDropDataTypes;
592
593         if (procs->wantedDropOperation == NULL)
594                 view->dragSourceProcs->wantedDropOperation = defWantedDropOperation;
595
596         /*
597            Note: askedOperations can be NULL, if wantedDropOperation never returns
598            WDOperationAsk.
599          */
600
601         if (procs->acceptDropOperation == NULL)
602                 view->dragSourceProcs->acceptDropOperation = defAcceptDropOperation;
603
604         if (procs->beganDrag == NULL)
605                 view->dragSourceProcs->beganDrag = defBeganDrag;
606
607         if (procs->endedDrag == NULL)
608                 view->dragSourceProcs->endedDrag = defEndedDrag;
609
610         if (procs->fetchDragData == NULL)
611                 view->dragSourceProcs->fetchDragData = defFetchDragData;
612 }
613
614 static Bool isXdndAware(WMScreen * scr, Window win)
615 {
616         Atom type;
617         int format;
618         unsigned long count, remain;
619         unsigned char *winXdndVersion;
620
621         if (win == None)
622                 return False;
623
624         XGetWindowProperty(scr->display, win, scr->xdndAwareAtom,
625                            0, 1, False, XA_ATOM, &type, &format, &count, &remain, &winXdndVersion);
626
627         if (type != XA_ATOM || format != XDND_PROPERTY_FORMAT || count == 0 || !winXdndVersion) {
628                 if (winXdndVersion)
629                         XFree(winXdndVersion);
630                 return False;
631         }
632
633         XFree(winXdndVersion);
634         return (count == 1);    /* xdnd version is set */
635 }
636
637 static Window *windowChildren(Display * dpy, Window win, unsigned *nchildren)
638 {
639         Window *children;
640         Window foo, bar;
641
642         if (!XQueryTree(dpy, win, &foo, &bar, &children, nchildren)) {
643                 *nchildren = 0;
644                 return NULL;
645         } else
646                 return children;
647 }
648
649 static Window lookForAwareWindow(WMScreen * scr, WMPoint * mousePos, Window win)
650 {
651         int tmpx, tmpy;
652         Window child;
653
654         /* Since xdnd v3, only the toplevel window should be aware */
655         if (isXdndAware(scr, win))
656                 return win;
657
658         /* inspect child under pointer */
659         if (XTranslateCoordinates(scr->display, scr->rootWin, win, mousePos->x, mousePos->y, &tmpx, &tmpy, &child)) {
660                 if (child == None)
661                         return None;
662                 else
663                         return lookForAwareWindow(scr, mousePos, child);
664         }
665
666         return None;
667 }
668
669 static Window findDestination(WMDraggingInfo * info, WMPoint * mousePos)
670 {
671         WMScreen *scr = sourceScreen(info);
672         unsigned nchildren;
673         Window *children = windowChildren(scr->display, scr->rootWin, &nchildren);
674         int i;
675         XWindowAttributes attr;
676
677         if (isXdndAware(scr, scr->rootWin))
678                 return scr->rootWin;
679
680         /* exclude drag icon (and upper) from search */
681         for (i = nchildren - 1; i >= 0; i--) {
682                 if (children[i] == XDND_DRAG_ICON(info)) {
683                         i--;
684                         break;
685                 }
686         }
687
688         if (i < 0) {
689                 /* root window has no child under drag icon, and is not xdnd aware. */
690                 return None;
691         }
692
693         /* inspecting children, from upper to lower */
694         for (; i >= 0; i--) {
695                 if (XGetWindowAttributes(scr->display, children[i], &attr)
696                     && attr.map_state == IsViewable
697                     && mousePos->x >= attr.x
698                     && mousePos->y >= attr.y
699                     && mousePos->x < attr.x + attr.width && mousePos->y < attr.y + attr.height) {
700                         return lookForAwareWindow(scr, mousePos, children[i]);
701                 }
702         }
703
704         /* No child window under drag pointer */
705         return None;
706 }
707
708 static void storeDestinationProtocolVersion(WMDraggingInfo * info)
709 {
710         Atom type;
711         int format;
712         unsigned long count, remain;
713         unsigned char *winXdndVersion;
714         WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
715
716         wassertr(XDND_DEST_WIN(info) != None);
717
718         if (XGetWindowProperty(scr->display, XDND_DEST_WIN(info),
719                                scr->xdndAwareAtom,
720                                0, 1, False, XA_ATOM, &type, &format,
721                                &count, &remain, &winXdndVersion) == Success) {
722                 XDND_DEST_VERSION(info) = *winXdndVersion;
723                 XFree(winXdndVersion);
724         } else {
725                 XDND_DEST_VERSION(info) = 0;
726                 wwarning("failed to read XDND version of drop target");
727         }
728 }
729
730 static void initMotionProcess(WMView * view, WMDraggingInfo * info, XEvent * event, WMPoint * startLocation)
731 {
732         WMScreen *scr = W_VIEW_SCREEN(view);
733
734         /* take ownership of XdndSelection */
735         XDND_SELECTION_PROCS(info) = (WMSelectionProcs *) wmalloc(sizeof(WMSelectionProcs));
736         XDND_SELECTION_PROCS(info)->convertSelection = convertSelection;
737         XDND_SELECTION_PROCS(info)->selectionLost = selectionLost;
738         XDND_SELECTION_PROCS(info)->selectionDone = selectionDone;
739         XDND_TIMESTAMP(info) = event->xmotion.time;
740
741         if (!WMCreateSelectionHandler(view, scr->xdndSelectionAtom, CurrentTime, XDND_SELECTION_PROCS(info), NULL)) {
742                 wwarning("could not get ownership or DND selection");
743                 return;
744         }
745
746         registerDropTypes(scr, view, info);
747
748         if (XDND_SOURCE_ACTION(info) == W_VIEW_SCREEN(view)->xdndActionAsk)
749                 registerSupportedOperations(view);
750
751         if (view->dragSourceProcs->beganDrag != NULL) {
752                 view->dragSourceProcs->beganDrag(view, startLocation);
753         }
754 }
755
756 static void processMotion(WMDraggingInfo * info, WMPoint * mousePos)
757 {
758         Window newDestination = findDestination(info, mousePos);
759
760         W_DragSourceStopTimer();
761
762         if (newDestination != XDND_DEST_WIN(info)) {
763                 recolorCursor(info, False);
764
765                 if (XDND_DEST_WIN(info) != None) {
766                         /* leaving a xdnd window */
767                         sendLeaveMessage(info);
768                 }
769
770                 XDND_DEST_WIN(info) = newDestination;
771                 XDND_DEST_ACTION(info) = None;
772                 XDND_NO_POS_ZONE(info).size.width = 0;
773                 XDND_NO_POS_ZONE(info).size.height = 0;
774
775                 if (newDestination != None) {
776                         /* entering a xdnd window */
777                         XDND_SOURCE_STATE(info) = idleState;
778                         storeDestinationProtocolVersion(info);
779
780                         if (!sendEnterMessage(info)) {
781                                 XDND_DEST_WIN(info) = None;
782                                 return;
783                         }
784
785                         W_DragSourceStartTimer(info);
786                 } else {
787                         XDND_SOURCE_STATE(info) = NULL;
788                 }
789         } else {
790                 if (XDND_DEST_WIN(info) != None) {
791                         if (!sendPositionMessage(info, mousePos)) {
792                                 XDND_DEST_WIN(info) = None;
793                                 return;
794                         }
795
796                         W_DragSourceStartTimer(info);
797                 }
798         }
799 }
800
801 static Bool processButtonRelease(WMDraggingInfo * info)
802 {
803         if (XDND_SOURCE_STATE(info) == dropAllowedState) {
804                 /* begin drop */
805                 W_DragSourceStopTimer();
806
807                 if (!sendDropMessage(info))
808                         return False;
809
810                 W_DragSourceStartTimer(info);
811                 return True;
812         } else {
813                 if (XDND_DEST_WIN(info) != None)
814                         sendLeaveMessage(info);
815
816                 W_DragSourceStopTimer();
817                 return False;
818         }
819 }
820
821 Bool WMIsDraggingFromView(WMView * view)
822 {
823         WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
824
825         return (XDND_SOURCE_INFO(info) != NULL && XDND_SOURCE_STATE(info) != finishDropState);
826         /* return W_VIEW_SCREEN(view)->dragInfo.sourceInfo != NULL; */
827 }
828
829 void WMDragImageFromView(WMView * view, XEvent * event)
830 {
831         WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
832         WMPoint mouseLocation;
833
834         switch (event->type) {
835         case ButtonPress:
836                 if (event->xbutton.button == Button1) {
837                         XEvent nextEvent;
838
839                         XPeekEvent(event->xbutton.display, &nextEvent);
840
841                         /* Initialize only if a drag really begins (avoid clicks) */
842                         if (nextEvent.type == MotionNotify) {
843                                 initSourceDragInfo(view, info);
844                         }
845                 }
846
847                 break;
848
849         case ButtonRelease:
850                 if (WMIsDraggingFromView(view)) {
851                         Bool dropBegan = processButtonRelease(info);
852
853                         recolorCursor(info, False);
854                         if (dropBegan) {
855                                 endDragImage(info, False);
856                                 XDND_SOURCE_STATE(info) = finishDropState;
857                         } else {
858                                 /* drop failed */
859                                 endDragImage(info, True);
860                                 endDragProcess(info, False);
861                         }
862                 }
863                 break;
864
865         case MotionNotify:
866                 if (WMIsDraggingFromView(view)) {
867                         mouseLocation = wmkpoint(event->xmotion.x_root, event->xmotion.y_root);
868
869                         if (abs(XDND_DRAG_ICON_POS(info).x - mouseLocation.x) >=
870                             MIN_X_MOVE_OFFSET
871                             || abs(XDND_DRAG_ICON_POS(info).y - mouseLocation.y) >= MIN_Y_MOVE_OFFSET) {
872                                 if (XDND_DRAG_ICON(info) == None) {
873                                         initMotionProcess(view, info, event, &mouseLocation);
874                                         startDragImage(view, info, event);
875                                 } else {
876                                         XDND_DRAG_ICON_POS(info).x = mouseLocation.x - XDND_MOUSE_OFFSET(info).x;
877                                         XDND_DRAG_ICON_POS(info).y = mouseLocation.y - XDND_MOUSE_OFFSET(info).y;
878
879                                         refreshDragImage(view, info);
880                                         processMotion(info, &mouseLocation);
881                                 }
882                         }
883                 }
884                 break;
885         }
886 }
887
888 /* Minimal mouse events handler: no right or double-click detection,
889  only drag is supported */
890 static void dragImageHandler(XEvent * event, void *cdata)
891 {
892         WMView *view = (WMView *) cdata;
893
894         WMDragImageFromView(view, event);
895 }
896
897 /* ----- source states ----- */
898
899 #ifdef XDND_DEBUG
900 static void traceStatusMsg(Display * dpy, XClientMessageEvent * statusEvent)
901 {
902         printf("Xdnd status message:\n");
903
904         if (statusEvent->data.l[1] & 0x2UL)
905                 printf("\tsend position on every move\n");
906         else {
907                 int x, y, w, h;
908                 x = statusEvent->data.l[2] >> 16;
909                 y = statusEvent->data.l[2] & 0xFFFFL;
910                 w = statusEvent->data.l[3] >> 16;
911                 h = statusEvent->data.l[3] & 0xFFFFL;
912
913                 printf("\tsend position out of ((%d,%d) , (%d,%d))\n", x, y, x + w, y + h);
914         }
915
916         if (statusEvent->data.l[1] & 0x1L)
917                 printf("\tallowed action: %s\n", XGetAtomName(dpy, statusEvent->data.l[4]));
918         else
919                 printf("\tno action allowed\n");
920 }
921 #endif
922
923 static void storeDropAction(WMDraggingInfo * info, Atom destAction)
924 {
925         WMView *sourceView = XDND_SOURCE_VIEW(info);
926         WMScreen *scr = W_VIEW_SCREEN(sourceView);
927
928         if (sourceView->dragSourceProcs->acceptDropOperation != NULL) {
929                 if (sourceView->dragSourceProcs->acceptDropOperation(sourceView,
930                                                                      W_ActionToOperation(scr, destAction)))
931                         XDND_DEST_ACTION(info) = destAction;
932                 else
933                         XDND_DEST_ACTION(info) = None;
934         } else {
935                 XDND_DEST_ACTION(info) = destAction;
936         }
937 }
938
939 static void storeStatusMessageInfos(WMDraggingInfo * info, XClientMessageEvent * statusEvent)
940 {
941         WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));
942
943 #ifdef XDND_DEBUG
944
945         traceStatusMsg(sourceScreen(info)->display, statusEvent);
946 #endif
947
948         if (statusEvent->data.l[1] & 0x2UL) {
949                 /* bit 1 set: destination wants position messages on every move */
950                 noPosZone->size.width = 0;
951                 noPosZone->size.height = 0;
952         } else {
953                 /* don't send another position message while in given rectangle */
954                 noPosZone->pos.x = statusEvent->data.l[2] >> 16;
955                 noPosZone->pos.y = statusEvent->data.l[2] & 0xFFFFL;
956                 noPosZone->size.width = statusEvent->data.l[3] >> 16;
957                 noPosZone->size.height = statusEvent->data.l[3] & 0xFFFFL;
958         }
959
960         if ((statusEvent->data.l[1] & 0x1L) || statusEvent->data.l[4] != None) {
961                 /* destination accept drop */
962                 storeDropAction(info, statusEvent->data.l[4]);
963         } else {
964                 XDND_DEST_ACTION(info) = None;
965         }
966 }
967
968 static void *idleState(WMView * view, XClientMessageEvent * event, WMDraggingInfo * info)
969 {
970         WMScreen *scr;
971         Atom destMsg = event->message_type;
972
973         scr = W_VIEW_SCREEN(view);
974
975         if (destMsg == scr->xdndStatusAtom) {
976                 storeStatusMessageInfos(info, event);
977
978                 if (XDND_DEST_ACTION(info) != None) {
979                         recolorCursor(info, True);
980                         W_DragSourceStartTimer(info);
981                         return dropAllowedState;
982                 } else {
983                         /* drop denied */
984                         recolorCursor(info, False);
985                         return idleState;
986                 }
987         }
988
989         if (destMsg == scr->xdndFinishedAtom) {
990                 wwarning("received xdndFinishedAtom before drop began");
991         }
992
993         W_DragSourceStartTimer(info);
994         return idleState;
995 }
996
997 static void *dropAllowedState(WMView * view, XClientMessageEvent * event, WMDraggingInfo * info)
998 {
999         WMScreen *scr = W_VIEW_SCREEN(view);
1000         Atom destMsg = event->message_type;
1001
1002         if (destMsg == scr->xdndStatusAtom) {
1003                 storeStatusMessageInfos(info, event);
1004
1005                 if (XDND_DEST_ACTION(info) == None) {
1006                         /* drop denied */
1007                         recolorCursor(info, False);
1008                         return idleState;
1009                 }
1010         }
1011
1012         W_DragSourceStartTimer(info);
1013         return dropAllowedState;
1014 }
1015
1016 static void *finishDropState(WMView * view, XClientMessageEvent * event, WMDraggingInfo * info)
1017 {
1018         WMScreen *scr = W_VIEW_SCREEN(view);
1019         Atom destMsg = event->message_type;
1020
1021         if (destMsg == scr->xdndFinishedAtom) {
1022                 endDragProcess(info, True);
1023                 return NULL;
1024         }
1025
1026         W_DragSourceStartTimer(info);
1027         return finishDropState;
1028 }
1029
1030 /* ----- end of source states ----- */
1031
1032 /* ----- source timer ----- */
1033 static void dragSourceResponseTimeOut(void *source)
1034 {
1035         WMView *view = (WMView *) source;
1036         WMDraggingInfo *info = &(W_VIEW_SCREEN(view)->dragInfo);
1037
1038         wwarning("delay for drag destination response expired");
1039         sendLeaveMessage(info);
1040
1041         recolorCursor(info, False);
1042         if (XDND_SOURCE_STATE(info) == finishDropState) {
1043                 /* drop failed */
1044                 endDragImage(info, True);
1045                 endDragProcess(info, False);
1046         } else {
1047                 XDND_SOURCE_STATE(info) = idleState;
1048         }
1049 }
1050
1051 void W_DragSourceStopTimer()
1052 {
1053         if (dndSourceTimer != NULL) {
1054                 WMDeleteTimerHandler(dndSourceTimer);
1055                 dndSourceTimer = NULL;
1056         }
1057 }
1058
1059 void W_DragSourceStartTimer(WMDraggingInfo * info)
1060 {
1061         W_DragSourceStopTimer();
1062
1063         dndSourceTimer = WMAddTimerHandler(XDND_DESTINATION_RESPONSE_MAX_DELAY,
1064                                            dragSourceResponseTimeOut, XDND_SOURCE_VIEW(info));
1065 }
1066
1067 /* ----- End of Destination timer ----- */
1068
1069 void W_DragSourceStateHandler(WMDraggingInfo * info, XClientMessageEvent * event)
1070 {
1071         WMView *view;
1072         W_DndState *newState;
1073
1074         if (XDND_SOURCE_VIEW_STORED(info)) {
1075                 if (XDND_SOURCE_STATE(info) != NULL) {
1076                         view = XDND_SOURCE_VIEW(info);
1077 #ifdef XDND_DEBUG
1078
1079                         printf("current source state: %s\n", stateName(XDND_SOURCE_STATE(info)));
1080 #endif
1081
1082                         newState = (W_DndState *) XDND_SOURCE_STATE(info) (view, event, info);
1083
1084 #ifdef XDND_DEBUG
1085
1086                         printf("new source state: %s\n", stateName(newState));
1087 #endif
1088
1089                         if (newState != NULL)
1090                                 XDND_SOURCE_STATE(info) = newState;
1091                         /* else drop finished, and info has been flushed */
1092                 }
1093
1094         } else {
1095                 wwarning("received DnD message without having a target");
1096         }
1097 }
1098
1099 void WMSetViewDragImage(WMView * view, WMPixmap * dragImage)
1100 {
1101         if (view->dragImage != NULL)
1102                 WMReleasePixmap(view->dragImage);
1103
1104         view->dragImage = WMRetainPixmap(dragImage);
1105 }
1106
1107 void WMReleaseViewDragImage(WMView * view)
1108 {
1109         if (view->dragImage != NULL)
1110                 WMReleasePixmap(view->dragImage);
1111 }
1112
1113 /* Create a drag handler, associating drag event masks with dragEventProc */
1114 void WMCreateDragHandler(WMView * view, WMEventProc * dragEventProc, void *clientData)
1115 {
1116         WMCreateEventHandler(view,
1117                              ButtonPressMask | ButtonReleaseMask | Button1MotionMask, dragEventProc, clientData);
1118 }
1119
1120 void WMDeleteDragHandler(WMView * view, WMEventProc * dragEventProc, void *clientData)
1121 {
1122         WMDeleteEventHandler(view,
1123                              ButtonPressMask | ButtonReleaseMask | Button1MotionMask, dragEventProc, clientData);
1124 }
1125
1126 /* set default drag handler for view */
1127 void WMSetViewDraggable(WMView * view, WMDragSourceProcs * dragSourceProcs, WMPixmap * dragImage)
1128 {
1129         wassertr(dragImage != NULL);
1130         view->dragImage = WMRetainPixmap(dragImage);
1131
1132         WMSetViewDragSourceProcs(view, dragSourceProcs);
1133
1134         WMCreateDragHandler(view, dragImageHandler, view);
1135 }
1136
1137 void WMUnsetViewDraggable(WMView * view)
1138 {
1139         if (view->dragSourceProcs) {
1140                 wfree(view->dragSourceProcs);
1141                 view->dragSourceProcs = NULL;
1142         }
1143
1144         WMReleaseViewDragImage(view);
1145
1146         WMDeleteDragHandler(view, dragImageHandler, view);
1147 }