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