1 #ifdef HAVE_CONFIG_H
2 # include "elementary_config.h"
3 #endif
4 
5 #include <Elementary.h>
6 
7 #include "elm_priv.h"
8 
9 #ifdef ISCOMFITOR
10 # define STR(X) #X
11 # define STUPID(X) STR(X)
12 # define TTDBG(x...) fprintf(stderr, STUPID(__LINE__)": " x)
13 #else
14 # define TTDBG(X...)
15 #endif
16 
17 static const char _tooltip_key[] = "_elm_tooltip";
18 
19 #define ELM_TOOLTIP_GET_OR_RETURN(tt, obj, ...)         \
20   Elm_Tooltip *tt;                                      \
21   do                                                    \
22     {                                                   \
23        if (!(obj))                                      \
24          {                                              \
25             CRI("Null pointer: " #obj);            \
26             return __VA_ARGS__;                         \
27          }                                              \
28        tt = evas_object_data_get((obj), _tooltip_key);  \
29        if (!tt)                                         \
30          {                                              \
31             ERR("Object does not have tooltip: " #obj); \
32             return __VA_ARGS__;                         \
33          }                                              \
34     }                                                   \
35   while (0)
36 
37 #define ELM_TOOLTIP_GET_OR_CREATE(tt, obj, ...)         \
38   Elm_Tooltip *tt;                                      \
39   do                                                    \
40     {                                                   \
41        if (!(obj))                                      \
42          {                                              \
43             CRI("Null pointer: " #obj);            \
44             return __VA_ARGS__;                         \
45          }                                              \
46        tt = evas_object_data_get((obj), _tooltip_key);  \
47        if (!tt)                                         \
48          {                                              \
49             tt = _elm_tooltip_create((obj));     \
50          }                                              \
51     }                                                   \
52   while (0)
53 
54 struct _Elm_Tooltip
55 {
56    Elm_Tooltip_Content_Cb   func;
57    Evas_Smart_Cb            del_cb;
58    const void              *data;
59    const char              *style;
60    Evas                    *evas, *tt_evas;
61    Evas_Object             *eventarea, *owner;
62    Evas_Object             *tooltip, *content;
63    Evas_Object             *tt_win;
64    Ecore_Timer             *show_timer;
65    Ecore_Timer             *hide_timer;
66    Ecore_Job               *reconfigure_job;
67    Evas_Coord               mouse_x, mouse_y;
68    struct
69      {
70         Evas_Coord            x, y, bx, by;
71      } pad;
72    struct
73      {
74         double                x, y;
75      } rel_pos;
76    Elm_Tooltip_Orient       orient; /** orientation for tooltip */
77    int                      move_freeze;
78    unsigned short           ref;
79 
80    double                   hide_timeout; /* from theme */
81    Eina_Bool                visible_lock:1;
82    Eina_Bool                changed_style:1;
83    Eina_Bool                free_size : 1;
84    Eina_Bool                unset_me : 1;
85 };
86 
87 static void _elm_tooltip_reconfigure(Elm_Tooltip *tt);
88 static void _elm_tooltip_reconfigure_job_start(Elm_Tooltip *tt);
89 static void _elm_tooltip_reconfigure_job_stop(Elm_Tooltip *tt);
90 static void _elm_tooltip_hide_anim_start(Elm_Tooltip *tt);
91 static void _elm_tooltip_hide_anim_stop(Elm_Tooltip *tt);
92 static void _elm_tooltip_show_timer_stop(Elm_Tooltip *tt);
93 static void _elm_tooltip_hide(Elm_Tooltip *tt);
94 static void _elm_tooltip_data_clean(Elm_Tooltip *tt);
95 static void _elm_tooltip_unset(Elm_Tooltip *tt);
96 
97 static void
_elm_tooltip_content_changed_hints_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)98 _elm_tooltip_content_changed_hints_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
99 {
100    _elm_tooltip_reconfigure_job_start(data);
101    TTDBG("HINTS CHANGED\n");
102 }
103 
104 static void
_elm_tooltip_content_del_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)105 _elm_tooltip_content_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
106 {
107    Elm_Tooltip *tt = data;
108    tt->content = NULL;
109    tt->visible_lock = EINA_FALSE;
110    if (tt->tooltip) _elm_tooltip_hide(tt);
111 }
112 
113 static void
_elm_tooltip_obj_move_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)114 _elm_tooltip_obj_move_cb(void *data, Evas *e  EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info  EINA_UNUSED)
115 {
116    Elm_Tooltip *tt = data;
117    _elm_tooltip_reconfigure_job_start(tt);
118    TTDBG("TT MOVED\n");
119 }
120 
121 static void
_elm_tooltip_obj_resize_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)122 _elm_tooltip_obj_resize_cb(void *data, Evas *e  EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info  EINA_UNUSED)
123 {
124    Elm_Tooltip *tt = data;
125    _elm_tooltip_reconfigure_job_start(tt);
126    TTDBG("TT RESIZE\n");
127 }
128 
129 static void
_elm_tooltip_obj_mouse_move_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event_info)130 _elm_tooltip_obj_mouse_move_cb(void *data, Evas *e  EINA_UNUSED,
131                                Evas_Object *obj EINA_UNUSED, void *event_info)
132 {
133    Elm_Tooltip *tt = data;
134    Evas_Event_Mouse_Move *ev = event_info;
135 
136    if (tt->mouse_x || tt->mouse_y)
137      {
138         if ((abs(ev->cur.output.x - tt->mouse_x) < 3) &&
139             (abs(ev->cur.output.y - tt->mouse_y) < 3))
140           {
141              TTDBG("MOUSE MOVE REJECTED!\n");
142              return;
143           }
144      }
145    tt->mouse_x = ev->cur.output.x;
146    tt->mouse_y = ev->cur.output.y;
147    TTDBG("MOUSE MOVED\n");
148    _elm_tooltip_reconfigure_job_start(tt);
149 }
150 
151 static void
_elm_tooltip_show(Elm_Tooltip * tt)152 _elm_tooltip_show(Elm_Tooltip *tt)
153 {
154    _elm_tooltip_show_timer_stop(tt);
155    _elm_tooltip_hide_anim_stop(tt);
156 
157    TTDBG("TT SHOW\n");
158    if (tt->tooltip)
159      {
160         _elm_tooltip_reconfigure_job_start(tt);
161         TTDBG("RECURSIVE JOB\n");
162         return;
163      }
164    if (tt->free_size)
165      {
166         tt->tt_win = elm_win_add(elm_win_get(tt->owner), "tooltip", ELM_WIN_TOOLTIP);
167         elm_win_override_set(tt->tt_win, EINA_TRUE);
168         tt->tt_evas = evas_object_evas_get(tt->tt_win);
169         tt->tooltip = edje_object_add(tt->tt_evas);
170         evas_object_size_hint_weight_set(tt->tooltip, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
171         elm_win_resize_object_add(tt->tt_win, tt->tooltip);
172      }
173    else
174       tt->tooltip = edje_object_add(tt->evas);
175    if (!tt->tooltip) return;
176    evas_object_pass_events_set(tt->tooltip, EINA_TRUE);
177 
178    if (tt->free_size)
179      evas_object_layer_set(tt->tooltip, ELM_OBJECT_LAYER_TOOLTIP);
180 
181    evas_object_event_callback_add
182      (tt->eventarea, EVAS_CALLBACK_MOVE, _elm_tooltip_obj_move_cb, tt);
183    evas_object_event_callback_add
184      (tt->eventarea, EVAS_CALLBACK_RESIZE, _elm_tooltip_obj_resize_cb, tt);
185 
186    if (tt->move_freeze == 0)
187      {
188       //No movement of tooltip upon mouse move if orientation set
189       if ((tt->orient <= ELM_TOOLTIP_ORIENT_NONE) || (tt->orient >= ELM_TOOLTIP_ORIENT_LAST))
190         {
191            evas_object_event_callback_add(tt->eventarea,
192                                           EVAS_CALLBACK_MOUSE_MOVE,
193                                           _elm_tooltip_obj_mouse_move_cb, tt);
194         }
195      }
196    tt->changed_style = EINA_TRUE;
197    _elm_tooltip_reconfigure_job_start(tt);
198 }
199 
200 static void
_elm_tooltip_content_del(Elm_Tooltip * tt)201 _elm_tooltip_content_del(Elm_Tooltip *tt)
202 {
203    if (!tt->content) return;
204 
205    TTDBG("CONTENT DEL\n");
206    evas_object_event_callback_del_full
207      (tt->content, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
208       _elm_tooltip_content_changed_hints_cb, tt);
209    evas_object_event_callback_del_full
210      (tt->content, EVAS_CALLBACK_DEL,
211       _elm_tooltip_content_del_cb, tt);
212    evas_object_hide(tt->content);
213    ELM_SAFE_FREE(tt->content, evas_object_del);
214 }
215 
216 static void
_elm_tooltip_hide(Elm_Tooltip * tt)217 _elm_tooltip_hide(Elm_Tooltip *tt)
218 {
219    Evas_Object *del;
220    TTDBG("TT HIDE\n");
221    _elm_tooltip_show_timer_stop(tt);
222    _elm_tooltip_hide_anim_stop(tt);
223    _elm_tooltip_reconfigure_job_stop(tt);
224 
225    if (!tt->tooltip) return;
226    if (tt->visible_lock) return;
227 
228    _elm_tooltip_content_del(tt);
229 
230    evas_object_event_callback_del_full
231      (tt->eventarea, EVAS_CALLBACK_MOVE, _elm_tooltip_obj_move_cb, tt);
232    evas_object_event_callback_del_full
233      (tt->eventarea, EVAS_CALLBACK_RESIZE, _elm_tooltip_obj_resize_cb, tt);
234    evas_object_event_callback_del_full
235      (tt->eventarea, EVAS_CALLBACK_MOUSE_MOVE, (Evas_Object_Event_Cb)_elm_tooltip_obj_mouse_move_cb, tt);
236 
237    del = tt->tt_win ? tt->tt_win : tt->tooltip;
238 
239    tt->tt_win = NULL;
240    tt->tt_evas = NULL;
241    tt->tooltip = NULL;
242    evas_object_del(del);
243 }
244 
245 static void
_elm_tooltip_reconfigure_job(void * data)246 _elm_tooltip_reconfigure_job(void *data)
247 {
248    Elm_Tooltip *tt = data;
249    tt->reconfigure_job = NULL;
250    _elm_tooltip_reconfigure(data);
251 }
252 
253 static void
_elm_tooltip_reconfigure_job_stop(Elm_Tooltip * tt)254 _elm_tooltip_reconfigure_job_stop(Elm_Tooltip *tt)
255 {
256    ELM_SAFE_FREE(tt->reconfigure_job, ecore_job_del);
257 }
258 
259 static void
_elm_tooltip_reconfigure_job_start(Elm_Tooltip * tt)260 _elm_tooltip_reconfigure_job_start(Elm_Tooltip *tt)
261 {
262    ecore_job_del(tt->reconfigure_job);
263    tt->reconfigure_job = ecore_job_add(_elm_tooltip_reconfigure_job, tt);
264 }
265 
266 static Eina_Bool
_elm_tooltip_hide_anim_cb(void * data)267 _elm_tooltip_hide_anim_cb(void *data)
268 {
269    Elm_Tooltip *tt = data;
270    tt->hide_timer = NULL;
271    _elm_tooltip_hide(tt);
272    return EINA_FALSE;
273 }
274 
275 static void
_elm_tooltip_hide_anim_start(Elm_Tooltip * tt)276 _elm_tooltip_hide_anim_start(Elm_Tooltip *tt)
277 {
278    double extra = 0;
279    if (tt->hide_timer) return;
280    TTDBG("HIDE START\n");
281    /* hide slightly faster when in window mode to look less stupid */
282    if ((tt->hide_timeout > 0) && tt->tt_win) extra = 0.1;
283 
284    if (elm_widget_is_legacy(tt->owner))
285      edje_object_signal_emit(tt->tooltip, "elm,action,hide", "elm");
286    else
287      edje_object_signal_emit(tt->tooltip, "efl,action,hide", "efl");
288    tt->hide_timer = ecore_timer_add
289      (tt->hide_timeout - extra, _elm_tooltip_hide_anim_cb, tt);
290 }
291 
292 static void
_elm_tooltip_hide_anim_stop(Elm_Tooltip * tt)293 _elm_tooltip_hide_anim_stop(Elm_Tooltip *tt)
294 {
295    if (!tt->hide_timer) return;
296    if (tt->tooltip)
297      {
298         if (elm_widget_is_legacy(tt->owner))
299           edje_object_signal_emit(tt->tooltip, "elm,action,show", "elm");
300         else
301           edje_object_signal_emit(tt->tooltip, "efl,action,show", "efl");
302      }
303 
304    ELM_SAFE_FREE(tt->hide_timer, ecore_timer_del);
305 }
306 
307 static void
_elm_tooltip_reconfigure_orient(Elm_Tooltip * tt,Evas_Coord ox,Evas_Coord oy,Evas_Coord ow,Evas_Coord oh,Evas_Coord tw,Evas_Coord th,Evas_Coord cw,Evas_Coord ch)308 _elm_tooltip_reconfigure_orient(Elm_Tooltip *tt,
309                                 Evas_Coord ox, Evas_Coord oy, Evas_Coord ow, Evas_Coord oh,
310                                 Evas_Coord tw, Evas_Coord th, Evas_Coord cw, Evas_Coord ch)
311 {
312    Evas_Coord mx, my;
313    Evas_Coord dx, dy;
314    Evas_Coord tcw, tch;
315    Evas_Coord px, py;
316 
317    switch (tt->orient)
318      {
319       case ELM_TOOLTIP_ORIENT_TOP_LEFT:
320          mx = ox - tw;
321          my = oy - th;
322          tt->rel_pos.x = 1.1;
323          tt->rel_pos.y = 1.1;
324          break;
325       case ELM_TOOLTIP_ORIENT_TOP:
326          mx = ox + ((ow - tw) / 2);
327          my = oy - th;
328          tt->rel_pos.x = 0.5;
329          tt->rel_pos.y = 1.1;
330          break;
331       case ELM_TOOLTIP_ORIENT_TOP_RIGHT:
332          mx = ox + ow;
333          my = oy - th;
334          tt->rel_pos.x = -1.1;
335          tt->rel_pos.y = 1.1;
336          break;
337       case ELM_TOOLTIP_ORIENT_LEFT:
338          mx = ox - tw;
339          my = oy + ((oh - th) / 2);
340          tt->rel_pos.x = 1.1;
341          tt->rel_pos.y = 0.5;
342          break;
343       case ELM_TOOLTIP_ORIENT_CENTER:
344          mx = ox + ((ow - tw) / 2);
345          my = oy + ((oh - th) / 2);
346          tt->rel_pos.x = 0.5;
347          tt->rel_pos.y = 0.5;
348          break;
349       case ELM_TOOLTIP_ORIENT_RIGHT:
350          mx = ox + ow;
351          my = oy + ((oh - th) / 2);
352          tt->rel_pos.x = -1.1;
353          tt->rel_pos.y = 0.5;
354          break;
355       case ELM_TOOLTIP_ORIENT_BOTTOM_LEFT:
356          mx = ox - tw;
357          my = oy + oh;
358          tt->rel_pos.x = 1.1;
359          tt->rel_pos.y = -1.1;
360          break;
361       case ELM_TOOLTIP_ORIENT_BOTTOM:
362          mx = ox + ((ow - tw) / 2);
363          my = oy + oh;
364          tt->rel_pos.x = 0.5;
365          tt->rel_pos.y = -1.1;
366          break;
367       case ELM_TOOLTIP_ORIENT_BOTTOM_RIGHT:
368          mx = ox + ow;
369          my = oy + oh;
370          tt->rel_pos.x = -1.1;
371          tt->rel_pos.y = -1.1;
372          break;
373       default:
374          return;
375      }
376 
377    evas_object_geometry_get(tt->content, NULL, NULL, &tcw, &tch);
378    if (tcw <= 0 || tcw > tw) tcw = tw;
379    if (tch <= 0 || tch > th) tch = th;
380 
381    px = (tw - tcw) / 2;
382    py = (th - tch) / 2;
383 
384    if (mx < 0)
385      {
386         dx = -mx;
387         mx = -(px / 2);
388         if (EINA_DBL_EQ(tt->rel_pos.x, 0.5))
389           {
390              tt->rel_pos.x = 0.5 - dx / (double)tcw;
391              if (tt->rel_pos.x < 0.0) tt->rel_pos.x = 0.0;
392           }
393      }
394    else if (mx + tw > cw)
395      {
396         dx = mx + tw - cw;
397         mx = cw - tw + px / 2;
398         if (EINA_DBL_EQ(tt->rel_pos.x, 0.5))
399           {
400              tt->rel_pos.x = 0.5 + dx / (double)tcw;
401              if (tt->rel_pos.x > 1.0) tt->rel_pos.x = 1.0;
402           }
403      }
404 
405    if (my < 0)
406      {
407         dy = -my;
408         my = -(py / 2);
409         if (EINA_DBL_EQ(tt->rel_pos.y, 0.5))
410           {
411              tt->rel_pos.y = 0.5 - dy / (double)tch;
412              if (tt->rel_pos.y < 0.0) tt->rel_pos.y = 0.0;
413           }
414      }
415    else if (my + th > ch)
416      {
417         dy = my + th - ch;
418         my = ch - th + py / 2;
419         if (EINA_DBL_EQ(tt->rel_pos.y, 0.5))
420           {
421              tt->rel_pos.y = 0.5 + dy / (double)tch;
422              if (tt->rel_pos.y > 1.0) tt->rel_pos.y = 1.0;
423           }
424      }
425 
426    evas_object_move(tt->tooltip, mx, my);
427    evas_object_show(tt->tooltip);
428 }
429 
430 static void
_elm_tooltip_reconfigure(Elm_Tooltip * tt)431 _elm_tooltip_reconfigure(Elm_Tooltip *tt)
432 {
433    Evas_Coord ox, oy, ow, oh, px = 0, py = 0, tx, ty, tw, th;
434    Evas_Coord cx = 0, cy = 0, cw = 0, ch = 0, basex = 0, basey = 0;;
435    Evas_Coord eminw, eminh, ominw, ominh;
436    double rel_x = 0.0, rel_y = 0.0;
437    Eina_Bool inside_eventarea;
438    Eina_Bool new_content = EINA_FALSE;
439 
440    _elm_tooltip_reconfigure_job_stop(tt);
441 
442    if (tt->hide_timer) return;
443    if (!tt->tooltip) return;
444    if (tt->changed_style)
445      {
446         const char *style = tt->style ? tt->style : "default";
447         const char *str;
448         if (_elm_theme_object_set(tt->tt_win ? : tt->owner, tt->tooltip,
449                                   "tooltip", NULL, style) == EFL_UI_THEME_APPLY_ERROR_GENERIC)
450           {
451              ERR("Could not apply the theme to the tooltip! style=%s", style);
452              if (tt->tt_win) evas_object_del(tt->tt_win);
453              else evas_object_del(tt->tooltip);
454              tt->tt_win = NULL;
455              tt->tt_evas = NULL;
456              tt->tooltip = NULL;
457              return;
458           }
459 
460         tt->rel_pos.x = 0;
461         tt->rel_pos.y = 0;
462 
463         tt->pad.x = 0;
464         tt->pad.y = 0;
465         tt->pad.bx = 0;
466         tt->pad.by = 0;
467         tt->hide_timeout = 0.0;
468 
469         str = edje_object_data_get(tt->tooltip, "transparent");
470         if (tt->tt_win)
471           {  /* FIXME: hardcoded here is bad */
472              if (str && (!strcmp(str, "enabled")))
473                {
474                   evas_object_hide(tt->tt_win);
475                   elm_win_alpha_set(tt->tt_win, EINA_TRUE);
476                }
477              else
478                {
479                   evas_object_hide(tt->tt_win);
480                   elm_win_alpha_set(tt->tt_win, EINA_FALSE);
481                }
482           }
483 
484         str = edje_object_data_get(tt->tooltip, "pad_x");
485         if (str) tt->pad.x = atoi(str);
486         str = edje_object_data_get(tt->tooltip, "pad_y");
487         if (str) tt->pad.y = atoi(str);
488 
489         str = edje_object_data_get(tt->tooltip, "pad_border_x");
490         if (str) tt->pad.bx = atoi(str);
491         str = edje_object_data_get(tt->tooltip, "pad_border_y");
492         if (str) tt->pad.by = atoi(str);
493 
494         str = edje_object_data_get(tt->tooltip, "hide_timeout");
495         if (str)
496           {
497              tt->hide_timeout = _elm_atof(str);
498              if (tt->hide_timeout < 0.0) tt->hide_timeout = 0.0;
499           }
500 
501         tt->changed_style = EINA_FALSE;
502         if (tt->tooltip)
503           {
504              if (elm_widget_is_legacy(tt->owner))
505                edje_object_part_swallow(tt->tooltip, "elm.swallow.content",
506                                         tt->content);
507              else
508                edje_object_part_swallow(tt->tooltip, "efl.content",
509                                         tt->content);
510           }
511 
512         if (elm_widget_is_legacy(tt->owner))
513           edje_object_signal_emit(tt->tooltip, "elm,action,show", "elm");
514         else
515           edje_object_signal_emit(tt->tooltip, "efl,action,show", "efl");
516      }
517 
518    if (!tt->content)
519      {
520         tt->ref++;
521         tt->content = tt->func((void *)tt->data, tt->owner, tt->tt_win ? : tt->owner);
522         tt->ref--;
523         if (tt->unset_me)
524           {
525              _elm_tooltip_unset(tt);
526              return;
527           }
528 
529         if (!tt->content)
530           {
531              WRN("could not create tooltip content!");
532              if (tt->tt_win) evas_object_del(tt->tt_win);
533              else evas_object_del(tt->tooltip);
534 
535              tt->tt_win = NULL;
536              tt->tt_evas = NULL;
537              tt->tooltip = NULL;
538              return;
539           }
540 
541         if (elm_widget_is_legacy(tt->owner))
542           edje_object_part_swallow
543              (tt->tooltip, "elm.swallow.content", tt->content);
544         else
545           edje_object_part_swallow
546              (tt->tooltip, "efl.content", tt->content);
547         new_content = EINA_TRUE;
548         evas_object_event_callback_add(tt->content, EVAS_CALLBACK_DEL,
549            _elm_tooltip_content_del_cb, tt);
550 
551         /* tooltip has to use layer tooltip */
552         evas_object_layer_set(tt->tooltip, ELM_OBJECT_LAYER_TOOLTIP);
553      }
554    TTDBG("*******RECALC\n");
555    evas_object_size_hint_combined_min_get(tt->content, &ominw, &ominh);
556    /* force size hints to update */
557    if ((!ominw) || (!ominh))
558      {
559         evas_object_smart_need_recalculate_set(tt->content, 1);
560         evas_object_smart_calculate(tt->content);
561         evas_object_size_hint_combined_min_get(tt->content, &ominw, &ominh);
562      }
563    if (new_content)
564      evas_object_event_callback_add(tt->content, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
565            _elm_tooltip_content_changed_hints_cb, tt);
566    edje_object_size_min_get(tt->tooltip, &eminw, &eminh);
567 
568    if (eminw && (ominw < eminw)) ominw = eminw;
569    if (eminh && (ominh < eminh)) ominh = eminh;
570 
571    edje_object_size_min_restricted_calc(tt->tooltip, &tw, &th, ominw, ominh);
572    TTDBG("TTSIZE:  tw=%d,th=%d,ominw=%d,ominh=%d\n", tw, th, ominw, ominh);
573 
574    if (tt->tt_win)
575      {
576         elm_win_screen_size_get(elm_widget_top_get(tt->owner),
577                                 &basex, &basey, &cw, &ch);
578         elm_win_screen_position_get(elm_widget_top_get(tt->owner),
579                                     &cx, &cy);
580         evas_pointer_canvas_xy_get(tt->evas, &px, &py);
581         cx -= basex;
582         cy -= basey;
583      }
584    else
585      {
586         evas_output_size_get(tt->evas, &cw, &ch);
587         evas_pointer_canvas_xy_get(tt->evas, &px, &py);
588      }
589    TTDBG("SCREEN:  cw=%d,ch=%d\n", cw, ch);
590 
591    evas_object_geometry_get(tt->eventarea, &ox, &oy, &ow, &oh);
592    /* win reports its screen position for x/y;
593     * reset to 0 since we expect canvas coords here
594     */
595    if (efl_isa(tt->eventarea, EFL_UI_WIN_CLASS))
596      ox = oy = 0;
597    TTDBG("EVENTAREA:  ox=%d,oy=%d,ow=%d,oh=%d\n", ox, oy, ow, oh);
598 
599    inside_eventarea = ((px >= ox) && (py >= oy) &&
600                        (px <= (ox + ow)) && (py <= (oy + oh)));
601 
602    if (inside_eventarea)
603      {
604         /* try to position bottom right corner at pointer */
605         tx = cx + px - tw - 1;
606         ty = cy + py - th - 1;
607         if (tx < 0)
608           {
609              tx = 0;
610              if (ELM_RECTS_INTERSECT(tx, ty, tw, th, (cx + ox), (cy + oy),
611                                      ow, oh))
612                tx = cx + ox + ow;
613           }
614         if (ty < 0)
615           {
616              ty = 0;
617              if (ELM_RECTS_INTERSECT(tx, ty, tw, th, (cx + ox), (cy + oy),
618                                      ow, oh))
619                ty = cy + oy + oh;
620           }
621         if ((tx + tw) > cw) tx = cw - tw;
622         if ((ty + th) > ch) ty = ch - th;
623         if (tx  < 0) tx = 0;
624         if (ty  < 0) ty = 0;
625      }
626    else
627      {
628         /* try centered on middle of eventarea */
629         tx = cx + ox + (ow / 2) - (tw / 2);
630         if (py < oy)
631           {
632              ty = cx + oy - th;
633              if (ty < cx) ty = cx + oy + oh;
634              if ((ty + th) > (cx + ch)) ty = cy + ch - th;
635           }
636         else
637           {
638              ty = cy + oy + oh;
639              if (ty < cy) ty = cy;
640              if ((ty + th) > (cy + ch)) ty = cy + oy - th;
641           }
642         if (tx < cx) tx = cx;
643         if ((tx + th) > (cx + cw)) tx = cy + cw - tw;
644 
645      }
646    // jf tt is over the pointer even after positiong then screen
647    // limiting, just use the poitner dumbly and choose to theleft or right
648    // above or below depending which has more space/ we're in a mess
649    // anyway
650    if (ELM_RECTS_INTERSECT(tx, ty, tw, th, (cx + px), (cy + py), 1, 1))
651      {
652         if ((px + cx) > (cw / 2)) tx = cx + px - 1 - tw;
653         else tx = cx + px + 1;
654         if ((py + cy) > (ch / 2)) ty = cy + py - 1 - th;
655         else ty = cy + py + 1;
656      }
657 
658    if (inside_eventarea)
659      {
660         rel_x = (px - (tx - cx)) / (double)tw;
661         rel_y = (py - (ty - cy)) / (double)th;
662      }
663    else
664      {
665         rel_x = (ox + (ow / 2) - (tx - cx)) / (double)tw;
666         rel_y = (oy + (oh / 2) - (ty - cy)) / (double)th;
667      }
668 
669    tx += basex;
670    ty += basey;
671    // XXX: if this is a window for toolkit this relies on abs positioning
672    // and this is not portable to wayland so we need relative positioning
673    // implemented lower down for this
674    evas_object_geometry_set(tt->tt_win ? : tt->tooltip, tx, ty, tw, th);
675    TTDBG("FINAL: tx=%d,ty=%d,tw=%d,th=%d\n", tx, ty, tw, th);
676    evas_object_show(tt->tooltip);
677 
678 #define FDIF(a, b) (fabs((a) - (b)) > 0.0001)
679    if ((FDIF(rel_x, tt->rel_pos.x)) || (FDIF(rel_y, tt->rel_pos.y)))
680      {
681         Edje_Message_Float_Set *msg;
682 
683         msg = alloca(sizeof(Edje_Message_Float_Set) + sizeof(double));
684         msg->count = 2;
685 
686         tt->rel_pos.x = rel_x;
687         tt->rel_pos.y = rel_y;
688 
689         _elm_tooltip_reconfigure_orient(tt,
690                                         cx + ox, cy + oy, ow, oh,
691                                         tw, th, cw, ch);
692 
693         msg->val[0] = tt->rel_pos.x;
694         msg->val[1] = tt->rel_pos.y;
695 
696         edje_object_message_send(tt->tooltip, EDJE_MESSAGE_FLOAT_SET, 1, msg);
697      }
698 #undef FDIF
699    if (tt->tt_win) evas_object_show(tt->tt_win);
700 }
701 
702 static void
_elm_tooltip_show_timer_stop(Elm_Tooltip * tt)703 _elm_tooltip_show_timer_stop(Elm_Tooltip *tt)
704 {
705    if (!tt->show_timer) return;
706    ELM_SAFE_FREE(tt->show_timer, ecore_timer_del);
707 }
708 
709 static Eina_Bool
_elm_tooltip_timer_show_cb(void * data)710 _elm_tooltip_timer_show_cb(void *data)
711 {
712    Elm_Tooltip *tt = data;
713    tt->show_timer = NULL;
714    _elm_tooltip_show(tt);
715    return ECORE_CALLBACK_CANCEL;
716 }
717 
718 static void
_elm_tooltip_obj_mouse_in_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)719 _elm_tooltip_obj_mouse_in_cb(void *data, Evas *e  EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info  EINA_UNUSED)
720 {
721    Elm_Tooltip *tt = data;
722 
723    _elm_tooltip_hide_anim_stop(tt);
724 
725    if ((tt->show_timer) || (tt->tooltip)) return;
726 
727    tt->show_timer = ecore_timer_add(_elm_config->tooltip_delay, _elm_tooltip_timer_show_cb, tt);
728    TTDBG("MOUSE IN\n");
729 }
730 
731 static void
_elm_tooltip_obj_mouse_out_cb(Elm_Tooltip * tt,Evas * e EINA_UNUSED,Evas_Object * obj EINA_UNUSED,Evas_Event_Mouse_Out * event EINA_UNUSED)732 _elm_tooltip_obj_mouse_out_cb(Elm_Tooltip *tt, Evas *e  EINA_UNUSED, Evas_Object *obj EINA_UNUSED, Evas_Event_Mouse_Out *event EINA_UNUSED)
733 {
734    if (tt->visible_lock) return;
735 
736    if (!tt->tooltip)
737      {
738         _elm_tooltip_show_timer_stop(tt);
739         return;
740      }
741    _elm_tooltip_hide_anim_start(tt);
742    TTDBG("MOUSE OUT\n");
743 }
744 
745 static void _elm_tooltip_obj_free_cb(void *data, Evas *e  EINA_UNUSED, Evas_Object *obj, void *event_info  EINA_UNUSED);
746 
747 static void
_elm_tooltip_unset(Elm_Tooltip * tt)748 _elm_tooltip_unset(Elm_Tooltip *tt)
749 {
750    if (tt->ref > 0)
751      {
752         tt->unset_me = EINA_TRUE;
753         return;
754      }
755    tt->visible_lock = EINA_FALSE;
756    _elm_tooltip_hide(tt);
757    _elm_tooltip_data_clean(tt);
758 
759    if (tt->eventarea)
760      {
761         evas_object_event_callback_del_full
762           (tt->eventarea, EVAS_CALLBACK_MOUSE_IN,
763            _elm_tooltip_obj_mouse_in_cb, tt);
764         evas_object_event_callback_del_full
765           (tt->eventarea, EVAS_CALLBACK_MOUSE_OUT,
766            (Evas_Object_Event_Cb)_elm_tooltip_obj_mouse_out_cb, tt);
767         evas_object_event_callback_del_full
768           (tt->eventarea, EVAS_CALLBACK_FREE, _elm_tooltip_obj_free_cb, tt);
769 
770         evas_object_data_del(tt->eventarea, _tooltip_key);
771      }
772    if (tt->owner)
773      {
774         evas_object_event_callback_del_full
775           (tt->owner, EVAS_CALLBACK_FREE, _elm_tooltip_obj_free_cb, tt);
776         elm_widget_tooltip_del(tt->owner, tt);
777      }
778 
779    eina_stringshare_del(tt->style);
780    free(tt);
781 }
782 
783 static void
_elm_tooltip_obj_free_cb(void * data,Evas * e EINA_UNUSED,Evas_Object * obj,void * event_info EINA_UNUSED)784 _elm_tooltip_obj_free_cb(void *data, Evas *e  EINA_UNUSED, Evas_Object *obj, void *event_info  EINA_UNUSED)
785 {
786    Elm_Tooltip *tt = data;
787    if (tt->eventarea == obj) tt->eventarea = NULL;
788    if (tt->owner == obj) tt->owner = NULL;
789    _elm_tooltip_unset(tt);
790 }
791 
792 static Elm_Tooltip *
_elm_tooltip_create(Evas_Object * eventarea)793 _elm_tooltip_create(Evas_Object *eventarea)
794 {
795    Elm_Tooltip *tt = NULL;
796 
797    tt = ELM_NEW(Elm_Tooltip);
798    if (!tt) return NULL;
799 
800    tt->eventarea = eventarea;
801    tt->evas = evas_object_evas_get(eventarea);
802    evas_object_data_set(eventarea, _tooltip_key, tt);
803 
804    evas_object_event_callback_add(eventarea, EVAS_CALLBACK_MOUSE_IN,
805       _elm_tooltip_obj_mouse_in_cb, tt);
806    evas_object_event_callback_add(eventarea, EVAS_CALLBACK_MOUSE_OUT,
807       (Evas_Object_Event_Cb)_elm_tooltip_obj_mouse_out_cb, tt);
808    evas_object_event_callback_add(eventarea, EVAS_CALLBACK_FREE,
809       _elm_tooltip_obj_free_cb, tt);
810 
811    return tt;
812 }
813 
814 static void
_tooltip_label_style_set(Evas_Object * obj,Evas_Object * label)815 _tooltip_label_style_set(Evas_Object *obj, Evas_Object *label)
816 {
817    ELM_TOOLTIP_GET_OR_RETURN(tt, obj);
818    char buf[100] = {0};
819    const char *style = tt->style ? tt->style : "default";
820 
821    sprintf(buf, "tooltip/%s", style);
822    if (!elm_object_style_set(label, buf))
823      {
824         WRN("Failed to set tooltip label style: %s, reverting to old style",
825             buf);
826         elm_object_style_set(label, "tooltip"); //XXX: remove it in EFL 2.0
827      }
828 }
829 
830 static Evas_Object *
_elm_tooltip_label_create(void * data,Evas_Object * obj,Evas_Object * tooltip)831 _elm_tooltip_label_create(void *data, Evas_Object *obj, Evas_Object *tooltip)
832 {
833    Evas_Object *label = elm_label_add(tooltip);
834    if (!label)
835      return NULL;
836    _tooltip_label_style_set(obj, label);
837    elm_object_text_set(label, data);
838    return label;
839 }
840 
841 static Evas_Object *
_elm_tooltip_trans_label_create(void * data,Evas_Object * obj,Evas_Object * tooltip)842 _elm_tooltip_trans_label_create(void *data, Evas_Object *obj, Evas_Object *tooltip)
843 {
844    Evas_Object *label = elm_label_add(tooltip);
845    const char **text = data;
846    if (!label)
847      return NULL;
848    _tooltip_label_style_set(obj, label);
849    elm_object_domain_translatable_text_set(label, text[0], text[1]);
850    return label;
851 }
852 
853 static void
_elm_tooltip_label_del_cb(void * data,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)854 _elm_tooltip_label_del_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
855 {
856    eina_stringshare_del(data);
857 }
858 
859 static void
_elm_tooltip_trans_label_del_cb(void * data,Evas_Object * obj EINA_UNUSED,void * event_info EINA_UNUSED)860 _elm_tooltip_trans_label_del_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
861 {
862    const char **text = data;
863    eina_stringshare_del(text[0]);
864    eina_stringshare_del(text[1]);
865    free(text);
866 }
867 
868 static void
_elm_tooltip_data_clean(Elm_Tooltip * tt)869 _elm_tooltip_data_clean(Elm_Tooltip *tt)
870 {
871    if (tt->del_cb) tt->del_cb((void *)tt->data, tt->owner, NULL);
872    tt->del_cb = NULL;
873    tt->data = NULL;
874 
875    _elm_tooltip_content_del(tt);
876 }
877 
878 EAPI void
elm_object_tooltip_move_freeze_push(Evas_Object * obj)879 elm_object_tooltip_move_freeze_push(Evas_Object *obj)
880 {
881    ELM_TOOLTIP_GET_OR_RETURN(tt, obj);
882 
883    tt->move_freeze++;
884 }
885 
886 EAPI void
elm_object_tooltip_move_freeze_pop(Evas_Object * obj)887 elm_object_tooltip_move_freeze_pop(Evas_Object *obj)
888 {
889    ELM_TOOLTIP_GET_OR_RETURN(tt, obj);
890 
891    tt->move_freeze--;
892    if (tt->move_freeze < 0) tt->move_freeze = 0;
893 }
894 
895 EAPI int
elm_object_tooltip_move_freeze_get(const Evas_Object * obj)896 elm_object_tooltip_move_freeze_get(const Evas_Object *obj)
897 {
898    ELM_TOOLTIP_GET_OR_RETURN(tt, obj, 0);
899 
900    return tt->move_freeze;
901 }
902 
903 EAPI void
elm_object_tooltip_orient_set(Evas_Object * obj,Elm_Tooltip_Orient orient)904 elm_object_tooltip_orient_set(Evas_Object *obj, Elm_Tooltip_Orient orient)
905 {
906    ELM_TOOLTIP_GET_OR_CREATE(tt, obj);
907 
908    if ((orient > ELM_TOOLTIP_ORIENT_NONE) && (orient < ELM_TOOLTIP_ORIENT_LAST))
909      tt->orient = orient;
910    else
911      tt->orient = ELM_TOOLTIP_ORIENT_NONE;
912 }
913 
914 EAPI Elm_Tooltip_Orient
elm_object_tooltip_orient_get(const Evas_Object * obj)915 elm_object_tooltip_orient_get(const Evas_Object *obj)
916 {
917    ELM_TOOLTIP_GET_OR_RETURN(tt, obj, ELM_TOOLTIP_ORIENT_NONE);
918 
919    Elm_Tooltip_Orient orient = ELM_TOOLTIP_ORIENT_NONE;
920 
921    orient = tt->orient;
922    return orient;
923 }
924 
925 /**
926  * Notify tooltip should recalculate its theme.
927  * @internal
928  */
929 void
elm_tooltip_theme(Elm_Tooltip * tt)930 elm_tooltip_theme(Elm_Tooltip *tt)
931 {
932    if (!tt->tooltip) return;
933    tt->changed_style = EINA_TRUE;
934    _elm_tooltip_reconfigure_job_start(tt);
935 }
936 
937 /**
938  * Set the content to be shown in the tooltip object for specific event area.
939  *
940  * Setup the tooltip to object. The object @a eventarea can have only
941  * one tooltip, so any previous tooltip data is removed. @p func(with
942  * @p data) will be called every time that need show the tooltip and
943  * it should return a valid Evas_Object. This object is then managed
944  * fully by tooltip system and is deleted when the tooltip is gone.
945  *
946  * This is an internal function that is used by objects with sub-items
947  * that want to provide different tooltips for each of them. The @a
948  * owner object should be an elm_widget and will be used to track
949  * theme changes and to feed @a func and @a del_cb. The @a eventarea
950  * may be any object and is the one that should be used later on with
951  * elm_object_tooltip apis, such as elm_object_tooltip_hide(),
952  * elm_object_tooltip_show() or elm_object_tooltip_unset().
953  *
954  * @param eventarea the object being attached a tooltip.
955  * @param owner the elm_widget that owns this object, will be used to
956  *        track theme changes and to be used in @a func or @a del_cb.
957  * @param func the function used to create the tooltip contents. The
958  *        @a Evas_Object parameters will receive @a owner as value.
959  * @param data what to provide to @a func as callback data/context.
960  * @param del_cb called when data is not needed anymore, either when
961  *        another callback replaces @p func, the tooltip is unset with
962  *        elm_object_tooltip_unset() or the owner object @a obj
963  *        dies. This callback receives as the first parameter the
964  *        given @a data, and @c event_info is NULL.
965  *
966  * @internal
967  * @ingroup Elm_Tooltips
968  */
969 void
elm_object_sub_tooltip_content_cb_set(Evas_Object * eventarea,Evas_Object * owner,Elm_Tooltip_Content_Cb func,const void * data,Evas_Smart_Cb del_cb)970 elm_object_sub_tooltip_content_cb_set(Evas_Object *eventarea, Evas_Object *owner, Elm_Tooltip_Content_Cb func, const void *data, Evas_Smart_Cb del_cb)
971 {
972    Elm_Tooltip *tt = NULL;
973    Eina_Bool just_created = EINA_TRUE;
974 
975    EINA_SAFETY_ON_NULL_GOTO(owner, error);
976    EINA_SAFETY_ON_NULL_GOTO(eventarea, error);
977 
978    if (!func)
979      {
980         elm_object_tooltip_unset(eventarea);
981         return;
982      }
983 
984    tt = evas_object_data_get(eventarea, _tooltip_key);
985    if (tt && tt->owner)
986      {
987         if (tt->owner != owner)
988           {
989              if (tt->owner != eventarea)
990                evas_object_event_callback_del_full
991                  (tt->owner, EVAS_CALLBACK_FREE, _elm_tooltip_obj_free_cb, tt);
992 
993              elm_widget_tooltip_del(tt->owner, tt);
994 
995              if (owner != eventarea)
996                evas_object_event_callback_add
997                  (owner, EVAS_CALLBACK_FREE, _elm_tooltip_obj_free_cb, tt);
998 
999              elm_widget_tooltip_add(tt->owner, tt);
1000           }
1001 
1002         if ((tt->func == func) && (tt->data == data) &&
1003             (tt->del_cb == del_cb))
1004           return;
1005         _elm_tooltip_data_clean(tt);
1006         just_created = EINA_FALSE;
1007      }
1008    else
1009      {
1010         if (!tt)
1011           {
1012              tt = _elm_tooltip_create(eventarea);
1013              if (!tt) goto error;
1014           }
1015 
1016         tt->owner = owner;
1017         if (owner != eventarea)
1018           evas_object_event_callback_add
1019             (owner, EVAS_CALLBACK_FREE, _elm_tooltip_obj_free_cb, tt);
1020 
1021         elm_widget_tooltip_add(tt->owner, tt);
1022      }
1023 
1024    tt->func = func;
1025    tt->data = data;
1026    tt->del_cb = del_cb;
1027 
1028    if (!just_created) _elm_tooltip_reconfigure_job_start(tt);
1029    else if (efl_canvas_pointer_inside_get(eventarea, NULL) && (!tt->tooltip))
1030      _elm_tooltip_show(tt);
1031    return;
1032 
1033  error:
1034    if (del_cb) del_cb((void *)data, owner, NULL);
1035 }
1036 
1037 EAPI void
elm_object_tooltip_show(Evas_Object * obj)1038 elm_object_tooltip_show(Evas_Object *obj)
1039 {
1040    ELM_TOOLTIP_GET_OR_RETURN(tt, obj);
1041    tt->visible_lock = EINA_TRUE;
1042    _elm_tooltip_show(tt);
1043 }
1044 
1045 EAPI void
elm_object_tooltip_hide(Evas_Object * obj)1046 elm_object_tooltip_hide(Evas_Object *obj)
1047 {
1048    ELM_TOOLTIP_GET_OR_RETURN(tt, obj);
1049    tt->visible_lock = EINA_FALSE;
1050    _elm_tooltip_hide_anim_start(tt);
1051 }
1052 
1053 EAPI void
elm_object_tooltip_text_set(Evas_Object * obj,const char * text)1054 elm_object_tooltip_text_set(Evas_Object *obj, const char *text)
1055 {
1056    Elm_Tooltip *tt;
1057    EINA_SAFETY_ON_NULL_RETURN(obj);
1058 
1059    if (!text)
1060      {
1061         tt = evas_object_data_get((obj), _tooltip_key);
1062         if (tt)
1063           elm_object_tooltip_unset(obj);
1064 	return;
1065      }
1066 
1067    text = eina_stringshare_add(text);
1068    elm_object_tooltip_content_cb_set
1069      (obj, _elm_tooltip_label_create, text, _elm_tooltip_label_del_cb);
1070 }
1071 
1072 EAPI void
elm_object_tooltip_domain_translatable_text_set(Evas_Object * obj,const char * domain,const char * text)1073 elm_object_tooltip_domain_translatable_text_set(Evas_Object *obj, const char *domain, const char *text)
1074 {
1075    const char **data;
1076    EINA_SAFETY_ON_NULL_RETURN(obj);
1077    EINA_SAFETY_ON_NULL_RETURN(text);
1078 
1079    data = malloc(2 * sizeof(char *));
1080    if (!data) return;
1081    data[0] = eina_stringshare_add(domain);
1082    data[1] = eina_stringshare_add(text);
1083    elm_object_tooltip_content_cb_set
1084      (obj, _elm_tooltip_trans_label_create, data,
1085       _elm_tooltip_trans_label_del_cb);
1086 }
1087 
1088 EAPI void
elm_object_tooltip_content_cb_set(Evas_Object * obj,Elm_Tooltip_Content_Cb func,const void * data,Evas_Smart_Cb del_cb)1089 elm_object_tooltip_content_cb_set(Evas_Object *obj, Elm_Tooltip_Content_Cb func, const void *data, Evas_Smart_Cb del_cb)
1090 {
1091    elm_object_sub_tooltip_content_cb_set(obj, obj, func, data, del_cb);
1092 }
1093 
1094 EAPI void
elm_object_tooltip_unset(Evas_Object * obj)1095 elm_object_tooltip_unset(Evas_Object *obj)
1096 {
1097    ELM_TOOLTIP_GET_OR_RETURN(tt, obj);
1098    _elm_tooltip_unset(tt);
1099 }
1100 
1101 EAPI void
elm_object_tooltip_style_set(Evas_Object * obj,const char * style)1102 elm_object_tooltip_style_set(Evas_Object *obj, const char *style)
1103 {
1104    ELM_TOOLTIP_GET_OR_CREATE(tt, obj);
1105    if (!eina_stringshare_replace(&tt->style, style)) return;
1106    elm_tooltip_theme(tt);
1107 }
1108 
1109 EAPI const char *
elm_object_tooltip_style_get(const Evas_Object * obj)1110 elm_object_tooltip_style_get(const Evas_Object *obj)
1111 {
1112    ELM_TOOLTIP_GET_OR_RETURN(tt, obj, NULL);
1113    return tt->style ? tt->style : "default";
1114 }
1115 
1116 EAPI Eina_Bool
elm_object_tooltip_window_mode_set(Evas_Object * obj,Eina_Bool disable)1117 elm_object_tooltip_window_mode_set(Evas_Object *obj, Eina_Bool disable)
1118 {
1119    ELM_TOOLTIP_GET_OR_CREATE(tt, obj, EINA_FALSE);
1120    return tt->free_size = disable;
1121 }
1122 
1123 EAPI Eina_Bool
elm_object_tooltip_window_mode_get(const Evas_Object * obj)1124 elm_object_tooltip_window_mode_get(const Evas_Object *obj)
1125 {
1126    ELM_TOOLTIP_GET_OR_RETURN(tt, obj, EINA_FALSE);
1127    return tt->free_size;
1128 }
1129