e721a3c3dd1ceca4c61402d0fa4f115b4887f753
[framework/uifw/elementary.git] / src / lib / elm_pager.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 typedef struct _Widget_Data Widget_Data;
5 typedef struct _Item Item;
6
7 struct _Widget_Data
8 {
9    Eina_List *stack;
10    Item *top, *oldtop;
11    Evas_Object *rect, *clip;
12    Eina_Bool disable_animation: 1;
13 };
14
15 struct _Item
16 {
17    Evas_Object *obj, *base, *content;
18    Evas_Coord minw, minh;
19    Eina_Bool popme : 1;
20 };
21
22 static const char *widtype = NULL;
23 static void _del_hook(Evas_Object *obj);
24 static void _mirrored_set(Evas_Object *obj, Eina_Bool rtl);
25 static void _theme_hook(Evas_Object *obj);
26 static void _sizing_eval(Evas_Object *obj);
27 static void _changed_size_hints(void *data,
28                                 Evas *e,
29                                 Evas_Object *obj,
30                                 void *event_info);
31 static void _content_del(void *data,
32                          Evas *e,
33                          Evas_Object *obj,
34                          void *event_info);
35 static Eina_List *_item_get(Evas_Object *obj, Evas_Object *content);
36
37 static const char SIG_HIDE_FINISHED[] = "hide,finished";
38
39 static const Evas_Smart_Cb_Description _signals[] = {
40    {SIG_HIDE_FINISHED, ""},
41    {NULL, NULL}
42 };
43
44 static void
45 _del_hook(Evas_Object *obj)
46 {
47    Widget_Data *wd = elm_widget_data_get(obj);
48    if (!wd) return;
49    free(wd);
50 }
51
52 static void
53 _mirrored_set(Evas_Object *obj, Eina_Bool rtl)
54 {
55    Widget_Data *wd = elm_widget_data_get(obj);
56    Eina_List *l;
57    Item *it;
58    if (!wd) return;
59    EINA_LIST_FOREACH(wd->stack, l, it)
60       edje_object_mirrored_set(it->base, rtl);
61 }
62
63 static void
64 _theme_hook(Evas_Object *obj)
65 {
66    Widget_Data *wd = elm_widget_data_get(obj);
67    Eina_List *l;
68    Item *it;
69    if (!wd) return;
70    _elm_widget_mirrored_reload(obj);
71    _mirrored_set(obj, elm_widget_mirrored_get(obj));
72    EINA_LIST_FOREACH(wd->stack, l, it)
73      {
74         _elm_theme_object_set(obj, it->base,  "pager", "base",
75                               elm_widget_style_get(obj));
76         edje_object_scale_set(it->base, elm_widget_scale_get(obj) *
77                               _elm_config->scale);
78      }
79    _sizing_eval(obj);
80 }
81
82 static Eina_List *
83 _item_get(Evas_Object *obj, Evas_Object *content)
84 {
85    Widget_Data *wd = elm_widget_data_get(obj);
86    Item *it;
87    Eina_List *l;
88    if (!wd) return NULL;
89
90    EINA_LIST_FOREACH(wd->stack, l, it)
91      {
92         if (it->content == content)
93           return l;
94      }
95
96    return NULL;
97 }
98
99 static Eina_Bool
100 _elm_pager_focus_next_hook(const Evas_Object *obj, Elm_Focus_Direction dir, Evas_Object **next)
101 {
102    Widget_Data *wd = elm_widget_data_get(obj);
103    Evas_Object *cur;
104
105    if ((!wd) || (!wd->top))
106      return EINA_FALSE;
107
108    cur = wd->top->content;
109
110    /* Try Focus cycle in subitem */
111    return elm_widget_focus_next_get(cur, dir, next);
112 }
113
114 static void
115 _sizing_eval(Evas_Object *obj)
116 {
117    Widget_Data *wd = elm_widget_data_get(obj);
118    Evas_Coord minw = -1, minh = -1;
119    Eina_List *l;
120    Item *it;
121    if (!wd) return;
122    EINA_LIST_FOREACH(wd->stack, l, it)
123      {
124         if (it->minw > minw) minw = it->minw;
125         if (it->minh > minh) minh = it->minh;
126      }
127    evas_object_size_hint_min_set(obj, minw, minh);
128    evas_object_size_hint_max_set(obj, -1, -1);
129 }
130
131 static void
132 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
133 {
134    Item *it = data;
135    Evas_Coord minw = -1, minh = -1;
136    evas_object_size_hint_min_get(it->content, &minw, &minh);
137    // FIXME: why is this needed? how does edje get this unswallowed or
138    // lose its callbacks to edje
139    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
140    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
141    _sizing_eval(it->obj);
142 }
143
144 static void
145 _eval_top(Evas_Object *obj)
146 {
147    Widget_Data *wd = elm_widget_data_get(obj);
148    Eina_Bool show_noanimate = EINA_TRUE;
149    Item *ittop;
150    if (!wd) return;
151    if (!wd->stack) return;
152    ittop = eina_list_last(wd->stack)->data;
153    if (ittop != wd->top)
154      {
155         Evas_Object *o;
156         const char *onshow, *onhide;
157
158         if (wd->top)
159           {
160              o = wd->top->base;
161              if(wd->disable_animation)
162                {
163                   edje_object_signal_emit(o, "elm,action,hide,noanimate", "elm");
164                   if (wd->top->popme)
165                     wd->stack = eina_list_remove(wd->stack, wd->top);
166                }
167              else if (wd->top->popme)
168                {
169                   edje_object_signal_emit(o, "elm,action,pop", "elm");
170                   wd->stack = eina_list_remove(wd->stack, wd->top);
171                }
172              else
173                edje_object_signal_emit(o, "elm,action,hide", "elm");
174              onhide = edje_object_data_get(o, "onhide");
175              if (onhide)
176                {
177                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
178                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
179                }
180           }
181         else
182           {
183              show_noanimate = EINA_FALSE;
184           }
185         wd->oldtop = wd->top;
186         wd->top = ittop;
187         o = wd->top->base;
188         evas_object_show(o);
189         if ((!show_noanimate)||(wd->disable_animation))
190           {
191              edje_object_signal_emit(o, "elm,action,show,noanimate", "elm");
192           }
193         else if (wd->oldtop)
194           {
195              if (elm_object_focus_get(wd->oldtop->content))
196                elm_object_focus_set(wd->top->content, EINA_TRUE);
197              if (wd->oldtop->popme)
198                edje_object_signal_emit(o, "elm,action,show", "elm");
199              else
200                edje_object_signal_emit(o, "elm,action,push", "elm");
201           }
202         else
203           edje_object_signal_emit(o, "elm,action,push", "elm");
204         onshow = edje_object_data_get(o, "onshow");
205         if (onshow)
206           {
207              if (!strcmp(onshow, "raise")) evas_object_raise(o);
208              else if (!strcmp(onshow, "lower")) evas_object_lower(o);
209           }
210      }
211 }
212
213 static void
214 _move(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
215 {
216    Widget_Data *wd = elm_widget_data_get(data);
217    Evas_Coord x, y;
218    Eina_List *l;
219    Item *it;
220    if (!wd) return;
221    evas_object_geometry_get(obj, &x, &y, NULL, NULL);
222    EINA_LIST_FOREACH(wd->stack, l, it)
223      evas_object_move(it->base, x, y);
224 }
225
226 static void
227 _content_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
228 {
229    Widget_Data *wd = elm_widget_data_get(data);
230    Eina_List *l;
231    Item *it;
232    if (!wd) return;
233    l = _item_get(data, obj);
234    if (!l) return;
235    it = l->data;
236    wd->stack = eina_list_remove_list(wd->stack, l);
237    evas_object_event_callback_del_full
238       (obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, it);
239    evas_object_del(it->base);
240    _eval_top(data);
241    free(it);
242 }
243
244 static void
245 _resize(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
246 {
247    Widget_Data *wd = elm_widget_data_get(data);
248    Evas_Coord w, h;
249    Eina_List *l;
250    Item *it;
251    if (!wd) return;
252    evas_object_geometry_get(obj, NULL, NULL, &w, &h);
253    EINA_LIST_FOREACH(wd->stack, l, it) evas_object_resize(it->base, w, h);
254 }
255
256 static void
257 _signal_hide_finished(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
258 {
259    Item *it = data;
260    Evas_Object *obj2 = it->obj;
261    Evas_Object *content = it->content;
262
263    if (it->popme)
264      {
265         evas_object_del(it->base);
266         evas_object_event_callback_del_full(content,
267                                             EVAS_CALLBACK_CHANGED_SIZE_HINTS,
268                                             _changed_size_hints,
269                                             it);
270         evas_object_event_callback_del(content,
271                                        EVAS_CALLBACK_DEL,
272                                        _content_del);
273         evas_object_del(content);
274         free(it);
275      }
276    else
277      {
278         evas_object_hide(it->base);
279         edje_object_signal_emit(it->base, "elm,action,reset", "elm");
280         edje_object_message_signal_process(it->base);
281         evas_object_hide(content);
282      }
283     evas_object_smart_callback_call(obj2, SIG_HIDE_FINISHED, content);
284     _sizing_eval(obj2);
285 }
286
287 EAPI Evas_Object *
288 elm_pager_add(Evas_Object *parent)
289 {
290    Evas_Object *obj;
291    Evas *e;
292    Widget_Data *wd;
293
294    ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL);
295
296    ELM_SET_WIDTYPE(widtype, "pager");
297    elm_widget_type_set(obj, "pager");
298    elm_widget_sub_object_add(parent, obj);
299    elm_widget_data_set(obj, wd);
300    elm_widget_del_hook_set(obj, _del_hook);
301    elm_widget_theme_hook_set(obj, _theme_hook);
302    elm_widget_focus_next_hook_set(obj, _elm_pager_focus_next_hook);
303    elm_widget_can_focus_set(obj, EINA_FALSE);
304
305    wd->clip = evas_object_rectangle_add(e);
306    elm_widget_resize_object_set(obj, wd->clip);
307    elm_widget_sub_object_add(obj, wd->clip);
308
309    wd->rect = evas_object_rectangle_add(e);
310    elm_widget_sub_object_add(obj, wd->rect);
311    evas_object_color_set(wd->rect, 255, 255, 255, 0);
312    evas_object_clip_set(wd->rect, wd->clip);
313
314    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, _move, obj);
315    evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _resize, obj);
316
317    evas_object_smart_callbacks_descriptions_set(obj, _signals);
318
319    _mirrored_set(obj, elm_widget_mirrored_get(obj));
320    _sizing_eval(obj);
321    return obj;
322 }
323
324 EAPI void
325 elm_pager_content_push(Evas_Object *obj, Evas_Object *content)
326 {
327    ELM_CHECK_WIDTYPE(obj, widtype);
328    Widget_Data *wd = elm_widget_data_get(obj);
329    Evas_Coord x, y, w, h;
330    Item *it;
331
332    if ((!wd) || (!content)) return;
333    if (_item_get(obj, content)) return;
334
335    it = ELM_NEW(Item);
336    if (!it) return;
337    it->obj = obj;
338    it->content = content;
339    it->base = edje_object_add(evas_object_evas_get(obj));
340    evas_object_smart_member_add(it->base, obj);
341    evas_object_geometry_get(obj, &x, &y, &w, &h);
342    evas_object_move(it->base, x, y);
343    evas_object_resize(it->base, w, h);
344    evas_object_clip_set(it->base, wd->clip);
345    elm_widget_sub_object_add(obj, it->base);
346    elm_widget_sub_object_add(obj, it->content);
347    _elm_theme_object_set(obj,
348                          it->base,
349                          "pager",
350                          "base",
351                          elm_widget_style_get(obj));
352    edje_object_signal_callback_add(it->base,
353                                    "elm,action,hide,finished",
354                                    "",
355                                    _signal_hide_finished,
356                                    it);
357    evas_object_event_callback_add(it->content,
358                                   EVAS_CALLBACK_CHANGED_SIZE_HINTS,
359                                   _changed_size_hints,
360                                   it);
361    evas_object_event_callback_add(it->content,
362                                   EVAS_CALLBACK_DEL,
363                                   _content_del,
364                                   obj);
365    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
366    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
367    evas_object_data_set(it->base, "_elm_leaveme", obj);
368    evas_object_show(it->content);
369    wd->stack = eina_list_append(wd->stack, it);
370    _eval_top(obj);
371    _sizing_eval(obj);
372 }
373
374 EAPI void
375 elm_pager_content_pop(Evas_Object *obj)
376 {
377    ELM_CHECK_WIDTYPE(obj, widtype);
378    Widget_Data *wd = elm_widget_data_get(obj);
379    Eina_List *ll;
380    Item *it;
381    if (!wd) return;
382    if (!wd->stack) return;
383    it = eina_list_last(wd->stack)->data;
384    it->popme = EINA_TRUE;
385    ll = eina_list_last(wd->stack);
386    if (ll)
387      {
388         ll = ll->prev;
389         if (!ll)
390           {
391              Evas_Object *o;
392              const char *onhide;
393
394              wd->top = it;
395              o = wd->top->base;
396              edje_object_signal_emit(o, "elm,action,pop", "elm");
397              wd->stack = eina_list_remove(wd->stack, it);
398              onhide = edje_object_data_get(o, "onhide");
399              if (onhide)
400                {
401                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
402                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
403                }
404              wd->top = NULL;
405           }
406         else
407           {
408              it = ll->data;
409              elm_pager_content_promote(obj, it->content);
410           }
411      }
412 }
413
414 static void
415 _del_job(void *data)
416 {
417    Evas_Object *obj = data;
418    evas_object_del(obj);
419 }
420
421 EAPI void
422 elm_pager_to_content_pop(Evas_Object *obj, Evas_Object *content)
423 {
424    ELM_CHECK_WIDTYPE(obj, widtype);
425    Widget_Data *wd = elm_widget_data_get(obj);
426    Eina_List *ll;
427    Item *it;
428    if (!wd) return;
429    if (!wd->stack) return;
430    it = eina_list_last(wd->stack)->data;
431    it->popme = EINA_TRUE;
432    ll = eina_list_last(wd->stack);
433    if (ll)
434      {
435         while(ll)
436           {
437              it = ll->data;
438              if(it->content != content)
439                {
440                   wd->stack = eina_list_remove_list(wd->stack, ll);
441                   ecore_job_add(_del_job, it->content);
442                   it->content = NULL;
443                }
444              else
445                break;
446
447              ll = ll->prev;
448           }
449      }
450    _eval_top(it->obj);
451 }
452
453 EAPI void
454 elm_pager_content_promote(Evas_Object *obj, Evas_Object *content)
455 {
456    ELM_CHECK_WIDTYPE(obj, widtype);
457    Widget_Data *wd = elm_widget_data_get(obj);
458    Eina_List *l;
459    Item *it;
460    if (!wd) return;
461    l = _item_get(obj, content);
462    if (!l) return;
463
464    it = l->data;
465    wd->stack = eina_list_remove_list(wd->stack, l);
466    wd->stack = eina_list_append(wd->stack, it);
467    _eval_top(obj);
468 }
469
470 EAPI Evas_Object *
471 elm_pager_content_bottom_get(const Evas_Object *obj)
472 {
473    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
474    Widget_Data *wd = elm_widget_data_get(obj);
475    Item *it;
476    if (!wd) return NULL;
477    if (!wd->stack) return NULL;
478    it = wd->stack->data;
479    return it->content;
480 }
481
482 EAPI Evas_Object *
483 elm_pager_content_top_get(const Evas_Object *obj)
484 {
485    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
486    Widget_Data *wd = elm_widget_data_get(obj);
487    if (!wd) return NULL;
488    if (!wd->top) return NULL;
489    return wd->top->content;
490 }
491
492 EAPI void
493 elm_pager_animation_disabled_set(Evas_Object *obj, Eina_Bool disable)
494 {
495    ELM_CHECK_WIDTYPE(obj, widtype);
496    Widget_Data *wd = elm_widget_data_get(obj);
497    wd->disable_animation = disable;
498 }