473c7958c8a97a6f3c5b342887e5bf24e7953610
[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
351 /**
352  * Pop to the object that is on the stack
353  *
354  * This pops the objects that are on the stack, makes them
355  * disappear, then deletes the objects. The content will become visible.
356  *
357  * @param obj The pager object
358  * @param content The object to show
359  *
360  * @ingroup Pager
361  */
362 EAPI void
363 elm_pager_to_content_pop(Evas_Object *obj, Evas_Object *content)
364 {
365    ELM_CHECK_WIDTYPE(obj, widtype);
366    Widget_Data *wd = elm_widget_data_get(obj);
367    Eina_List *ll;
368    Item *it;
369    if (!wd) return;
370    if (!wd->stack) return;
371    it = eina_list_last(wd->stack)->data;
372    it->popme = EINA_TRUE;
373    ll = eina_list_last(wd->stack);
374    if (ll)
375      {
376        EINA_LIST_FOREACH(wd->stack, ll, it)
377           {
378             if (it->content == content)
379               {
380                  ll = eina_list_last(wd->stack);
381                  while (ll = ll->prev)
382                  {
383                    if (ll->data == it) break;
384                    else 
385                    {
386                      Item *itdel = ll->data;
387                      evas_object_del(itdel->content);
388                    }
389                  }
390                  elm_pager_content_promote(obj, content);   
391                  return;
392               }
393           }
394       }
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