0583593ff9890774974a1aae2c947e2234f4535e
[framework/uifw/elementary.git] / src / lib / elm_pager.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Pager Pager
6  *
7  * The pager is an object that allows flipping (with animation) between 1 or
8  * more “pages” of objects, much like a stack of windows within the window.
9  *
10  * Objects can be pushed or popped from the stack or deleted as normal.
11  * Pushes and pops will animate (and a pop will delete the object once the
12  * animation is finished). Any object in the pager can be promoted to the top
13  * (from its current stacking position) as well. Objects are pushed to the
14  * top with elm_pager_content_push() and when the top item is no longer
15  * wanted, simply pop it with elm_pager_content_pop() and it will also be
16  * deleted. Any object you wish to promote to the top that is already in the
17  * pager, simply use elm_pager_content_promote(). If an object is no longer
18  * needed and is not the top item, just delete it as normal. You can query
19  * which objects are the top and bottom with elm_pager_content_bottom_get()
20  * and elm_pager_content_top_get().
21  *
22  * Signals that you can add callbacks for are:
23  *
24  * "hide,finished" - when the previous page is hided
25  *
26  */
27
28 typedef struct _Widget_Data Widget_Data;
29 typedef struct _Item Item;
30
31 struct _Widget_Data
32 {
33    Eina_List *stack;
34    Item *top, *oldtop;
35    Evas_Object *rect, *clip;
36    Eina_Bool disable_animation: 1;
37 };
38
39 struct _Item
40 {
41    Evas_Object *obj, *base, *content;
42    Evas_Coord minw, minh;
43    Eina_Bool popme : 1;
44 };
45
46 static const char *widtype = NULL;
47 static void _del_hook(Evas_Object *obj);
48 static void _mirrored_set(Evas_Object *obj, Eina_Bool rtl);
49 static void _theme_hook(Evas_Object *obj);
50 static void _sizing_eval(Evas_Object *obj);
51 static void _changed_size_hints(void *data,
52                                 Evas *e,
53                                 Evas_Object *obj,
54                                 void *event_info);
55 static void _content_del(void *data,
56                          Evas *e,
57                          Evas_Object *obj,
58                          void *event_info);
59 static Eina_List *_item_get(Evas_Object *obj, Evas_Object *content);
60
61 static const char SIG_HIDE_FINISHED[] = "hide,finished";
62
63 static const Evas_Smart_Cb_Description _signals[] = {
64    {SIG_HIDE_FINISHED, ""},
65    {NULL, NULL}
66 };
67
68 static void
69 _del_hook(Evas_Object *obj)
70 {
71    Widget_Data *wd = elm_widget_data_get(obj);
72    if (!wd) return;
73    free(wd);
74 }
75
76 static void
77 _mirrored_set(Evas_Object *obj, Eina_Bool rtl)
78 {
79    Widget_Data *wd = elm_widget_data_get(obj);
80    Eina_List *l;
81    Item *it;
82    if (!wd) return;
83    EINA_LIST_FOREACH(wd->stack, l, it)
84       edje_object_mirrored_set(it->base, rtl);
85 }
86
87 static void
88 _theme_hook(Evas_Object *obj)
89 {
90    Widget_Data *wd = elm_widget_data_get(obj);
91    Eina_List *l;
92    Item *it;
93    if (!wd) return;
94    _elm_widget_mirrored_reload(obj);
95    _mirrored_set(obj, elm_widget_mirrored_get(obj));
96    EINA_LIST_FOREACH(wd->stack, l, it)
97      {
98         _elm_theme_object_set(obj, it->base,  "pager", "base",
99                               elm_widget_style_get(obj));
100         edje_object_scale_set(it->base, elm_widget_scale_get(obj) *
101                               _elm_config->scale);
102      }
103    _sizing_eval(obj);
104 }
105
106 static Eina_List *
107 _item_get(Evas_Object *obj, Evas_Object *content)
108 {
109    Widget_Data *wd = elm_widget_data_get(obj);
110    Item *it;
111    Eina_List *l;
112    if (!wd) return NULL;
113
114    EINA_LIST_FOREACH(wd->stack, l, it)
115      {
116         if (it->content == content)
117           return l;
118      }
119
120    return NULL;
121 }
122
123 static Eina_Bool
124 _elm_pager_focus_next_hook(const Evas_Object *obj, Elm_Focus_Direction dir, Evas_Object **next)
125 {
126    Widget_Data *wd = elm_widget_data_get(obj);
127    Evas_Object *cur;
128
129    if ((!wd) || (!wd->top))
130      return EINA_FALSE;
131
132    cur = wd->top->content;
133
134    /* Try Focus cycle in subitem */
135    return elm_widget_focus_next_get(cur, dir, next);
136 }
137
138 static void
139 _sizing_eval(Evas_Object *obj)
140 {
141    Widget_Data *wd = elm_widget_data_get(obj);
142    Evas_Coord minw = -1, minh = -1;
143    Eina_List *l;
144    Item *it;
145    if (!wd) return;
146    EINA_LIST_FOREACH(wd->stack, l, it)
147      {
148         if (it->minw > minw) minw = it->minw;
149         if (it->minh > minh) minh = it->minh;
150      }
151    evas_object_size_hint_min_set(obj, minw, minh);
152    evas_object_size_hint_max_set(obj, -1, -1);
153 }
154
155 static void
156 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
157 {
158    Item *it = data;
159    Evas_Coord minw = -1, minh = -1;
160    evas_object_size_hint_min_get(it->content, &minw, &minh);
161    // FIXME: why is this needed? how does edje get this unswallowed or
162    // lose its callbacks to edje
163    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
164    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
165    _sizing_eval(it->obj);
166 }
167
168 static void
169 _eval_top(Evas_Object *obj)
170 {
171    Widget_Data *wd = elm_widget_data_get(obj);
172    Eina_Bool show_noanimate = EINA_TRUE;
173    Item *ittop;
174    if (!wd) return;
175    if (!wd->stack) return;
176    ittop = eina_list_last(wd->stack)->data;
177    if (ittop != wd->top)
178      {
179         Evas_Object *o;
180         const char *onshow, *onhide;
181
182         if (wd->top)
183           {
184              o = wd->top->base;
185              if(wd->disable_animation)
186                {
187                   edje_object_signal_emit(o, "elm,action,hide,noanimate", "elm");
188                   if (wd->top->popme)
189                     wd->stack = eina_list_remove(wd->stack, wd->top);
190                }
191              else if (wd->top->popme)
192                {
193                   edje_object_signal_emit(o, "elm,action,pop", "elm");
194                   wd->stack = eina_list_remove(wd->stack, wd->top);
195                }
196              else
197                edje_object_signal_emit(o, "elm,action,hide", "elm");
198              onhide = edje_object_data_get(o, "onhide");
199              if (onhide)
200                {
201                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
202                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
203                }
204           }
205         else
206           {
207              show_noanimate = EINA_FALSE;
208           }
209         wd->oldtop = wd->top;
210         wd->top = ittop;
211         o = wd->top->base;
212         evas_object_show(o);
213         if ((!show_noanimate)||(wd->disable_animation))
214           {
215              edje_object_signal_emit(o, "elm,action,show,noanimate", "elm");
216           }
217         else if (wd->oldtop)
218           {
219              if (elm_object_focus_get(wd->oldtop->content))
220                elm_object_focus(wd->top->content);
221              if (wd->oldtop->popme)
222                edje_object_signal_emit(o, "elm,action,show", "elm");
223              else
224                edje_object_signal_emit(o, "elm,action,push", "elm");
225           }
226         else
227           edje_object_signal_emit(o, "elm,action,push", "elm");
228         onshow = edje_object_data_get(o, "onshow");
229         if (onshow)
230           {
231              if (!strcmp(onshow, "raise")) evas_object_raise(o);
232              else if (!strcmp(onshow, "lower")) evas_object_lower(o);
233           }
234      }
235 }
236
237 static void
238 _move(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
239 {
240    Widget_Data *wd = elm_widget_data_get(data);
241    Evas_Coord x, y;
242    Eina_List *l;
243    Item *it;
244    if (!wd) return;
245    evas_object_geometry_get(obj, &x, &y, NULL, NULL);
246    EINA_LIST_FOREACH(wd->stack, l, it)
247      evas_object_move(it->base, x, y);
248 }
249
250 static void
251 _content_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
252 {
253    Widget_Data *wd = elm_widget_data_get(data);
254    Eina_List *l;
255    Item *it;
256    if (!wd) return;
257    l = _item_get(data, obj);
258    if (!l) return;
259    it = l->data;
260    wd->stack = eina_list_remove_list(wd->stack, l);
261    evas_object_event_callback_del_full
262       (obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, it);
263    evas_object_del(it->base);
264    _eval_top(data);
265    free(it);
266 }
267
268 static void
269 _resize(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
270 {
271    Widget_Data *wd = elm_widget_data_get(data);
272    Evas_Coord w, h;
273    Eina_List *l;
274    Item *it;
275    if (!wd) return;
276    evas_object_geometry_get(obj, NULL, NULL, &w, &h);
277    EINA_LIST_FOREACH(wd->stack, l, it) evas_object_resize(it->base, w, h);
278 }
279
280 static void
281 _signal_hide_finished(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
282 {
283    Item *it = data;
284    Evas_Object *obj2 = it->obj;
285    Evas_Object *content = it->content;
286
287    if (it->popme)
288      {
289         evas_object_del(it->base);
290         evas_object_event_callback_del_full(content,
291                                             EVAS_CALLBACK_CHANGED_SIZE_HINTS,
292                                             _changed_size_hints,
293                                             it);
294         evas_object_event_callback_del(content,
295                                        EVAS_CALLBACK_DEL,
296                                        _content_del);
297         evas_object_del(content);
298         free(it);
299      }
300    else
301      {
302         evas_object_hide(it->base);
303         edje_object_signal_emit(it->base, "elm,action,reset", "elm");
304         edje_object_message_signal_process(it->base);
305         evas_object_hide(content);
306      }
307     evas_object_smart_callback_call(obj2, SIG_HIDE_FINISHED, content);
308     _sizing_eval(obj2);
309 }
310
311 /**
312  * Add a new pager to the parent
313  *
314  * @param parent The parent object
315  * @return The new object or NULL if it cannot be created
316  *
317  * @ingroup Pager
318  */
319 EAPI Evas_Object *
320 elm_pager_add(Evas_Object *parent)
321 {
322    Evas_Object *obj;
323    Evas *e;
324    Widget_Data *wd;
325
326    ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL);
327
328    ELM_SET_WIDTYPE(widtype, "pager");
329    elm_widget_type_set(obj, "pager");
330    elm_widget_sub_object_add(parent, obj);
331    elm_widget_data_set(obj, wd);
332    elm_widget_del_hook_set(obj, _del_hook);
333    elm_widget_theme_hook_set(obj, _theme_hook);
334    elm_widget_focus_next_hook_set(obj, _elm_pager_focus_next_hook);
335    elm_widget_can_focus_set(obj, EINA_FALSE);
336
337    wd->clip = evas_object_rectangle_add(e);
338    elm_widget_resize_object_set(obj, wd->clip);
339    elm_widget_sub_object_add(obj, wd->clip);
340
341    wd->rect = evas_object_rectangle_add(e);
342    elm_widget_sub_object_add(obj, wd->rect);
343    evas_object_color_set(wd->rect, 255, 255, 255, 0);
344    evas_object_clip_set(wd->rect, wd->clip);
345
346    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, _move, obj);
347    evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _resize, obj);
348
349    evas_object_smart_callbacks_descriptions_set(obj, _signals);
350
351    _mirrored_set(obj, elm_widget_mirrored_get(obj));
352    _sizing_eval(obj);
353    return obj;
354 }
355
356 /**
357  * Push an object to the top of the pager stack (and show it)
358  *
359  * The object pushed becomes a child of the pager and will be controlled
360  * it and deleted when the pager is deleted.
361  *
362  * @param obj The pager object
363  * @param content The object to push
364  *
365  * @ingroup Pager
366  * @warning It will be failed if the content exists on the stack already.
367  */
368 EAPI void
369 elm_pager_content_push(Evas_Object *obj, Evas_Object *content)
370 {
371    ELM_CHECK_WIDTYPE(obj, widtype);
372    Widget_Data *wd = elm_widget_data_get(obj);
373    Evas_Coord x, y, w, h;
374    Item *it;
375
376    if ((!wd) || (!content)) return;
377    if (_item_get(obj, content)) return;
378
379    it = ELM_NEW(Item);
380    if (!it) return;
381    it->obj = obj;
382    it->content = content;
383    it->base = edje_object_add(evas_object_evas_get(obj));
384    evas_object_smart_member_add(it->base, obj);
385    evas_object_geometry_get(obj, &x, &y, &w, &h);
386    evas_object_move(it->base, x, y);
387    evas_object_resize(it->base, w, h);
388    evas_object_clip_set(it->base, wd->clip);
389    elm_widget_sub_object_add(obj, it->base);
390    elm_widget_sub_object_add(obj, it->content);
391    _elm_theme_object_set(obj,
392                          it->base,
393                          "pager",
394                          "base",
395                          elm_widget_style_get(obj));
396    edje_object_signal_callback_add(it->base,
397                                    "elm,action,hide,finished",
398                                    "",
399                                    _signal_hide_finished,
400                                    it);
401    evas_object_event_callback_add(it->content,
402                                   EVAS_CALLBACK_CHANGED_SIZE_HINTS,
403                                   _changed_size_hints,
404                                   it);
405    evas_object_event_callback_add(it->content,
406                                   EVAS_CALLBACK_DEL,
407                                   _content_del,
408                                   obj);
409    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
410    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
411    evas_object_data_set(it->base, "_elm_leaveme", obj);
412    evas_object_show(it->content);
413    wd->stack = eina_list_append(wd->stack, it);
414    _eval_top(obj);
415    _sizing_eval(obj);
416 }
417
418 /**
419  * Pop the object that is on top of the stack
420  *
421  * This pops the object that is on top (visible) in the pager, makes it
422  * disappear, then deletes the object. The object that was underneath it
423  * on the stack will become visible.
424  *
425  * @param obj The pager object
426  *
427  * @ingroup Pager
428  */
429 EAPI void
430 elm_pager_content_pop(Evas_Object *obj)
431 {
432    ELM_CHECK_WIDTYPE(obj, widtype);
433    Widget_Data *wd = elm_widget_data_get(obj);
434    Eina_List *ll;
435    Item *it;
436    if (!wd) return;
437    if (!wd->stack) return;
438    it = eina_list_last(wd->stack)->data;
439    it->popme = EINA_TRUE;
440    ll = eina_list_last(wd->stack);
441    if (ll)
442      {
443         ll = ll->prev;
444         if (!ll)
445           {
446              Evas_Object *o;
447              const char *onhide;
448
449              wd->top = it;
450              o = wd->top->base;
451              edje_object_signal_emit(o, "elm,action,pop", "elm");
452              wd->stack = eina_list_remove(wd->stack, it);
453              onhide = edje_object_data_get(o, "onhide");
454              if (onhide)
455                {
456                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
457                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
458                }
459              wd->top = NULL;
460           }
461         else
462           {
463              it = ll->data;
464              elm_pager_content_promote(obj, it->content);
465           }
466      }
467 }
468
469 static void
470 _del_job(void *data)
471 {
472    Evas_Object *obj = data;
473    evas_object_del(obj);
474 }
475
476 /**
477  * Pop to the object that is on the stack
478  *
479  * This pops the objects that are on the stack, makes them
480  * disappear, then deletes the objects. The content will become visible.
481  *
482  * @param obj The pager object
483  * @param content The object to show
484  *
485  * @ingroup Pager
486  */
487 EAPI void
488 elm_pager_to_content_pop(Evas_Object *obj, Evas_Object *content)
489 {
490    ELM_CHECK_WIDTYPE(obj, widtype);
491    Widget_Data *wd = elm_widget_data_get(obj);
492    Eina_List *ll;
493    Item *it;
494    if (!wd) return;
495    if (!wd->stack) return;
496    it = eina_list_last(wd->stack)->data;
497    it->popme = EINA_TRUE;
498    ll = eina_list_last(wd->stack);
499    if (ll)
500      {
501         while(ll)
502           {
503              it = ll->data;
504              if(it->content != content)
505                {
506                   wd->stack = eina_list_remove_list(wd->stack, ll);
507                   ecore_job_add(_del_job, it->content);
508                   it->content = NULL;
509                }
510              else
511                break;
512              
513              ll = ll->prev;
514           }
515      }
516    _eval_top(it->obj);
517 }
518
519 /**
520  * Promote an object already in the pager stack to the top of the stack
521  *
522  * This will take the indicated object and promote it to the top of the stack
523  * as if it had been pushed there. The object must already be inside the
524  * pager stack to work.
525  *
526  * @param obj The pager object
527  * @param content The object to promote
528  *
529  * @ingroup Pager
530  */
531 EAPI void
532 elm_pager_content_promote(Evas_Object *obj, Evas_Object *content)
533 {
534    ELM_CHECK_WIDTYPE(obj, widtype);
535    Widget_Data *wd = elm_widget_data_get(obj);
536    Eina_List *l;
537    Item *it;
538    if (!wd) return;
539    l = _item_get(obj, content);
540    if (!l) return;
541
542    it = l->data;
543    wd->stack = eina_list_remove_list(wd->stack, l);
544    wd->stack = eina_list_append(wd->stack, it);
545    _eval_top(obj);
546 }
547
548 /**
549  * Return the object at the bottom of the pager stack
550  *
551  * @param obj The pager object
552  * @return The bottom object or NULL if none
553  *
554  * @ingroup Pager
555  */
556 EAPI Evas_Object *
557 elm_pager_content_bottom_get(const Evas_Object *obj)
558 {
559    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
560    Widget_Data *wd = elm_widget_data_get(obj);
561    Item *it;
562    if (!wd) return NULL;
563    if (!wd->stack) return NULL;
564    it = wd->stack->data;
565    return it->content;
566 }
567
568 /**
569  * Return the object at the top of the pager stack
570  *
571  * @param obj The pager object
572  * @return The top object or NULL if none
573  *
574  * @ingroup Pager
575  */
576 EAPI Evas_Object *
577 elm_pager_content_top_get(const Evas_Object *obj)
578 {
579    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
580    Widget_Data *wd = elm_widget_data_get(obj);
581    if (!wd) return NULL;
582    if (!wd->top) return NULL;
583    return wd->top->content;
584 }
585
586 /**
587  * This disables content animation on push/pop.
588  *
589  * @param obj The pager object
590  * @param disable  if EINA_TRUE animation is disabled.
591  *
592  * @ingroup Pager
593  */
594 EAPI void
595 elm_pager_animation_disabled_set(Evas_Object *obj, Eina_Bool disable)
596 {
597    ELM_CHECK_WIDTYPE(obj, widtype)NULL;
598    Widget_Data *wd = elm_widget_data_get(obj);
599    wd->disable_animation = disable;
600 }