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