[efl-upgrade]
[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              if (wd->top->popme)
117                 edje_object_signal_emit(o, "elm,action,pop", "elm");
118              else
119                 edje_object_signal_emit(o, "elm,action,hide", "elm");
120              onhide = edje_object_data_get(o, "onhide");
121              if (onhide)
122                {
123                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
124                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
125                }
126           }
127         else
128           {
129              animate = EINA_FALSE;
130           }
131         wd->top = ittop;
132         o = wd->top->base;
133         evas_object_show(o);
134         wd->oldtop = wd->top;
135         wd->top = ittop;
136         o = wd->top->base;
137         evas_object_show(o);
138         if (!animate)
139                 edje_object_signal_emit(o, "elm,action,show,noanimate", "elm");
140         else if (wd->oldtop && wd->oldtop->popme)
141             edje_object_signal_emit(o, "elm,action,show", "elm");
142         else
143             edje_object_signal_emit(o, "elm,action,push", "elm");
144         onshow = edje_object_data_get(o, "onshow");
145         if (onshow)
146           {
147              if (!strcmp(onshow, "raise")) evas_object_raise(o);
148              else if (!strcmp(onshow, "lower")) evas_object_lower(o);
149           }
150      }
151 }
152
153 static void
154 _move(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
155 {
156    Widget_Data *wd = elm_widget_data_get(data);
157    Evas_Coord x, y;
158    Eina_List *l;
159    Item *it;
160    if (!wd) return;
161    evas_object_geometry_get(obj, &x, &y, NULL, NULL);
162    EINA_LIST_FOREACH(wd->stack, l, it)
163      evas_object_move(it->base, x, y);
164 }
165
166 static void
167 _sub_del(void *data, Evas_Object *obj __UNUSED__, void *event_info)
168 {
169    Widget_Data *wd = elm_widget_data_get(data);
170    Evas_Object *sub = event_info;
171    Eina_List *l;
172    Item *it;
173    if (!wd) return;
174    EINA_LIST_FOREACH(wd->stack, l, it)
175      {
176         if (it->content == sub)
177           {
178              wd->stack = eina_list_remove_list(wd->stack, l);
179              evas_object_event_callback_del_full
180                (sub, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, it);
181              evas_object_del(it->base);
182              _eval_top(it->obj);
183              free(it);
184              return;
185           }
186      }
187 }
188
189 static void
190 _resize(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
191 {   
192    Widget_Data *wd = elm_widget_data_get(data);
193    Evas_Coord w, h;
194    Eina_List *l;
195    Item *it;
196    if (!wd) return;
197    evas_object_geometry_get(obj, NULL, NULL, &w, &h);
198    EINA_LIST_FOREACH(wd->stack, l, it) evas_object_resize(it->base, w, h);
199 }
200
201 static void
202 _signal_hide_finished(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
203 {
204    Item *it = data;
205    Evas_Object *obj2 = it->obj;
206    evas_object_hide(it->base);
207    edje_object_signal_emit(it->base, "elm,action,reset", "elm");
208    evas_object_smart_callback_call(obj2, "hide,finished", it->content);
209    edje_object_message_signal_process(it->base);
210    if (it->popme) evas_object_del(it->content);
211    _sizing_eval(obj2);
212 }
213
214 /**
215  * Add a new pager to the parent
216  *
217  * @param parent The parent object
218  * @return The new object or NULL if it cannot be created
219  *
220  * @ingroup Pager
221  */
222 EAPI Evas_Object *
223 elm_pager_add(Evas_Object *parent)
224 {
225    Evas_Object *obj;
226    Evas *e;
227    Widget_Data *wd;
228
229    wd = ELM_NEW(Widget_Data);
230    e = evas_object_evas_get(parent);
231    obj = elm_widget_add(e);
232    ELM_SET_WIDTYPE(widtype, "pager");
233    elm_widget_type_set(obj, "pager");
234    elm_widget_sub_object_add(parent, obj);
235    elm_widget_data_set(obj, wd);
236    elm_widget_del_hook_set(obj, _del_hook);
237    elm_widget_theme_hook_set(obj, _theme_hook);
238
239    wd->clip = evas_object_rectangle_add(e);
240    elm_widget_resize_object_set(obj, wd->clip);
241    elm_widget_sub_object_add(obj, wd->clip);
242
243    wd->rect = evas_object_rectangle_add(e);
244    elm_widget_sub_object_add(obj, wd->rect);
245    evas_object_color_set(wd->rect, 255, 255, 255, 0); 
246    evas_object_clip_set(wd->rect, wd->clip);
247
248    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, _move, obj);
249    evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _resize, obj);
250
251    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
252
253    _sizing_eval(obj);
254    return obj;
255 }
256
257 /**
258  * Push an object to the top of the pager stack (and show it)
259  *
260  * The object pushed becomes a child of the pager and will be controlled
261  * it and deleted when the pager is deleted.
262  *
263  * @param obj The pager object
264  * @param content The object to push
265  *
266  * @ingroup Pager
267  */
268 EAPI void
269 elm_pager_content_push(Evas_Object *obj, Evas_Object *content)
270 {
271    ELM_CHECK_WIDTYPE(obj, widtype);
272    Widget_Data *wd = elm_widget_data_get(obj);
273    Item *it = ELM_NEW(Item);
274    Evas_Coord x, y, w, h;
275    if (!wd) return;
276    if (!it) return;
277    it->obj = obj;
278    it->content = content;
279    it->base = edje_object_add(evas_object_evas_get(obj));
280    evas_object_smart_member_add(it->base, obj);
281    evas_object_geometry_get(obj, &x, &y, &w, &h);
282    evas_object_move(it->base, x, y);
283    evas_object_resize(it->base, w, h);
284    evas_object_clip_set(it->base, wd->clip);
285    elm_widget_sub_object_add(obj, it->base);
286    elm_widget_sub_object_add(obj, it->content);
287    _elm_theme_object_set(obj, it->base,  "pager", "base", elm_widget_style_get(obj));
288    edje_object_signal_callback_add(it->base, "elm,action,hide,finished", "", 
289                                    _signal_hide_finished, it);
290    evas_object_event_callback_add(it->content,
291                                   EVAS_CALLBACK_CHANGED_SIZE_HINTS,
292                                   _changed_size_hints, it);
293    edje_object_part_swallow(it->base, "elm.swallow.content", it->content);
294    edje_object_size_min_calc(it->base, &it->minw, &it->minh);
295    evas_object_show(it->content);
296    wd->stack = eina_list_append(wd->stack, it);
297    _eval_top(obj);
298    _sizing_eval(obj);
299 }
300
301 /**
302  * Pop the object that is on top of the stack
303  *
304  * This pops the object that is on top (visible) in the pager, makes it
305  * disappear, then deletes the object. The object that was underneath it
306  * on the stack will become visible.
307  *
308  * @param obj The pager object
309  *
310  * @ingroup Pager
311  */
312 EAPI void
313 elm_pager_content_pop(Evas_Object *obj)
314 {
315    ELM_CHECK_WIDTYPE(obj, widtype);
316    Widget_Data *wd = elm_widget_data_get(obj);
317    Eina_List *ll;
318    Item *it;
319    if (!wd) return;
320    if (!wd->stack) return;
321    it = eina_list_last(wd->stack)->data;
322    it->popme = EINA_TRUE;
323    ll = eina_list_last(wd->stack);
324    if (ll)
325      {
326         ll = ll->prev;
327         if (!ll)
328           {
329              Evas_Object *o;
330              const char *onhide;
331
332              wd->top = it;
333              o = wd->top->base;
334              edje_object_signal_emit(o, "elm,action,pop", "elm");
335              onhide = edje_object_data_get(o, "onhide");
336              if (onhide)
337                {
338                   if (!strcmp(onhide, "raise")) evas_object_raise(o);
339                   else if (!strcmp(onhide, "lower")) evas_object_lower(o);
340                }
341              wd->top = NULL;
342           }
343         else
344           {
345              it = ll->data;
346              elm_pager_content_promote(obj, it->content);
347           }
348      }
349 }
350 static void
351 _del_job(void *data)
352 {
353    Evas_Object *obj = data;
354    evas_object_del(obj);
355 }
356
357 /**
358  * Pop to the object that is on the stack
359  *
360  * This pops the objects that are on the stack, makes them
361  * disappear, then deletes the objects. The content will become visible.
362  *
363  * @param obj The pager object
364  * @param content The object to show
365  *
366  * @ingroup Pager
367  */
368 EAPI void
369 elm_pager_to_content_pop(Evas_Object *obj, Evas_Object *content)
370 {
371    ELM_CHECK_WIDTYPE(obj, widtype);
372    Widget_Data *wd = elm_widget_data_get(obj);
373    Eina_List *ll;
374    Item *it;
375    if (!wd) return;
376    if (!wd->stack) return;
377    it = eina_list_last(wd->stack)->data;
378    it->popme = EINA_TRUE;
379    ll = eina_list_last(wd->stack);
380    if (ll)
381      {
382        while(ll)
383                         {
384                                 it = ll->data;
385                                 if(it->content != content)
386                                         {
387                                                 wd->stack = eina_list_remove_list(wd->stack, ll);
388                                                 ecore_job_add(_del_job, it->content);
389                                         }
390                                 else
391                                         break;
392                                 
393                                 ll = ll->prev;
394                         }
395       }
396    _eval_top(it->obj);
397 }
398
399 /**
400  * Promote an object already in the pager stack to the top of the stack
401  *
402  * This will take the indicated object and promote it to the top of the stack
403  * as if it had been pushed there. The object must already be inside the
404  * pager stack to work.
405  *
406  * @param obj The pager object
407  * @param content The object to promote
408  *
409  * @ingroup Pager
410  */
411 EAPI void
412 elm_pager_content_promote(Evas_Object *obj, Evas_Object *content)
413 {
414    ELM_CHECK_WIDTYPE(obj, widtype);
415    Widget_Data *wd = elm_widget_data_get(obj);
416    Eina_List *l;
417    Item *it;
418    if (!wd) return;
419    EINA_LIST_FOREACH(wd->stack, l, it)
420      {
421         if (it->content == content)
422           {
423              wd->stack = eina_list_remove_list(wd->stack, l);
424              wd->stack = eina_list_append(wd->stack, it);
425              _eval_top(obj);
426              return;
427           }
428      }
429 }
430
431 /**
432  * Return the object at the bottom of the pager stack
433  *
434  * @param obj The pager object
435  * @return The bottom object or NULL if none
436  *
437  * @ingroup Pager
438  */
439 EAPI Evas_Object *
440 elm_pager_content_bottom_get(const Evas_Object *obj)
441 {
442    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
443    Widget_Data *wd = elm_widget_data_get(obj);
444    Item *it;
445    if (!wd) return NULL;
446    if (!wd->stack) return NULL;
447    it = wd->stack->data;
448    return it->content;
449 }
450
451 /**
452  * Return the object at the top of the pager stack
453  *
454  * @param obj The pager object
455  * @return The top object or NULL if none
456  *
457  * @ingroup Pager
458  */
459 EAPI Evas_Object *
460 elm_pager_content_top_get(const Evas_Object *obj)
461 {
462    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
463    Widget_Data *wd = elm_widget_data_get(obj);
464    Item *it;
465    if (!wd) return NULL;
466    if (!wd->stack) return NULL;
467    it = eina_list_last(wd->stack)->data;
468    return it->content;
469 }
470