svn update: 48958 (latest:48959)
[framework/uifw/ecore.git] / src / lib / ecore_x / xlib / ecore_x_dnd.c
1 /*
2  * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
3  */
4
5 #ifdef HAVE_CONFIG_H
6 # include <config.h>
7 #endif
8
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include "Ecore.h"
13 #include "ecore_x_private.h"
14 #include "Ecore_X.h"
15 #include "Ecore_X_Atoms.h"
16
17 EAPI int ECORE_X_EVENT_XDND_ENTER = 0;
18 EAPI int ECORE_X_EVENT_XDND_POSITION = 0;
19 EAPI int ECORE_X_EVENT_XDND_STATUS = 0;
20 EAPI int ECORE_X_EVENT_XDND_LEAVE = 0;
21 EAPI int ECORE_X_EVENT_XDND_DROP = 0;
22 EAPI int ECORE_X_EVENT_XDND_FINISHED = 0;
23
24 static Ecore_X_DND_Source *_source = NULL;
25 static Ecore_X_DND_Target *_target = NULL;
26 static int _ecore_x_dnd_init_count = 0;
27
28 typedef struct _Version_Cache_Item
29 {
30    Ecore_X_Window win;
31    int ver;
32 } Version_Cache_Item;
33 static Version_Cache_Item *_version_cache = NULL;
34 static int _version_cache_num = 0, _version_cache_alloc = 0;
35
36 void
37 _ecore_x_dnd_init(void)
38 {
39    if (!_ecore_x_dnd_init_count)
40      {
41         _source = calloc(1, sizeof(Ecore_X_DND_Source));
42         _source->version = ECORE_X_DND_VERSION;
43         _source->win = None;
44         _source->dest = None;
45         _source->state = ECORE_X_DND_SOURCE_IDLE;
46         _source->prev.window = 0;
47
48         _target = calloc(1, sizeof(Ecore_X_DND_Target));
49         _target->win = None;
50         _target->source = None;
51         _target->state = ECORE_X_DND_TARGET_IDLE;
52         
53         ECORE_X_EVENT_XDND_ENTER              = ecore_event_type_new();
54         ECORE_X_EVENT_XDND_POSITION           = ecore_event_type_new();
55         ECORE_X_EVENT_XDND_STATUS             = ecore_event_type_new();
56         ECORE_X_EVENT_XDND_LEAVE              = ecore_event_type_new();
57         ECORE_X_EVENT_XDND_DROP               = ecore_event_type_new();
58         ECORE_X_EVENT_XDND_FINISHED           = ecore_event_type_new();
59      }
60
61    _ecore_x_dnd_init_count++;
62 }
63
64 void
65 _ecore_x_dnd_shutdown(void)
66 {
67    _ecore_x_dnd_init_count--;
68    if (_ecore_x_dnd_init_count > 0)
69      return;
70
71    if (_source)
72      free(_source);
73    _source = NULL;
74
75    if (_target)
76      free(_target);
77    _target = NULL;
78
79    _ecore_x_dnd_init_count = 0;
80 }
81
82 static int
83 _ecore_x_dnd_converter_copy(char *target __UNUSED__, void *data, int size, void **data_ret, int *size_ret)
84 {
85    XTextProperty text_prop;
86    char *mystr;
87    XICCEncodingStyle style = XTextStyle;
88
89    if (!data || !size)
90      return 0;
91
92    mystr = calloc(1, size + 1);
93    if (!mystr) return 0;
94    memcpy(mystr, data, size);
95
96    if (XmbTextListToTextProperty(_ecore_x_disp, &mystr, 1, style, &text_prop) == Success)
97      {
98         int bufsize = strlen((char *)text_prop.value) + 1;
99         *data_ret = malloc(bufsize);
100         memcpy(*data_ret, text_prop.value, bufsize);
101         *size_ret = bufsize;
102         XFree(text_prop.value);
103         free(mystr);
104         return 1;
105      }
106    else
107      {
108         free(mystr);
109         return 0;
110      }
111 }
112
113 EAPI void
114 ecore_x_dnd_aware_set(Ecore_X_Window win, int on)
115 {
116    Ecore_X_Atom prop_data = ECORE_X_DND_VERSION;
117
118    LOGFN(__FILE__, __LINE__, __FUNCTION__);
119    if (on)
120      ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_AWARE,
121                                       XA_ATOM, 32, &prop_data, 1);
122    else
123      ecore_x_window_prop_property_del(win, ECORE_X_ATOM_XDND_AWARE);
124 }
125
126 EAPI int
127 ecore_x_dnd_version_get(Ecore_X_Window win)
128 {
129    unsigned char *prop_data;
130    int num;
131
132    LOGFN(__FILE__, __LINE__, __FUNCTION__);
133    // this looks hacky - and it is, but we need a way of caching info about
134    // a window while dragging, because we literally query this every mouse
135    // move and going to and from x multiple times per move is EXPENSIVE
136    // and slows things down, puts lots of load on x etc.
137    if (_source->state == ECORE_X_DND_SOURCE_DRAGGING)
138      {
139         if (_version_cache)
140           {
141              int i;
142              
143              for (i = 0; i < _version_cache_num; i++)
144                {
145                   if (_version_cache[i].win == win)
146                     return _version_cache[i].ver;
147                }
148           }
149      }
150      
151    if (ecore_x_window_prop_property_get(win, ECORE_X_ATOM_XDND_AWARE,
152                                         XA_ATOM, 32, &prop_data, &num))
153      {
154         int version = (int) *prop_data;
155         free(prop_data);
156         if (_source->state == ECORE_X_DND_SOURCE_DRAGGING)
157           {
158              _version_cache_num++;
159              if (_version_cache_num > _version_cache_alloc)
160                _version_cache_alloc += 16;
161              _version_cache = realloc(_version_cache, _version_cache_alloc * sizeof(Version_Cache_Item));
162              _version_cache[_version_cache_num - 1].win = win;
163              _version_cache[_version_cache_num - 1].ver = version;
164           }
165         return version;
166      }
167    if (_source->state == ECORE_X_DND_SOURCE_DRAGGING)
168      {
169         _version_cache_num++;
170         if (_version_cache_num > _version_cache_alloc)
171           _version_cache_alloc += 16;
172         _version_cache = realloc(_version_cache, _version_cache_alloc * sizeof(Version_Cache_Item));
173         _version_cache[_version_cache_num - 1].win = win;
174         _version_cache[_version_cache_num - 1].ver = 0;
175      }
176    return 0;
177 }
178
179 EAPI int
180 ecore_x_dnd_type_isset(Ecore_X_Window win, const char *type)
181 {
182    int                  num, i, ret = 0;
183    unsigned char       *data;
184    Ecore_X_Atom        *atoms, atom;
185
186    LOGFN(__FILE__, __LINE__, __FUNCTION__);
187    if (!ecore_x_window_prop_property_get(win, ECORE_X_ATOM_XDND_TYPE_LIST,
188                                          XA_ATOM, 32, &data, &num))
189      return ret;
190
191    atom = ecore_x_atom_get(type);
192    atoms = (Ecore_X_Atom *)data;
193
194    for (i = 0; i < num; ++i)
195      {
196         if (atom == atoms[i])
197           {
198              ret = 1;
199              break;
200           }
201      }
202
203    XFree(data);
204    return ret;
205 }
206
207 EAPI void
208 ecore_x_dnd_type_set(Ecore_X_Window win, const char *type, int on)
209 {
210    Ecore_X_Atom      atom;
211    Ecore_X_Atom      *oldset = NULL, *newset = NULL;
212    int               i, j = 0, num = 0;
213    unsigned char     *data = NULL;
214    unsigned char     *old_data = NULL;
215
216    LOGFN(__FILE__, __LINE__, __FUNCTION__);
217    atom = ecore_x_atom_get(type);
218    ecore_x_window_prop_property_get(win, ECORE_X_ATOM_XDND_TYPE_LIST,
219                                     XA_ATOM, 32, &old_data, &num);
220    oldset = (Ecore_X_Atom *)old_data;
221
222    LOGFN(__FILE__, __LINE__, __FUNCTION__);
223    if (on)
224      {
225         if (ecore_x_dnd_type_isset(win, type))
226           {
227              XFree(old_data);
228              return;
229           }
230         newset = calloc(num + 1, sizeof(Ecore_X_Atom));
231         if (!newset) return;
232         data = (unsigned char *)newset;
233
234         for (i = 0; i < num; i++)
235           newset[i + 1] = oldset[i];
236         /* prepend the new type */
237         newset[0] = atom;
238
239         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_TYPE_LIST,
240                                          XA_ATOM, 32, data, num + 1);
241      }
242    else
243      {
244         if (!ecore_x_dnd_type_isset(win, type))
245           {
246              XFree(old_data);
247              return;
248           }
249         newset = calloc(num - 1, sizeof(Ecore_X_Atom));
250         if (!newset)
251           {
252              XFree(old_data);
253              return;
254           }
255         data = (unsigned char *)newset;
256         for (i = 0; i < num; i++)
257           if (oldset[i] != atom)
258             newset[j++] = oldset[i];
259
260         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_TYPE_LIST,
261                                          XA_ATOM, 32, data, num - 1);
262      }
263    XFree(oldset);
264    free(newset);
265 }
266
267 EAPI void
268 ecore_x_dnd_types_set(Ecore_X_Window win, const char **types, unsigned int num_types)
269 {
270    Ecore_X_Atom      *newset = NULL;
271    unsigned int      i;
272    unsigned char     *data = NULL;
273
274    LOGFN(__FILE__, __LINE__, __FUNCTION__);
275    if (!num_types)
276      {
277         ecore_x_window_prop_property_del(win, ECORE_X_ATOM_XDND_TYPE_LIST);
278      }
279    else
280      {
281         newset = calloc(num_types, sizeof(Ecore_X_Atom));
282         if (!newset) return;
283         data = (unsigned char *)newset;
284         for (i = 0; i < num_types; i++)
285           {
286              newset[i] = ecore_x_atom_get(types[i]);
287              ecore_x_selection_converter_atom_add(newset[i], _ecore_x_dnd_converter_copy);
288           }
289         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_TYPE_LIST,
290                                          XA_ATOM, 32, data, num_types);
291         free(newset);
292      }
293 }
294
295 EAPI void
296 ecore_x_dnd_actions_set(Ecore_X_Window win, Ecore_X_Atom *actions, unsigned int num_actions)
297 {
298    unsigned int      i;
299    unsigned char     *data = NULL;
300
301    LOGFN(__FILE__, __LINE__, __FUNCTION__);
302    if (!num_actions)
303      {
304         ecore_x_window_prop_property_del(win, ECORE_X_ATOM_XDND_ACTION_LIST);
305      }
306    else
307      {
308         data = (unsigned char *)actions;
309         for (i = 0; i < num_actions; i++)
310           {
311              ecore_x_selection_converter_atom_add(actions[i], _ecore_x_dnd_converter_copy);
312           }
313         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_ACTION_LIST,
314                                          XA_ATOM, 32, data, num_actions);
315      }
316 }
317
318 Ecore_X_DND_Source *
319 _ecore_x_dnd_source_get(void)
320 {
321    return _source;
322 }
323
324 Ecore_X_DND_Target *
325 _ecore_x_dnd_target_get(void)
326 {
327    return _target;
328 }
329
330 EAPI int
331 ecore_x_dnd_begin(Ecore_X_Window source, unsigned char *data, int size)
332 {
333
334    LOGFN(__FILE__, __LINE__, __FUNCTION__);
335    if (!ecore_x_dnd_version_get(source))
336      return 0;
337
338    /* Take ownership of XdndSelection */
339    if (!ecore_x_selection_xdnd_set(source, data, size))
340      return 0;
341
342    if (_version_cache)
343      {
344         free(_version_cache);
345         _version_cache = NULL;
346         _version_cache_num = 0;
347         _version_cache_alloc = 0;
348      }
349    ecore_x_window_shadow_tree_flush();
350    
351    _source->win = source;
352    ecore_x_window_ignore_set(_source->win, 1);
353    _source->state = ECORE_X_DND_SOURCE_DRAGGING;
354    _source->time = _ecore_x_event_last_time;
355    _source->prev.window = 0;
356
357    /* Default Accepted Action: move */
358    _source->action = ECORE_X_ATOM_XDND_ACTION_MOVE;
359    _source->accepted_action = None;
360    _source->dest = None;
361
362    return 1;
363 }
364
365 EAPI int
366 ecore_x_dnd_drop(void)
367 {
368    XEvent xev;
369    int status = 0;
370
371    LOGFN(__FILE__, __LINE__, __FUNCTION__);
372    if (_source->dest)
373      {
374         xev.xany.type = ClientMessage;
375         xev.xany.display = _ecore_x_disp;
376         xev.xclient.format = 32;
377         xev.xclient.window = _source->dest;
378
379         if (_source->will_accept)
380           {
381              xev.xclient.message_type = ECORE_X_ATOM_XDND_DROP;
382              xev.xclient.data.l[0] = _source->win;
383              xev.xclient.data.l[1] = 0;
384              xev.xclient.data.l[2] = _source->time;
385              XSendEvent(_ecore_x_disp, _source->dest, False, 0, &xev);
386              _source->state = ECORE_X_DND_SOURCE_DROPPED;
387              status = 1;
388           }
389         else
390           {
391              xev.xclient.message_type = ECORE_X_ATOM_XDND_LEAVE;
392              xev.xclient.data.l[0] = _source->win;
393              xev.xclient.data.l[1] = 0;
394              XSendEvent(_ecore_x_disp, _source->dest, False, 0, &xev);
395              _source->state = ECORE_X_DND_SOURCE_IDLE;
396           }
397      }
398    else
399      {
400         /* Dropping on nothing */
401         ecore_x_selection_xdnd_clear();
402         _source->state = ECORE_X_DND_SOURCE_IDLE;
403      }
404    ecore_x_window_ignore_set(_source->win, 0);
405
406    _source->prev.window = 0;
407
408    return status;
409 }
410
411 EAPI void
412 ecore_x_dnd_send_status(int will_accept, int suppress, Ecore_X_Rectangle rectangle, Ecore_X_Atom action)
413 {
414    XEvent xev;
415
416    if (_target->state == ECORE_X_DND_TARGET_IDLE)
417      return;
418
419    LOGFN(__FILE__, __LINE__, __FUNCTION__);
420    memset(&xev, 0, sizeof(XEvent));
421
422    _target->will_accept = will_accept;
423
424    xev.xclient.type = ClientMessage;
425    xev.xclient.display = _ecore_x_disp;
426    xev.xclient.message_type = ECORE_X_ATOM_XDND_STATUS;
427    xev.xclient.format = 32;
428    xev.xclient.window = _target->source;
429
430    xev.xclient.data.l[0] = _target->win;
431    xev.xclient.data.l[1] = 0;
432    if (will_accept)
433      xev.xclient.data.l[1] |= 0x1UL;
434    if (!suppress)
435      xev.xclient.data.l[1] |= 0x2UL;
436
437    /* Set rectangle information */
438    xev.xclient.data.l[2] = rectangle.x;
439    xev.xclient.data.l[2] <<= 16;
440    xev.xclient.data.l[2] |= rectangle.y;
441    xev.xclient.data.l[3] = rectangle.width;
442    xev.xclient.data.l[3] <<= 16;
443    xev.xclient.data.l[3] |= rectangle.height;
444
445    if (will_accept)
446      {
447         xev.xclient.data.l[4] = action;
448         _target->accepted_action = action;
449      }
450    else
451      {
452         xev.xclient.data.l[4] = None;
453         _target->accepted_action = action;
454      }
455
456    XSendEvent(_ecore_x_disp, _target->source, False, 0, &xev);
457 }
458
459 EAPI void
460 ecore_x_dnd_send_finished(void)
461 {
462    XEvent   xev;
463
464    if (_target->state == ECORE_X_DND_TARGET_IDLE)
465      return;
466
467    LOGFN(__FILE__, __LINE__, __FUNCTION__);
468    xev.xany.type = ClientMessage;
469    xev.xany.display = _ecore_x_disp;
470    xev.xclient.message_type = ECORE_X_ATOM_XDND_FINISHED;
471    xev.xclient.format = 32;
472    xev.xclient.window = _target->source;
473
474    xev.xclient.data.l[0] = _target->win;
475    xev.xclient.data.l[1] = 0;
476    xev.xclient.data.l[2] = 0;
477    if (_target->will_accept)
478      {
479         xev.xclient.data.l[1] |= 0x1UL;
480         xev.xclient.data.l[2] = _target->accepted_action;
481      }
482    XSendEvent(_ecore_x_disp, _target->source, False, 0, &xev);
483
484    _target->state = ECORE_X_DND_TARGET_IDLE;
485 }
486
487 void
488 ecore_x_dnd_source_action_set(Ecore_X_Atom action)
489 {
490    _source->action = action;
491    if (_source->prev.window)
492      _ecore_x_dnd_drag(_source->prev.window, _source->prev.x, _source->prev.y);
493 }
494
495 Ecore_X_Atom
496 ecore_x_dnd_source_action_get(void)
497 {
498    return _source->action;
499 }
500
501 void
502 _ecore_x_dnd_drag(Ecore_X_Window root, int x, int y)
503 {
504    XEvent          xev;
505    Ecore_X_Window  win;
506    Ecore_X_Window *skip;
507    int             num;
508
509    if (_source->state != ECORE_X_DND_SOURCE_DRAGGING)
510      return;
511
512    /* Preinitialize XEvent struct */
513    memset(&xev, 0, sizeof(XEvent));
514    xev.xany.type = ClientMessage;
515    xev.xany.display = _ecore_x_disp;
516    xev.xclient.format = 32;
517
518    /* Attempt to find a DND-capable window under the cursor */
519    skip = ecore_x_window_ignore_list(&num);
520 // WARNING - this function is HEAVY. it goes to and from x a LOT walking the
521 // window tree - use the SHADOW version - makes a 1-off tree copy, then uses
522 // that instead.
523 //   win = ecore_x_window_at_xy_with_skip_get(x, y, skip, num);
524    win = ecore_x_window_shadow_tree_at_xy_with_skip_get(root, x, y, skip, num);
525
526 // NOTE: This now uses the shadow version to find parent windows
527 //   while ((win) && !(ecore_x_dnd_version_get(win)))
528 //     win = ecore_x_window_parent_get(win);
529    while ((win) && !(ecore_x_dnd_version_get(win)))
530      win = ecore_x_window_shadow_parent_get(root, win);
531
532    /* Send XdndLeave to current destination window if we have left it */
533    if ((_source->dest) && (win != _source->dest))
534      {
535         xev.xclient.window = _source->dest;
536         xev.xclient.message_type = ECORE_X_ATOM_XDND_LEAVE;
537         xev.xclient.data.l[0] = _source->win;
538         xev.xclient.data.l[1] = 0;
539
540         XSendEvent(_ecore_x_disp, _source->dest, False, 0, &xev);
541         _source->suppress = 0;
542      }
543
544    if (win)
545      {
546         int x1, x2, y1, y2;
547
548         _source->version = MIN(ECORE_X_DND_VERSION,
549                              ecore_x_dnd_version_get(win));
550         if (win != _source->dest)
551           {
552              int i;
553              unsigned char *data;
554              Ecore_X_Atom *types;
555
556              ecore_x_window_prop_property_get(_source->win, ECORE_X_ATOM_XDND_TYPE_LIST,
557                                               XA_ATOM, 32, &data, &num);
558              types = (Ecore_X_Atom *)data;
559
560              /* Entered new window, send XdndEnter */
561              xev.xclient.window = win;
562              xev.xclient.message_type = ECORE_X_ATOM_XDND_ENTER;
563              xev.xclient.data.l[0] = _source->win;
564              xev.xclient.data.l[1] = 0;
565              if (num > 3)
566                xev.xclient.data.l[1] |= 0x1UL;
567              else
568                xev.xclient.data.l[1] &= 0xfffffffeUL;
569              xev.xclient.data.l[1] |= ((unsigned long) _source->version) << 24;
570
571              for (i = 2; i < 5; i++)
572                xev.xclient.data.l[i] = 0;
573              for (i = 0; i < MIN(num, 3); ++i)
574                xev.xclient.data.l[i + 2] = types[i];
575              XFree(data);
576              XSendEvent(_ecore_x_disp, win, False, 0, &xev);
577              _source->await_status = 0;
578              _source->will_accept = 0;
579           }
580
581         /* Determine if we're still in the rectangle from the last status */
582         x1 = _source->rectangle.x;
583         x2 = _source->rectangle.x + _source->rectangle.width;
584         y1 = _source->rectangle.y;
585         y2 = _source->rectangle.y + _source->rectangle.height;
586
587         if ((!_source->await_status) ||
588             (!_source->suppress) ||
589             ((x < x1) || (x > x2) || (y < y1) || (y > y2)))
590           {
591              xev.xclient.window = win;
592              xev.xclient.message_type = ECORE_X_ATOM_XDND_POSITION;
593              xev.xclient.data.l[0] = _source->win;
594              xev.xclient.data.l[1] = 0; /* Reserved */
595              xev.xclient.data.l[2] = ((x << 16) & 0xffff0000) | (y & 0xffff);
596              xev.xclient.data.l[3] = _source->time; /* Version 1 */
597              xev.xclient.data.l[4] = _source->action; /* Version 2, Needs to be pre-set */
598              XSendEvent(_ecore_x_disp, win, False, 0, &xev);
599
600              _source->await_status = 1; 
601           }
602      }
603
604    _source->prev.x = x;
605    _source->prev.y = y;
606    _source->prev.window = root;
607    _source->dest = win;
608 }