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