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