Elementary: Added on-the-fly UI-mirroing support to all of the widgets
[framework/uifw/elementary.git] / src / lib / elm_panel.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Panel Panel
6  *
7  * A panel is a type of animated container that contains subobjects.  It
8  * can be expanded or contracted.
9  *
10  * Orientations are as follows:
11  * ELM_PANEL_ORIENT_TOP
12  * ELM_PANEL_ORIENT_BOTTOM
13  * ELM_PANEL_ORIENT_LEFT
14  * ELM_PANEL_ORIENT_RIGHT
15  * NOTE: Only LEFT and RIGHT orientations are implemented.
16  *
17  * THIS WIDGET IS UNDER CONSTRUCTION!
18  */
19
20 typedef struct _Widget_Data Widget_Data;
21 struct _Widget_Data 
22 {
23    Evas_Object *scr, *bx, *content;
24    Elm_Panel_Orient orient;
25    Eina_Bool hidden : 1;
26 };
27
28 static const char *widtype = NULL;
29 static void _del_hook(Evas_Object *obj);
30 static void _theme_hook(Evas_Object *obj);
31 static void _on_focus_hook(void *data, Evas_Object *obj);
32 static void _sizing_eval(Evas_Object *obj);
33 static void _resize(void *data, Evas *evas, Evas_Object *obj, void *event);
34 static void _layout(Evas_Object *o, Evas_Object_Box_Data *priv, void *data);
35 static void _toggle_panel(void *data, Evas_Object *obj, const char *emission, const char *source);
36
37 static void 
38 _del_hook(Evas_Object *obj) 
39 {
40    Widget_Data *wd = elm_widget_data_get(obj);
41    if (!wd) return;
42    free(wd);
43 }
44
45 static void
46 _mirrored_set(Evas_Object *obj, Eina_Bool rtl)
47 {
48    Widget_Data *wd = elm_widget_data_get(obj);
49    if (!wd) return;
50
51    if (wd->scr)
52      {
53         elm_widget_mirrored_set(wd->bx, rtl);
54         elm_panel_orient_set(obj, elm_panel_orient_get(obj));
55      }
56 }
57
58 static void 
59 _theme_hook(Evas_Object *obj) 
60 {
61    Widget_Data *wd = elm_widget_data_get(obj);
62    if (!wd) return;
63    _elm_widget_mirrored_reload(obj);
64    if (wd->scr)
65      {
66         Evas_Object *edj;
67         const char *str;
68
69         _mirrored_set(obj, elm_widget_mirrored_get(obj));
70         elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base",
71                                             elm_widget_style_get(obj));
72         //   scale = (elm_widget_scale_get(obj) * _elm_config->scale);
73         //   edje_object_scale_set(wd->scr, scale);
74         edj = elm_smart_scroller_edje_object_get(wd->scr);
75         str = edje_object_data_get(edj, "focus_highlight");
76         if ((str) && (!strcmp(str, "on")))
77           elm_widget_highlight_in_theme_set(obj, EINA_TRUE);
78         else
79           elm_widget_highlight_in_theme_set(obj, EINA_FALSE);
80      }
81
82    _sizing_eval(obj);
83 }
84
85 static void
86 _on_focus_hook(void *data __UNUSED__, Evas_Object *obj)
87 {
88    Widget_Data *wd = elm_widget_data_get(obj);
89    if (!wd) return;
90    if (elm_widget_focus_get(obj))
91      evas_object_focus_set(obj, EINA_TRUE);
92    else
93      evas_object_focus_set(obj, EINA_FALSE);
94 }
95
96 static Eina_Bool
97 _elm_panel_focus_next_hook(const Evas_Object *obj, Elm_Focus_Direction dir, Evas_Object **next)
98 {
99    Widget_Data *wd = elm_widget_data_get(obj);
100    Evas_Object *cur;
101
102    if ((!wd) || (!wd->content))
103      return EINA_FALSE;
104
105    cur = wd->content;
106
107    /* Try Focus cycle in subitem */
108    if (!wd->hidden)
109       return elm_widget_focus_next_get(cur, dir, next);
110
111    /* Return */
112    *next = (Evas_Object *)obj;
113    return !elm_widget_focus_get(obj);
114 }
115
116 static void
117 _signal_emit_hook(Evas_Object *obj, const char *emission, const char *source)
118 {
119    Widget_Data *wd = elm_widget_data_get(obj);
120    if (!wd) return;
121    edje_object_signal_emit(elm_smart_scroller_edje_object_get(wd->scr),
122          emission, source);
123 }
124
125 static void
126 _signal_callback_add_hook(Evas_Object *obj, const char *emission, const char *source, void (*func_cb) (void *data, Evas_Object *o, const char *emission, const char *source), void *data)
127 {
128    Widget_Data *wd = elm_widget_data_get(obj);
129    if (!wd) return;
130    edje_object_signal_callback_add(elm_smart_scroller_edje_object_get(wd->scr),
131          emission, source, func_cb, data);
132 }
133
134 static void
135 _signal_callback_del_hook(Evas_Object *obj, const char *emission, const char *source, void (*func_cb) (void *data, Evas_Object *o, const char *emission, const char *source), void *data)
136 {
137    Widget_Data *wd = elm_widget_data_get(obj);
138    edje_object_signal_callback_del_full(
139          elm_smart_scroller_edje_object_get(wd->scr), emission, source,
140          func_cb, data);
141 }
142
143 static void 
144 _sizing_eval(Evas_Object *obj) 
145 {
146    Widget_Data *wd = elm_widget_data_get(obj);
147    Evas_Coord mw = -1, mh = -1;
148    Evas_Coord vw = 0, vh = 0;
149    Evas_Coord w, h;
150    if (!wd) return;
151    evas_object_smart_calculate(wd->bx);
152    edje_object_size_min_calc(elm_smart_scroller_edje_object_get(wd->scr), 
153                              &mw, &mh);
154    evas_object_geometry_get(obj, NULL, NULL, &w, &h);
155    if (w < mw) w = mw;
156    if (h < mh) h = mh;
157    evas_object_resize(wd->scr, w, h);
158
159    evas_object_size_hint_min_get(wd->bx, &mw, &mh);
160    if (w > mw) mw = w;
161    if (h > mh) mh = h;
162    evas_object_resize(wd->bx, mw, mh);
163
164    elm_smart_scroller_child_viewport_size_get(wd->scr, &vw, &vh);
165    mw = mw + (w - vw);
166    mh = mh + (h - vh);
167    evas_object_size_hint_min_set(obj, mw, mh);
168    evas_object_size_hint_max_set(obj, -1, -1);
169 }
170
171 static void 
172 _resize(void *data, Evas *evas __UNUSED__, Evas_Object *obj __UNUSED__, void *event __UNUSED__)
173 {
174    Widget_Data *wd = elm_widget_data_get(data);
175    Evas_Coord mw, mh, vw, vh, w, h;
176    if (!wd) return;
177    elm_smart_scroller_child_viewport_size_get(wd->scr, &vw, &vh);
178    evas_object_size_hint_min_get(wd->bx, &mw, &mh);
179    evas_object_geometry_get(wd->bx, NULL, NULL, &w, &h);
180    if ((vw >= mw) || (vh >= mh))
181      {
182         if ((w != vw) || (h != vh)) evas_object_resize(wd->bx, vw, vh);
183      }
184 }
185
186 static void 
187 _layout(Evas_Object *o, Evas_Object_Box_Data *priv, void *data) 
188 {
189    Widget_Data *wd = data;
190    if (!wd) return;
191    _els_box_layout(o, priv, EINA_TRUE, EINA_FALSE, EINA_FALSE);
192 }
193
194 static void 
195 _toggle_panel(void *data, Evas_Object *obj, const char *emission __UNUSED__, const char *source __UNUSED__) 
196 {
197    Widget_Data *wd = elm_widget_data_get(data);
198    if (!wd) return;
199    if (wd->hidden) 
200      {
201         edje_object_signal_emit(elm_smart_scroller_edje_object_get(wd->scr), 
202                                 "elm,action,show", "elm");
203         wd->hidden = EINA_FALSE;
204         evas_object_repeat_events_set(obj, EINA_FALSE);
205      }
206    else
207      {
208         edje_object_signal_emit(elm_smart_scroller_edje_object_get(wd->scr), 
209                                 "elm,action,hide", "elm");
210         wd->hidden = EINA_TRUE;
211         evas_object_repeat_events_set(obj, EINA_TRUE);
212         if (elm_widget_focus_get(wd->content))
213           {
214              elm_widget_focused_object_clear(obj);
215              elm_widget_focus_steal(obj);
216           }
217      }
218 }
219
220 static Eina_Bool
221 _event_hook(Evas_Object *obj, Evas_Object *src __UNUSED__, Evas_Callback_Type type, void *event_info)
222 {
223    if ((src != obj) || (type != EVAS_CALLBACK_KEY_DOWN)) return EINA_FALSE;
224
225    Evas_Event_Key_Down *ev = event_info;
226    Widget_Data *wd = elm_widget_data_get(obj);
227    if (!wd) return EINA_FALSE;
228
229    if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
230
231    if ((strcmp(ev->keyname, "Return")) &&
232        (strcmp(ev->keyname, "KP_Enter")) &&
233        (strcmp(ev->keyname, "space")))
234      return EINA_FALSE;
235
236    _toggle_panel(obj, NULL, "elm,action,panel,toggle", "*");
237
238    ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
239    return EINA_TRUE;
240 }
241
242 /**
243  * Adds a panel object
244  *
245  * @param parent The parent object
246  *
247  * @return The panel object, or NULL on failure
248  *
249  * @ingroup Panel
250  */
251 EAPI Evas_Object *
252 elm_panel_add(Evas_Object *parent) 
253 {
254    Evas_Object *obj;
255    Evas *evas;
256    Widget_Data *wd;
257
258    EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
259
260    wd = ELM_NEW(Widget_Data);
261    evas = evas_object_evas_get(parent);
262    if (!evas) return NULL;
263    obj = elm_widget_add(evas);
264    ELM_SET_WIDTYPE(widtype, "panel");
265    elm_widget_type_set(obj, "panel");
266    elm_widget_sub_object_add(parent, obj);
267    elm_widget_data_set(obj, wd);
268    elm_widget_del_hook_set(obj, _del_hook);
269    elm_widget_theme_hook_set(obj, _theme_hook);
270    elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL);
271    elm_widget_signal_emit_hook_set(obj, _signal_emit_hook);
272    elm_widget_signal_callback_add_hook_set(obj, _signal_callback_add_hook);
273    elm_widget_signal_callback_del_hook_set(obj, _signal_callback_del_hook);
274    elm_widget_focus_next_hook_set(obj, _elm_panel_focus_next_hook);
275    elm_widget_can_focus_set(obj, EINA_TRUE);
276    elm_widget_event_hook_set(obj, _event_hook);
277
278    wd->scr = elm_smart_scroller_add(evas);
279    elm_smart_scroller_widget_set(wd->scr, obj);
280    _theme_hook(obj);
281    elm_smart_scroller_bounce_allow_set(wd->scr, EINA_FALSE, EINA_FALSE);
282    elm_widget_resize_object_set(obj, wd->scr);
283    elm_smart_scroller_policy_set(wd->scr, ELM_SMART_SCROLLER_POLICY_OFF, 
284                                  ELM_SMART_SCROLLER_POLICY_OFF);
285
286    wd->hidden = EINA_FALSE;
287    wd->orient = ELM_PANEL_ORIENT_LEFT;
288
289    wd->bx = evas_object_box_add(evas);
290    evas_object_size_hint_align_set(wd->bx, 0.5, 0.5);
291    evas_object_box_layout_set(wd->bx, _layout, wd, NULL);
292    elm_widget_sub_object_add(obj, wd->bx);
293    elm_smart_scroller_child_set(wd->scr, wd->bx);
294    evas_object_show(wd->bx);
295
296    edje_object_signal_callback_add(elm_smart_scroller_edje_object_get(wd->scr), 
297                                    "elm,action,panel,toggle", "*", 
298                                    _toggle_panel, obj);
299
300    evas_object_event_callback_add(wd->scr, EVAS_CALLBACK_RESIZE, _resize, obj);
301
302    _mirrored_set(obj, elm_widget_mirrored_get(obj));
303    _sizing_eval(obj);
304    return obj;
305 }
306
307 /**
308  * Sets the orientation of the panel
309  *
310  * @param parent The parent object
311  * @param orient The panel orientation.  Can be one of the following:
312  * ELM_PANEL_ORIENT_TOP
313  * ELM_PANEL_ORIENT_BOTTOM
314  * ELM_PANEL_ORIENT_LEFT
315  * ELM_PANEL_ORIENT_RIGHT
316  *
317  * NOTE: Only LEFT and RIGHT orientations are implemented.
318  *
319  * @ingroup Panel
320  */
321 EAPI void 
322 elm_panel_orient_set(Evas_Object *obj, Elm_Panel_Orient orient) 
323 {
324    ELM_CHECK_WIDTYPE(obj, widtype);
325    Widget_Data *wd = elm_widget_data_get(obj);
326    if (!wd) return;
327    wd->orient = orient;
328    switch (orient) 
329      {
330      case ELM_PANEL_ORIENT_TOP:
331         elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base", "top");
332         break;
333      case ELM_PANEL_ORIENT_BOTTOM:
334         elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base", "bottom");
335         break;
336      case ELM_PANEL_ORIENT_LEFT:
337         if (!elm_widget_mirrored_get(obj))
338           elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base", "left");
339         else
340           elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base", "right");
341         break;
342      case ELM_PANEL_ORIENT_RIGHT:
343         if (!elm_widget_mirrored_get(obj))
344           elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base", "right");
345         else
346           elm_smart_scroller_object_theme_set(obj, wd->scr, "panel", "base", "left");
347         break;
348      }
349
350    _sizing_eval(obj);
351 }
352
353 /**
354  * Get the orientation of the panel.
355  *
356  * @param obj The panel object
357  * @return The Elm_Panel_Orient, or ELM_PANEL_ORIENT_LEFT on failure.
358  *
359  * @ingroup Panel
360  */
361 EAPI Elm_Panel_Orient
362 elm_panel_orient_get(const Evas_Object *obj)
363 {
364    ELM_CHECK_WIDTYPE(obj, widtype) ELM_PANEL_ORIENT_LEFT;
365    Widget_Data *wd = elm_widget_data_get(obj);
366    if (!wd) return ELM_PANEL_ORIENT_LEFT;
367    return wd->orient;   
368 }
369
370 /**
371  * Set the content of the panel.
372  *
373  * Once the content object is set, a previously set one will be deleted.
374  * If you want to keep that old content object, use the
375  * elm_panel_content_unset() function.
376  *
377  * @param obj The panel object
378  * @param content The panel content
379  *
380  * @ingroup Panel
381  */
382 EAPI void
383 elm_panel_content_set(Evas_Object *obj, Evas_Object *content)
384 {
385    ELM_CHECK_WIDTYPE(obj, widtype);
386    Widget_Data *wd = elm_widget_data_get(obj);
387    if (!wd) return;
388    if (wd->content == content) return;
389    if (wd->content)
390      evas_object_box_remove_all(wd->bx, EINA_TRUE);
391    wd->content = content;
392    if (content)
393      {
394         evas_object_box_append(wd->bx, wd->content);
395         evas_object_show(wd->content);
396      }
397    _sizing_eval(obj);
398 }
399
400 /**
401  * Get the content of the panel.
402  *
403  * Return the content object which is set for this widget.
404  *
405  * @param obj The panel object
406  * @return The content that is being used
407  *
408  * @ingroup Panel
409  */
410 EAPI Evas_Object *
411 elm_panel_content_get(const Evas_Object *obj)
412 {
413    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
414    Widget_Data *wd = elm_widget_data_get(obj);
415    if (!wd) return NULL;
416    return wd->content;
417 }
418
419 /**
420  * Unset the content of the panel.
421  *
422  * Unparent and return the content object which was set for this widget.
423  *
424  * @param obj The panel object
425  * @return The content that was being used
426  *
427  * @ingroup Panel
428  */
429 EAPI Evas_Object *
430 elm_panel_content_unset(Evas_Object *obj)
431 {
432    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
433    Widget_Data *wd = elm_widget_data_get(obj);
434    Evas_Object *content;
435    if (!wd) return NULL;
436    if (!wd->content) return NULL;
437    content = wd->content;
438    evas_object_box_remove_all(wd->bx, EINA_FALSE);
439    wd->content = NULL;
440    return content;
441 }
442
443 /**
444  * Set the state of the panel.
445  *
446  * @param obj The panel object
447  * @param hidden If true, the panel will run the edje animation to contract
448  *
449  * @ingroup Panel
450  */
451 EAPI void
452 elm_panel_hidden_set(Evas_Object *obj, Eina_Bool hidden)
453 {
454    ELM_CHECK_WIDTYPE(obj, widtype);
455    Widget_Data *wd = elm_widget_data_get(obj);
456    if (!wd) return;
457    if (wd->hidden == hidden) return;
458    _toggle_panel(obj, NULL, "elm,action,panel,toggle", "*");
459 }
460
461 /**
462  * Get the state of the panel.
463  *
464  * @param obj The panel object
465  * @param hidden If true, the panel is in the "hide" state
466  *
467  * @ingroup Panel
468  */
469 EAPI Eina_Bool
470 elm_panel_hidden_get(const Evas_Object *obj)
471 {
472    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
473    Widget_Data *wd = elm_widget_data_get(obj);
474    if (!wd) return EINA_FALSE;
475    return wd->hidden;
476 }
477
478 /**
479  * Toggle the state of the panel from code
480  *
481  * @param obj The panel object
482  *
483  * @ingroup Panel
484  */
485 EAPI void
486 elm_panel_toggle(Evas_Object *obj)
487 {
488    ELM_CHECK_WIDTYPE(obj, widtype);
489    Widget_Data *wd = elm_widget_data_get(obj);
490    if (!wd) return;
491    wd->hidden = !(wd->hidden);
492    _toggle_panel(obj, NULL, "elm,action,panel,toggle", "*");
493 }