fc0c402a68290f22b28e18de0a01aaa98d3ece30
[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  * @ingroup Elementary
7  *
8  * The pager is an object that allows flipping (with animation) between 1 or
9  * more ?\9cpages??of objects, much like a stack of windows within the window.
10  *
11  * Objects can be pushed or popped from he stack or deleted as normal.
12  * Pushes and pops will animate (and a pop will delete the object once the
13  * animation is finished). Any object in the pager can be promoted to the top
14  * (from its current stacking position) as well. Objects are pushed to the
15  * top with elm_pager_content_push() and when the top item is no longer
16  * wanted, simply pop it with elm_pager_content_pop() and it will also be
17  * deleted. Any object you wish to promote to the top that is already in the
18  * pager, simply use elm_pager_content_promote(). If an object is no longer
19  * needed and is not the top item, just delete it as normal. You can query
20  * which objects are the top and bottom with elm_pager_content_bottom_get()
21  * and elm_pager_content_top_get().
22  */
23
24 typedef struct _Widget_Data Widget_Data;
25 typedef struct _Item Item;
26
27 struct _Widget_Data
28 {
29    Eina_List *stack;
30    Item *top, *oldtop;
31    Evas_Object *rect, *clip;
32 };
33
34 struct _Item
35 {
36    Evas_Object *obj, *base, *content;
37    Evas_Coord minw, minh;
38    Eina_Bool popme : 1;
39 };
40
41 static const char *widtype = NULL;
42 static void _del_hook(Evas_Object *obj);
43 static void _theme_hook(Evas_Object *obj);
44 static void _sizing_eval(Evas_Object *obj);
45 static void _changed_size_hints(void *data, Evas *e, Evas_Object *obj, void *event_info);
46 static void _sub_del(void *data, Evas_Object *obj, void *event_info);
47
48 static void
49 _del_hook(Evas_Object *obj)
50 {
51    Widget_Data *wd = elm_widget_data_get(obj);
52    if (!wd) return;
53    free(wd);
54 }
55
56 static void
57 _theme_hook(Evas_Object *obj)
58 {
59    Widget_Data *wd = elm_widget_data_get(obj);
60    Eina_List *l;
61    Item *it;
62    if (!wd) return;
63    EINA_LIST_FOREACH(wd->stack, l, it)
64      edje_object_scale_set(it->base, elm_widget_scale_get(obj) * 
65                            _elm_config->scale);
66    _sizing_eval(obj);
67 }
68
69 static void
70 _sizing_eval(Evas_Object *obj)
71 {
72    Widget_Data *wd = elm_widget_data_get(obj);
73    Evas_Coord minw = -1, minh = -1;
74    Eina_List *l;
75    Item *it;
76    if (!wd) return;
77    EINA_LIST_FOREACH(wd->stack, l, it)
78      {
79         if (it->minw > minw) minw = it->minw;
80         if (it->minh > minh) minh = it->minh;
81      }
82    evas_object_size_hint_min_set(obj, minw, minh);
83    evas_object_size_hint_max_set(obj, -1, -1);
84 }
85
86 static void
87 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
88 {
89    Item *it = data;
90    Evas_Coord minw = -1, minh = -1;
91    evas_object_size_hint_min_get(it->content, &minw, &minh);
92    // FIXME: why is this needed? how does edje get this unswallowed or
93    // lose its callbacks to edje
94    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
95    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
96    _sizing_eval(it->obj);
97 }
98
99 static void
100 _eval_top(Evas_Object *obj)
101 {
102    Widget_Data *wd = elm_widget_data_get(obj);
103    Eina_Bool animate=EINA_TRUE;
104    Item *ittop;
105    if (!wd) return;
106    if (!wd->stack) return;
107    ittop = eina_list_last(wd->stack)->data;
108    if (ittop != wd->top)
109      {
110         Evas_Object *o;
111         const char *onshow, *onhide;
112
113         if (wd->top)
114           {
115              o = wd->top->base;
116              edje_object_signal_emit(o, "elm,action,hide", "elm");
117              onhide = edje_object_data_get(o, "onhide");
118              if (onhide)
119                {
120                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
121                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
122                }
123           }
124         else
125           {
126              animate = EINA_FALSE;
127           }
128         wd->top = ittop;
129         o = wd->top->base;
130         evas_object_show(o);
131         if(animate)
132           edje_object_signal_emit(o, "elm,action,show", "elm");
133         else
134           edje_object_signal_emit(o, "elm,action,show,noanimate", "elm");
135         onshow = edje_object_data_get(o, "onshow");
136         if (onshow)
137           {
138              if (!strcmp(onshow, "raise")) evas_object_raise(o);
139              else if (!strcmp(onshow, "lower")) evas_object_lower(o);
140           }
141      }
142 }
143
144 static void
145 _move(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
146 {
147    Widget_Data *wd = elm_widget_data_get(data);
148    Evas_Coord x, y;
149    Eina_List *l;
150    Item *it;
151    if (!wd) return;
152    evas_object_geometry_get(obj, &x, &y, NULL, NULL);
153    EINA_LIST_FOREACH(wd->stack, l, it)
154      evas_object_move(it->base, x, y);
155 }
156
157 static void
158 _sub_del(void *data, Evas_Object *obj __UNUSED__, void *event_info)
159 {
160    Widget_Data *wd = elm_widget_data_get(data);
161    Evas_Object *sub = event_info;
162    Eina_List *l;
163    Item *it;
164    if (!wd) return;
165    EINA_LIST_FOREACH(wd->stack, l, it)
166      {
167         if (it->content == sub)
168           {
169              wd->stack = eina_list_remove_list(wd->stack, l);
170              evas_object_event_callback_del_full
171                (sub, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, it);
172              evas_object_del(it->base);
173              _eval_top(it->obj);
174              free(it);
175              return;
176           }
177      }
178 }
179
180 static void
181 _resize(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
182 {   
183    Widget_Data *wd = elm_widget_data_get(data);
184    Evas_Coord w, h;
185    Eina_List *l;
186    Item *it;
187    if (!wd) return;
188    evas_object_geometry_get(obj, NULL, NULL, &w, &h);
189    EINA_LIST_FOREACH(wd->stack, l, it) evas_object_resize(it->base, w, h);
190 }
191
192 static void
193 _signal_hide_finished(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
194 {
195    Item *it = data;
196    Evas_Object *obj2 = it->obj;
197    evas_object_hide(it->base);
198    edje_object_signal_emit(it->base, "elm,action,reset", "elm");
199    evas_object_smart_callback_call(obj2, "hide,finished", it->content);
200    edje_object_message_signal_process(it->base);
201    if (it->popme) evas_object_del(it->content);
202    _sizing_eval(obj2);
203 }
204
205 /**
206  * Add a new pager to the parent
207  *
208  * @param parent The parent object
209  * @return The new object or NULL if it cannot be created
210  *
211  * @ingroup Pager
212  */
213 EAPI Evas_Object *
214 elm_pager_add(Evas_Object *parent)
215 {
216    Evas_Object *obj;
217    Evas *e;
218    Widget_Data *wd;
219
220    wd = ELM_NEW(Widget_Data);
221    e = evas_object_evas_get(parent);
222    obj = elm_widget_add(e);
223    ELM_SET_WIDTYPE(widtype, "pager");
224    elm_widget_type_set(obj, "pager");
225    elm_widget_sub_object_add(parent, obj);
226    elm_widget_data_set(obj, wd);
227    elm_widget_del_hook_set(obj, _del_hook);
228    elm_widget_theme_hook_set(obj, _theme_hook);
229
230    wd->clip = evas_object_rectangle_add(e);
231    elm_widget_resize_object_set(obj, wd->clip);
232    elm_widget_sub_object_add(obj, wd->clip);
233
234    wd->rect = evas_object_rectangle_add(e);
235    elm_widget_sub_object_add(obj, wd->rect);
236    evas_object_color_set(wd->rect, 255, 255, 255, 0); 
237    evas_object_clip_set(wd->rect, wd->clip);
238
239    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, _move, obj);
240    evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _resize, obj);
241
242    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
243
244    _sizing_eval(obj);
245    return obj;
246 }
247
248 /**
249  * Push an object to the top of the pager stack (and show it)
250  *
251  * The object pushed becomes a child of the pager and will be controlled
252  * it and deleted when the pager is deleted.
253  *
254  * @param obj The pager object
255  * @param content The object to push
256  *
257  * @ingroup Pager
258  */
259 EAPI void
260 elm_pager_content_push(Evas_Object *obj, Evas_Object *content)
261 {
262    ELM_CHECK_WIDTYPE(obj, widtype);
263    Widget_Data *wd = elm_widget_data_get(obj);
264    Item *it = ELM_NEW(Item);
265    Evas_Coord x, y, w, h;
266    if (!wd) return;
267    if (!it) return;
268    it->obj = obj;
269    it->content = content;
270    it->base = edje_object_add(evas_object_evas_get(obj));
271    evas_object_smart_member_add(it->base, obj);
272    evas_object_geometry_get(obj, &x, &y, &w, &h);
273    evas_object_move(it->base, x, y);
274    evas_object_resize(it->base, w, h);
275    evas_object_clip_set(it->base, wd->clip);
276    elm_widget_sub_object_add(obj, it->base);
277    elm_widget_sub_object_add(obj, it->content);
278    _elm_theme_object_set(obj, it->base,  "pager", "base", elm_widget_style_get(obj));
279    edje_object_signal_callback_add(it->base, "elm,action,hide,finished", "", 
280                                    _signal_hide_finished, it);
281    evas_object_event_callback_add(it->content,
282                                   EVAS_CALLBACK_CHANGED_SIZE_HINTS,
283                                   _changed_size_hints, it);
284    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
285    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
286    evas_object_show(it->content);
287    wd->stack = eina_list_append(wd->stack, it);
288    _eval_top(obj);
289    _sizing_eval(obj);
290 }
291
292 /**
293  * Pop the object that is on top of the stack
294  *
295  * This pops the object that is on top (visible) in the pager, makes it
296  * disappear, then deletes the object. The object that was underneath it
297  * on the stack will become visible.
298  *
299  * @param obj The pager object
300  *
301  * @ingroup Pager
302  */
303 EAPI void
304 elm_pager_content_pop(Evas_Object *obj)
305 {
306    ELM_CHECK_WIDTYPE(obj, widtype);
307    Widget_Data *wd = elm_widget_data_get(obj);
308    Eina_List *ll;
309    Item *it;
310    if (!wd) return;
311    if (!wd->stack) return;
312    it = eina_list_last(wd->stack)->data;
313    it->popme = EINA_TRUE;
314    ll = eina_list_last(wd->stack);
315    if (ll)
316      {
317         ll = ll->prev;
318         if (!ll)
319           {
320              Evas_Object *o;
321              const char *onhide;
322
323              wd->top = it;
324              o = wd->top->base;
325              edje_object_signal_emit(o, "elm,action,hide", "elm");
326              onhide = edje_object_data_get(o, "onhide");
327              if (onhide)
328                {
329                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
330                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
331                }
332              wd->top = NULL;
333           }
334         else
335           {
336              it = ll->data;
337              elm_pager_content_promote(obj, it->content);
338           }
339      }
340 }
341
342 /**
343  * Pop to the object that is on the stack
344  *
345  * This pops the objects that are on the stack, makes them
346  * disappear, then deletes the objects. The content will become visible.
347  *
348  * @param obj The pager object
349  * @param content The object to show
350  *
351  * @ingroup Pager
352  */
353 EAPI void
354 elm_pager_to_content_pop(Evas_Object *obj, Evas_Object *content)
355 {
356    ELM_CHECK_WIDTYPE(obj, widtype);
357    Widget_Data *wd = elm_widget_data_get(obj);
358    Eina_List *ll;
359    Item *it;
360    if (!wd) return;
361    if (!wd->stack) return;
362    it = eina_list_last(wd->stack)->data;
363    it->popme = EINA_TRUE;
364    ll = eina_list_last(wd->stack);
365    if (ll)
366      {
367        EINA_LIST_FOREACH(wd->stack, ll, it)
368           {
369             if (it->content == content)
370               {
371                  ll = eina_list_last(wd->stack);
372                  while (ll = ll->prev)
373                  {
374                    if (ll->data == it) break;
375                    else 
376                    {
377                      Item *itdel = ll->data;
378                      evas_object_del(itdel->content);
379                    }
380                  }
381                  elm_pager_content_promote(obj, content);   
382                  return;
383               }
384           }
385       }
386 }
387
388 /**
389  * Promote an object already in the pager stack to the top of the stack
390  *
391  * This will take the indicated object and promote it to the top of the stack
392  * as if it had been pushed there. The object must already be inside the
393  * pager stack to work.
394  *
395  * @param obj The pager object
396  * @param content The object to promote
397  *
398  * @ingroup Pager
399  */
400 EAPI void
401 elm_pager_content_promote(Evas_Object *obj, Evas_Object *content)
402 {
403    ELM_CHECK_WIDTYPE(obj, widtype);
404    Widget_Data *wd = elm_widget_data_get(obj);
405    Eina_List *l;
406    Item *it;
407    if (!wd) return;
408    EINA_LIST_FOREACH(wd->stack, l, it)
409      {
410         if (it->content == content)
411           {
412              wd->stack = eina_list_remove_list(wd->stack, l);
413              wd->stack = eina_list_append(wd->stack, it);
414              _eval_top(obj);
415              return;
416           }
417      }
418 }
419
420 /**
421  * Return the object at the bottom of the pager stack
422  *
423  * @param obj The pager object
424  * @return The bottom object or NULL if none
425  *
426  * @ingroup Pager
427  */
428 EAPI Evas_Object *
429 elm_pager_content_bottom_get(const Evas_Object *obj)
430 {
431    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
432    Widget_Data *wd = elm_widget_data_get(obj);
433    Item *it;
434    if (!wd) return NULL;
435    if (!wd->stack) return NULL;
436    it = wd->stack->data;
437    return it->content;
438 }
439
440 /**
441  * Return the object at the top of the pager stack
442  *
443  * @param obj The pager object
444  * @return The top object or NULL if none
445  *
446  * @ingroup Pager
447  */
448 EAPI Evas_Object *
449 elm_pager_content_top_get(const Evas_Object *obj)
450 {
451    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
452    Widget_Data *wd = elm_widget_data_get(obj);
453    Item *it;
454    if (!wd) return NULL;
455    if (!wd->stack) return NULL;
456    it = eina_list_last(wd->stack)->data;
457    return it->content;
458 }
459