22cc80e276b33e231503c8123298d73353c0320f
[framework/uifw/elementary.git] / src / lib / elm_hover.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Hover Hover
6  * @ingroup Elementary
7  *
8  * A Hover object will over over the @p Parent object at the @p Target
9  * location.  Anything in the background will be given a darker coloring
10  * to indicate that the hover object is on top.
11  *
12  * NOTE: The hover object will take up the entire space of @p Target object.
13  */
14
15 typedef struct _Widget_Data Widget_Data;
16 typedef struct _Subinfo Subinfo;
17
18 struct _Widget_Data
19 {
20    Evas_Object *hov, *cov;
21    Evas_Object *offset, *size;
22    Evas_Object *parent, *target;
23    Eina_List *subs;
24 };
25
26 struct _Subinfo
27 {
28    const char *swallow;
29    Evas_Object *obj;
30 };
31
32 static const char *widtype = NULL;
33 static void _del_pre_hook(Evas_Object *obj);
34 static void _del_hook(Evas_Object *obj);
35 static void _theme_hook(Evas_Object *obj);
36 static void _sizing_eval(Evas_Object *obj);
37 static void _reval_content(Evas_Object *obj);
38 static void _sub_del(void *data, Evas_Object *obj, void *event_info);
39 static void _hov_show_do(Evas_Object *obj);
40 static void _hov_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
41 static void _hov_resize(void *data, Evas *e, Evas_Object *obj, void *event_info);
42 static void _hov_show(void *data, Evas *e, Evas_Object *obj, void *event_info);
43 static void _hov_hide(void *data, Evas *e, Evas_Object *obj, void *event_info);
44
45 static void
46 _del_pre_hook(Evas_Object *obj)
47 {
48    Widget_Data *wd = elm_widget_data_get(obj);
49    Subinfo *si;
50    if (!wd) return;
51    if (evas_object_visible_get(obj))
52      evas_object_smart_callback_call(obj, "clicked", NULL);
53    elm_hover_target_set(obj, NULL);
54    elm_hover_parent_set(obj, NULL);
55    evas_object_event_callback_del_full(wd->hov, EVAS_CALLBACK_MOVE, _hov_move, obj);
56    evas_object_event_callback_del_full(wd->hov, EVAS_CALLBACK_RESIZE, _hov_resize, obj);
57    evas_object_event_callback_del_full(wd->hov, EVAS_CALLBACK_SHOW, _hov_show, obj);
58    evas_object_event_callback_del_full(wd->hov, EVAS_CALLBACK_HIDE, _hov_hide, obj);
59
60    EINA_LIST_FREE(wd->subs, si)
61      {
62         eina_stringshare_del(si->swallow);
63         free(si);
64      }
65 }
66
67 static void
68 _del_hook(Evas_Object *obj)
69 {
70    Widget_Data *wd = elm_widget_data_get(obj);
71    if (!wd) return;
72    free(wd);
73 }
74
75 static void
76 _theme_hook(Evas_Object *obj)
77 {
78    Widget_Data *wd = elm_widget_data_get(obj);
79    if (!wd) return;
80    // FIXME: hover contents doesnt seem to propagate resizes properly
81    _elm_theme_object_set(obj, wd->cov, "hover", "base", elm_widget_style_get(obj));
82    edje_object_scale_set(wd->cov, elm_widget_scale_get(obj) *
83                          _elm_config->scale);
84    _reval_content(obj);
85    _sizing_eval(obj);
86    if (evas_object_visible_get(wd->cov)) _hov_show_do(obj);
87 }
88
89
90 static void
91 _sizing_eval(Evas_Object *obj)
92 {
93    Widget_Data *wd = elm_widget_data_get(obj);
94    Evas_Coord x = 0, y = 0, w = 0, h = 0, x2 = 0, y2 = 0, w2 = 0, h2 = 0;
95    if (!wd) return;
96    if (wd->parent) evas_object_geometry_get(wd->parent, &x, &y, &w, &h);
97    if (wd->hov) evas_object_geometry_get(wd->hov, &x2, &y2, &w2, &h2);
98    evas_object_move(wd->cov, x, y);
99    evas_object_resize(wd->cov, w, h);
100    evas_object_size_hint_min_set(wd->offset, x2 - x, y2 - y);
101    evas_object_size_hint_min_set(wd->size, w2, h2);
102    edje_object_part_swallow(wd->cov, "elm.swallow.offset", wd->offset);
103    edje_object_part_swallow(wd->cov, "elm.swallow.size", wd->size);
104 }
105
106 static void
107 _reval_content(Evas_Object *obj)
108 {
109    Widget_Data *wd = elm_widget_data_get(obj);
110    const Eina_List *l;
111    const Subinfo *si;
112    if (!wd) return;
113    EINA_LIST_FOREACH(wd->subs, l, si)
114      edje_object_part_swallow(wd->cov, si->swallow, si->obj);
115 }
116
117 static void
118 _sub_del(void *data __UNUSED__, Evas_Object *obj, void *event_info)
119 {
120    Widget_Data *wd = elm_widget_data_get(obj);
121    Evas_Object *sub = event_info;
122    Eina_List *l;
123    Subinfo *si;
124    if (!wd) return;
125    EINA_LIST_FOREACH(wd->subs, l, si)
126      {
127         if (si->obj == sub)
128           {
129              wd->subs = eina_list_remove_list(wd->subs, l);
130              eina_stringshare_del(si->swallow);
131              free(si);
132              break;
133           }
134      }
135 }
136
137 static void
138 _hov_show_do(Evas_Object *obj)
139 {
140    Widget_Data *wd = elm_widget_data_get(obj);
141    const Eina_List *l;
142    const Subinfo *si;
143    if (!wd) return;
144    if (wd->cov)
145      {
146         evas_object_show(wd->cov);
147         edje_object_signal_emit(wd->cov, "elm,action,show", "elm");
148      }
149    EINA_LIST_FOREACH(wd->subs, l, si)
150      {
151         char buf[1024];
152
153         if (!strncmp(si->swallow, "elm.swallow.slot.", 17))
154           {
155              snprintf(buf, sizeof(buf), "elm,action,slot,%s,show",
156                       si->swallow + 17);
157              edje_object_signal_emit(wd->cov, buf, "elm");
158           }
159      }
160 }
161
162 static void
163 _hov_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
164 {
165    _sizing_eval(data);
166 }
167
168 static void
169 _hov_resize(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
170 {
171    _sizing_eval(data);
172 }
173
174 static void
175 _hov_show(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
176 {
177    _hov_show_do(data);
178 }
179
180 static void
181 _hov_hide(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
182 {
183    Widget_Data *wd = elm_widget_data_get(data);
184    const Eina_List *l;
185    const Subinfo *si;
186    if (!wd) return;
187    if (wd->cov)
188      {
189         edje_object_signal_emit(wd->cov, "elm,action,hide", "elm");
190         evas_object_hide(wd->cov);
191      }
192    EINA_LIST_FOREACH(wd->subs, l, si)
193      {
194         char buf[1024];
195
196         if (!strncmp(si->swallow, "elm.swallow.slot.", 17))
197           {
198              snprintf(buf, sizeof(buf), "elm,action,slot,%s,hide",
199                       si->swallow + 17);
200              edje_object_signal_emit(wd->cov, buf, "elm");
201           }
202      }
203 }
204
205 static void
206 _target_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
207 {
208    Widget_Data *wd = elm_widget_data_get(data);
209    if (!wd) return;
210    wd->target = NULL;
211 }
212
213 static void
214 _signal_dismiss(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
215 {
216    Widget_Data *wd = elm_widget_data_get(data);
217    if (!wd) return;
218    evas_object_hide(data);
219    evas_object_smart_callback_call(data, "clicked", NULL);
220 }
221
222 static void
223 _parent_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
224 {
225    _sizing_eval(data);
226 }
227
228 static void
229 _parent_resize(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
230 {
231    _sizing_eval(data);
232 }
233
234 static void
235 _parent_show(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
236 {
237 }
238
239 static void
240 _parent_hide(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
241 {
242    Widget_Data *wd = elm_widget_data_get(data);
243    if (!wd) return;
244    evas_object_hide(wd->cov);
245 }
246
247 static void
248 _parent_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
249 {
250    Widget_Data *wd = elm_widget_data_get(data);
251    if (!wd) return;
252    elm_hover_parent_set(data, NULL);
253    _sizing_eval(data);
254 }
255
256 /**
257  * Adds a hover object to @p parent
258  *
259  * @param parent The parent object
260  * @return The hover object or NULL if one could not be created
261  *
262  * @ingroup Hover
263  */
264 EAPI Evas_Object *
265 elm_hover_add(Evas_Object *parent)
266 {
267    Evas_Object *obj;
268    Evas *e;
269    Widget_Data *wd;
270
271    wd = ELM_NEW(Widget_Data);
272    e = evas_object_evas_get(parent);
273    obj = elm_widget_add(e);
274    ELM_SET_WIDTYPE(widtype, "hover");
275    elm_widget_type_set(obj, "hover");
276    elm_widget_sub_object_add(parent, obj);
277    elm_widget_data_set(obj, wd);
278    elm_widget_del_pre_hook_set(obj, _del_pre_hook);
279    elm_widget_theme_hook_set(obj, _theme_hook);
280    elm_widget_del_hook_set(obj, _del_hook);
281
282    wd->hov = evas_object_rectangle_add(e);
283    evas_object_pass_events_set(wd->hov, 1);
284    evas_object_color_set(wd->hov, 0, 0, 0, 0);
285    elm_widget_resize_object_set(obj, wd->hov);
286    evas_object_event_callback_add(wd->hov, EVAS_CALLBACK_MOVE, _hov_move, obj);
287    evas_object_event_callback_add(wd->hov, EVAS_CALLBACK_RESIZE, _hov_resize, obj);
288    evas_object_event_callback_add(wd->hov, EVAS_CALLBACK_SHOW, _hov_show, obj);
289    evas_object_event_callback_add(wd->hov, EVAS_CALLBACK_HIDE, _hov_hide, obj);
290
291    wd->cov = edje_object_add(e);
292    _elm_theme_object_set(obj, wd->cov, "hover", "base", "default");
293    elm_widget_sub_object_add(obj, wd->cov);
294    edje_object_signal_callback_add(wd->cov, "elm,action,dismiss", "",
295                                    _signal_dismiss, obj);
296
297    wd->offset = evas_object_rectangle_add(e);
298    evas_object_pass_events_set(wd->offset, 1);
299    evas_object_color_set(wd->offset, 0, 0, 0, 0);
300    elm_widget_sub_object_add(obj, wd->offset);
301
302    wd->size = evas_object_rectangle_add(e);
303    evas_object_pass_events_set(wd->size, 1);
304    evas_object_color_set(wd->size, 0, 0, 0, 0);
305    elm_widget_sub_object_add(obj, wd->size);
306
307    edje_object_part_swallow(wd->cov, "elm.swallow.offset", wd->offset);
308    edje_object_part_swallow(wd->cov, "elm.swallow.size", wd->size);
309
310    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
311
312    elm_hover_parent_set(obj, parent);
313
314    _sizing_eval(obj);
315    return obj;
316 }
317
318 /**
319  * Sets the target object for the hover.
320  *
321  * @param obj The hover object
322  * @param target The object to center the hover onto. The hover
323  * will take up the entire space that the target object fills.
324  *
325  * @ingroup Hover
326  */
327 EAPI void
328 elm_hover_target_set(Evas_Object *obj, Evas_Object *target)
329 {
330    ELM_CHECK_WIDTYPE(obj, widtype);
331    Widget_Data *wd = elm_widget_data_get(obj);
332
333    if (wd->target)
334      evas_object_event_callback_del_full(wd->target, EVAS_CALLBACK_DEL,
335                                     _target_del, obj);
336    wd->target = target;
337    if (wd->target)
338      {
339         evas_object_event_callback_add(wd->target, EVAS_CALLBACK_DEL,
340                                        _target_del, obj);
341         elm_widget_hover_object_set(target, obj);
342         _sizing_eval(obj);
343      }
344 }
345
346
347 /**
348  * Sets the parent object for the hover.
349  *
350  * @param obj The hover object
351  * @param parent The object to locate the hover over.
352  *
353  * @ingroup Hover
354  */
355 EAPI void
356 elm_hover_parent_set(Evas_Object *obj, Evas_Object *parent)
357 {
358    ELM_CHECK_WIDTYPE(obj, widtype);
359    Widget_Data *wd = elm_widget_data_get(obj);
360    if (!wd) return;
361    if (wd->parent)
362      {
363         evas_object_event_callback_del_full(wd->parent, EVAS_CALLBACK_MOVE,
364                                        _parent_move, obj);
365         evas_object_event_callback_del_full(wd->parent, EVAS_CALLBACK_RESIZE,
366                                        _parent_resize, obj);
367         evas_object_event_callback_del_full(wd->parent, EVAS_CALLBACK_SHOW,
368                                        _parent_show, obj);
369         evas_object_event_callback_del_full(wd->parent, EVAS_CALLBACK_HIDE,
370                                        _parent_hide, obj);
371         evas_object_event_callback_del_full(wd->parent, EVAS_CALLBACK_DEL,
372                                        _parent_del, obj);
373      }
374    wd->parent = parent;
375    if (wd->parent)
376      {
377         evas_object_event_callback_add(wd->parent, EVAS_CALLBACK_MOVE,
378                                        _parent_move, obj);
379         evas_object_event_callback_add(wd->parent, EVAS_CALLBACK_RESIZE,
380                                        _parent_resize, obj);
381         evas_object_event_callback_add(wd->parent, EVAS_CALLBACK_SHOW,
382                                        _parent_show, obj);
383         evas_object_event_callback_add(wd->parent, EVAS_CALLBACK_HIDE,
384                                        _parent_hide, obj);
385         evas_object_event_callback_add(wd->parent, EVAS_CALLBACK_DEL,
386                                        _parent_del, obj);
387 //      elm_widget_sub_object_add(parent, obj);
388      }
389    _sizing_eval(obj);
390 }
391
392 /**
393  * Gets the target object for the hover.
394  *
395  * @param obj The hover object
396  * @return The target object of the hover.
397  * 
398  * @ingroup Hover
399  */
400 EAPI Evas_Object *
401 elm_hover_target_get(Evas_Object *obj)
402 {
403    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
404    Widget_Data *wd = elm_widget_data_get(obj);
405    if (!wd) return NULL;
406
407    return wd->target;
408 }
409
410 /**
411  * Gets the parent object for the hover.
412  *
413  * @param obj The hover object
414  * @return The parent object to locate the hover over.
415  *
416  * @ingroup Hover
417  */
418 EAPI Evas_Object *
419 elm_hover_parent_get(Evas_Object *obj)
420 {
421    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
422    Widget_Data *wd = elm_widget_data_get(obj);
423    if (!wd) return NULL;
424
425    return wd->parent;
426 }
427
428 /**
429  * Sets the content of the hover object and the direction in which
430  * it will pop out.
431  *
432  * Once the content object is set, a previously set one will be deleted.
433  * If you want to keep that old content object, use the
434  * elm_hover_content_unset() function.
435  *
436  * @param obj The hover object
437  * @param swallow The direction that the object will display in. Multiple
438  * objects can have the same swallow location. Objects placed in the same
439  * swallow will be placed starting at the middle of the hover and ending
440  * farther from the middle.
441  * Accepted values are "left" "right" "top" "bottom" "middle"
442  * @param content The content to place at @p swallow
443  *
444  * @ingroup Hover
445  */
446 EAPI void
447 elm_hover_content_set(Evas_Object *obj, const char *swallow, Evas_Object *content)
448 {
449    ELM_CHECK_WIDTYPE(obj, widtype);
450    Widget_Data *wd = elm_widget_data_get(obj);
451    Subinfo *si;
452    const Eina_List *l;
453    char buf[1024];
454    if (!wd) return;
455    snprintf(buf, sizeof(buf), "elm.swallow.slot.%s", swallow);
456    EINA_LIST_FOREACH(wd->subs, l, si)
457      {
458         if (!strcmp(buf, si->swallow))
459           {
460              if (content == si->obj) return;
461              evas_object_del(si->obj);
462              si->obj = NULL;
463              break;
464           }
465      }
466    if (content)
467      {
468         elm_widget_sub_object_add(obj, content);
469         edje_object_part_swallow(wd->cov, buf, content);
470         si = ELM_NEW(Subinfo);
471         si->swallow = eina_stringshare_add(buf);
472         si->obj = content;
473         wd->subs = eina_list_append(wd->subs, si);
474      }
475    _sizing_eval(obj);
476 }
477
478 /**
479  * Unset the content of the hover object
480  *
481  * Unparent and return the content object which was set for this widget
482  *
483  * @param obj The hover object
484  * @param swallow The direction that the object will display in. Multiple
485  * objects can have the same swallow location. Objects placed in the same
486  * swallow will be placed starting at the middle of the hover and ending
487  * farther from the middle.
488  * Accepted values are "left" "right" "top" "bottom" "middle"
489  * @return The content that was being used
490  *
491  * @ingroup Hover
492  */
493 EAPI Evas_Object *
494 elm_hover_content_unset(Evas_Object *obj, const char *swallow)
495 {
496    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
497    Widget_Data *wd = elm_widget_data_get(obj);
498    Subinfo *si;
499    const Eina_List *l;
500    char buf[1024];
501    if (!wd) return NULL;
502    snprintf(buf, sizeof(buf), "elm.swallow.slot.%s", swallow);
503    EINA_LIST_FOREACH(wd->subs, l, si)
504      {
505         if (!strcmp(buf, si->swallow))
506           {
507              Evas_Object *content;
508              if (!si->obj) return NULL;
509              content = si->obj;
510              elm_widget_sub_object_del(obj, si->obj);
511              edje_object_part_unswallow(wd->cov, si->obj);
512              si->obj = NULL;
513              return content;
514           }
515      }
516    return NULL;
517 }
518
519 /**
520  * Returns the best swallow location for content in the hover.
521  *
522  * @param obj The hover object
523  * @return The edje location to place content into the hover.
524  * See also elm_hover_content_set()
525  *
526  * @ingroup Hover
527  */
528 EAPI const char *
529 elm_hover_best_content_location_get(const Evas_Object *obj, Elm_Hover_Axis pref_axis)
530 {
531    ELM_CHECK_WIDTYPE(obj, widtype) "left";
532    Widget_Data *wd = elm_widget_data_get(obj);
533    Evas_Coord x = 0, y = 0, w = 0, h = 0, x2 = 0, y2 = 0, w2 = 0, h2 = 0;
534    Evas_Coord spc_l, spc_r, spc_t, spc_b;
535    if (!wd) return "left";
536    if (wd->parent) evas_object_geometry_get(wd->parent, &x, &y, &w, &h);
537    if (wd->target) evas_object_geometry_get(wd->target, &x2, &y2, &w2, &h2);
538    spc_l = x2 - x;
539    spc_r = (x + w) - (x2 + w2);
540    if (spc_l < 0) spc_l = 0;
541    if (spc_r < 0) spc_r = 0;
542    spc_t = y2 - y;
543    spc_b = (y + h) - (y2 + h2);
544    if (spc_t < 0) spc_t = 0;
545    if (spc_b < 0) spc_b = 0;
546    if (pref_axis == ELM_HOVER_AXIS_HORIZONTAL)
547      {
548         if (spc_l < spc_r) return "right";
549         else return "left";
550      }
551    else if (pref_axis == ELM_HOVER_AXIS_VERTICAL)
552      {
553         if (spc_t < spc_b) return "bottom";
554         else return "top";
555      }
556    if (spc_l < spc_r)
557      {
558         if (spc_t > spc_r) return "top";
559         else if (spc_b > spc_r) return "bottom";
560         return "right";
561      }
562    if (spc_t > spc_r) return "top";
563    else if (spc_b > spc_r) return "bottom";
564    return "left";
565 }