================================================================
[framework/uifw/elementary.git] / src / lib / elm_toggle.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Toggle Toggle
6  * @ingroup Elementary
7  *
8  * A toggle is a slider which can be used to toggle between
9  * two values.  It has two states: on and off.
10  *
11  * Signals that you can add callbacks for are:
12  *
13  * changed - Whenever the toggle value has been changed.  Is not called
14  * until the toggle is released by the cursor (assuming it has been triggered
15  * by the cursor in the first place).
16  */
17
18 typedef struct _Widget_Data Widget_Data;
19
20 struct _Widget_Data
21 {
22    Evas_Object *tgl;
23    Evas_Object *icon;
24    Eina_Bool state;
25    Eina_Bool *statep;
26    const char *label;
27    const char *ontext, *offtext;
28 };
29
30 static const char *widtype = NULL;
31 static void _del_hook(Evas_Object *obj);
32 static void _disable_hook(Evas_Object *obj);
33 static void _theme_hook(Evas_Object *obj);
34 static void _sizing_eval(Evas_Object *obj);
35 static void _changed_size_hints(void *data, Evas *e, Evas_Object *obj, void *event_info);
36 static void _sub_del(void *data, Evas_Object *obj, void *event_info);
37 static void _signal_toggle_off(void *data, Evas_Object *obj, const char *emission, const char *source);
38 static void _signal_toggle_on(void *data, Evas_Object *obj, const char *emission, const char *source);
39 static void _on_focus_hook(void *data, Evas_Object *obj);
40 static Eina_Bool _event_hook(Evas_Object *obj, Evas_Object *src,
41                              Evas_Callback_Type type, void *event_info);
42
43 static const char SIG_CHANGED[] = "changed";
44 static const Evas_Smart_Cb_Description _signals[] = {
45   {SIG_CHANGED, ""},
46   {NULL, NULL}
47 };
48
49 static Eina_Bool
50 _event_hook(Evas_Object *obj, Evas_Object *src __UNUSED__, Evas_Callback_Type type, void *event_info)
51 {
52    if (type != EVAS_CALLBACK_KEY_DOWN) return EINA_FALSE;
53    Evas_Event_Key_Down *ev = event_info;
54    Widget_Data *wd = elm_widget_data_get(obj);
55    if (!wd) return EINA_FALSE;
56    if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
57    if (elm_widget_disabled_get(obj)) return EINA_FALSE;
58    if ((strcmp(ev->keyname, "Return")) &&
59        (strcmp(ev->keyname, "KP_Enter")) &&
60        (strcmp(ev->keyname, "space")))
61      return EINA_FALSE;
62    elm_toggle_state_set(obj, !wd->state);
63    evas_object_smart_callback_call(obj, SIG_CHANGED, NULL);
64    ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
65    return EINA_TRUE;
66 }
67
68 static void
69 _del_hook(Evas_Object *obj)
70 {
71    Widget_Data *wd = elm_widget_data_get(obj);
72    if (!wd) return;
73    if (wd->label) eina_stringshare_del(wd->label);
74    if (wd->ontext) eina_stringshare_del(wd->ontext);
75    if (wd->offtext) eina_stringshare_del(wd->offtext);
76    free(wd);
77 }
78
79 static void
80 _disable_hook(Evas_Object *obj)
81 {
82    Widget_Data *wd = elm_widget_data_get(obj);
83    if (!wd) return;
84    if (elm_widget_disabled_get(obj))
85      edje_object_signal_emit(wd->tgl, "elm,state,disabled", "elm");
86    else
87      edje_object_signal_emit(wd->tgl, "elm,state,enabled", "elm");
88 }
89
90 static void
91 _on_focus_hook(void *data __UNUSED__, Evas_Object *obj)
92 {
93    Widget_Data *wd = elm_widget_data_get(obj);
94    if (!wd) return;
95    if (elm_widget_focus_get(obj))
96      {
97         edje_object_signal_emit(wd->tgl, "elm,action,focus", "elm");
98         evas_object_focus_set(wd->tgl, EINA_TRUE);
99      }
100    else
101      {
102         edje_object_signal_emit(wd->tgl, "elm,action,unfocus", "elm");
103         evas_object_focus_set(wd->tgl, EINA_FALSE);
104      }
105 }
106
107 static void
108 _theme_hook(Evas_Object *obj)
109 {
110    Widget_Data *wd = elm_widget_data_get(obj);
111    if (!wd) return;
112    _elm_theme_object_set(obj, wd->tgl, "toggle", "base", elm_widget_style_get(obj));
113    if (wd->icon)
114      edje_object_signal_emit(wd->tgl, "elm,state,icon,visible", "elm");
115    else
116      edje_object_signal_emit(wd->tgl, "elm,state,icon,hidden", "elm");
117    if (wd->state)
118      edje_object_signal_emit(wd->tgl, "elm,state,toggle,on", "elm");
119    else
120      edje_object_signal_emit(wd->tgl, "elm,state,toggle,off", "elm");
121    if (wd->label)
122      edje_object_signal_emit(wd->tgl, "elm,state,text,visible", "elm");
123    else
124      edje_object_signal_emit(wd->tgl, "elm,state,text,hidden", "elm");
125    edje_object_part_text_set(wd->tgl, "elm.text", wd->label);
126    edje_object_part_text_set(wd->tgl, "elm.ontext", wd->ontext);
127    edje_object_part_text_set(wd->tgl, "elm.offtext", wd->offtext);
128    if (elm_widget_disabled_get(obj))
129      edje_object_signal_emit(wd->tgl, "elm,state,disabled", "elm");
130    edje_object_message_signal_process(wd->tgl);
131    edje_object_scale_set(wd->tgl, elm_widget_scale_get(obj) * _elm_config->scale);
132    _sizing_eval(obj);
133 }
134
135 static void
136 _sizing_eval(Evas_Object *obj)
137 {
138    Widget_Data *wd = elm_widget_data_get(obj);
139    Evas_Coord minw = -1, minh = -1;
140
141    if (!wd) return;
142    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
143    edje_object_size_min_restricted_calc(wd->tgl, &minw, &minh, minw, minh);
144    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
145    evas_object_size_hint_min_set(obj, minw, minh);
146    evas_object_size_hint_max_set(obj, -1, -1);
147 }
148
149 static void
150 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
151 {
152    Widget_Data *wd = elm_widget_data_get(data);
153    if (!wd) return;
154    if (obj != wd->icon) return;
155    _sizing_eval(data);
156 }
157
158 static void
159 _sub_del(void *data __UNUSED__, Evas_Object *obj, void *event_info)
160 {
161    Widget_Data *wd = elm_widget_data_get(obj);
162    Evas_Object *sub = event_info;
163    if (!wd) return;
164    if (sub == wd->icon)
165      {
166         edje_object_signal_emit(wd->tgl, "elm,state,icon,hidden", "elm");
167         evas_object_event_callback_del_full
168           (sub, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, obj);
169         wd->icon = NULL;
170         edje_object_message_signal_process(wd->tgl);
171         _sizing_eval(obj);
172      }
173 }
174
175 static void
176 _signal_toggle_off(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
177 {
178    Widget_Data *wd = elm_widget_data_get(data);
179    if (!wd) return;
180    wd->state = 0;
181    if (wd->statep) *wd->statep = wd->state;
182    evas_object_smart_callback_call(data, SIG_CHANGED, NULL);
183 }
184
185 static void
186 _signal_toggle_on(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
187 {
188    Widget_Data *wd = elm_widget_data_get(data);
189    if (!wd) return;
190    wd->state = 1;
191    if (wd->statep) *wd->statep = wd->state;
192    evas_object_smart_callback_call(data, SIG_CHANGED, NULL);
193 }
194
195 /**
196  * Add a toggle to @p parent.
197  *
198  * @param parent The parent object
199  *
200  * @return The toggle object
201  *
202  * @ingroup Toggle
203  */
204 EAPI Evas_Object *
205 elm_toggle_add(Evas_Object *parent)
206 {
207    Evas_Object *obj;
208    Evas *e;
209    Widget_Data *wd;
210
211    EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
212
213    wd = ELM_NEW(Widget_Data);
214    e = evas_object_evas_get(parent);
215    if (!e) return NULL;
216    obj = elm_widget_add(e);
217    ELM_SET_WIDTYPE(widtype, "toggle");
218    elm_widget_type_set(obj, "toggle");
219    elm_widget_sub_object_add(parent, obj);
220    elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL);
221    elm_widget_data_set(obj, wd);
222    elm_widget_del_hook_set(obj, _del_hook);
223    elm_widget_theme_hook_set(obj, _theme_hook);
224    elm_widget_disable_hook_set(obj, _disable_hook);
225    elm_widget_can_focus_set(obj, EINA_TRUE);
226    elm_widget_event_hook_set(obj, _event_hook);
227
228    wd->tgl = edje_object_add(e);
229    _elm_theme_object_set(obj, wd->tgl, "toggle", "base", "default");
230    wd->ontext = eina_stringshare_add("ON");
231    wd->offtext = eina_stringshare_add("OFF");
232    edje_object_signal_callback_add(wd->tgl, "elm,action,toggle,on", "",
233                                    _signal_toggle_on, obj);
234    edje_object_signal_callback_add(wd->tgl, "elm,action,toggle,off", "",
235                                    _signal_toggle_off, obj);
236    elm_widget_resize_object_set(obj, wd->tgl);
237    edje_object_part_text_set(wd->tgl, "elm.ontext", wd->ontext);
238    edje_object_part_text_set(wd->tgl, "elm.offtext", wd->offtext);
239
240    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
241
242    _sizing_eval(obj);
243
244    // TODO: convert Elementary to subclassing of Evas_Smart_Class
245    // TODO: and save some bytes, making descriptions per-class and not instance!
246    evas_object_smart_callbacks_descriptions_set(obj, _signals);
247    return obj;
248 }
249
250 /**
251  * Sets the label to be displayed with the toggle.
252  *
253  * @param obj The toggle object
254  * @param label The label to be displayed
255  *
256  * @ingroup Toggle
257  */
258 EAPI void
259 elm_toggle_label_set(Evas_Object *obj, const char *label)
260 {
261    ELM_CHECK_WIDTYPE(obj, widtype);
262    Widget_Data *wd = elm_widget_data_get(obj);
263    if (!wd) return;
264    eina_stringshare_replace(&wd->label, label);
265    if (label)
266      edje_object_signal_emit(wd->tgl, "elm,state,text,visible", "elm");
267    else
268      edje_object_signal_emit(wd->tgl, "elm,state,text,hidden", "elm");
269    edje_object_message_signal_process(wd->tgl);
270    edje_object_part_text_set(wd->tgl, "elm.text", label);
271    _sizing_eval(obj);
272 }
273
274 /**
275  * Gets the label of the toggle
276  *
277  * @param obj  toggleeee object
278  * @return The label of the toggle
279  *
280  * @ingroup Toggle
281  */
282 EAPI const char *
283 elm_toggle_label_get(const Evas_Object *obj)
284 {
285    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
286    Widget_Data *wd = elm_widget_data_get(obj);
287    if (!wd) return NULL;
288    return wd->label;
289 }
290
291 /**
292  * Set the icon used for the toggle
293  *
294  * Once the icon object is set, a previously set one will be deleted
295  * If you want to keep that old content object, use the
296  * elm_toggle_icon_unset() function.
297  *
298  * @param obj The toggle object
299  * @param icon The icon object for the button
300  *
301  * @ingroup Toggle
302  */
303 EAPI void
304 elm_toggle_icon_set(Evas_Object *obj, Evas_Object *icon)
305 {
306    ELM_CHECK_WIDTYPE(obj, widtype);
307    Widget_Data *wd = elm_widget_data_get(obj);
308    if (!wd) return;
309    if (wd->icon == icon) return;
310    if (wd->icon) evas_object_del(wd->icon);
311    wd->icon = icon;
312    if (icon)
313      {
314         elm_widget_sub_object_add(obj, icon);
315         evas_object_event_callback_add(icon, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
316               _changed_size_hints, obj);
317         edje_object_part_swallow(wd->tgl, "elm.swallow.content", icon);
318         edje_object_signal_emit(wd->tgl, "elm,state,icon,visible", "elm");
319         edje_object_message_signal_process(wd->tgl);
320      }
321    _sizing_eval(obj);
322 }
323
324 /**
325  * Get the icon used for the toggle
326  *
327  * Return the icon object which is set for this widget.
328  *
329  * @param obj The toggle object
330  * @return The icon object that is being used
331  *
332  * @ingroup Toggle
333  */
334 EAPI Evas_Object *
335 elm_toggle_icon_get(const Evas_Object *obj)
336 {
337    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
338    Widget_Data *wd = elm_widget_data_get(obj);
339    if (!wd) return NULL;
340    return wd->icon;
341 }
342
343 /**
344  * Unset the icon used for the toggle
345  *
346  * Unparent and return the icon object which was set for this widget.
347  *
348  * @param obj The toggle object
349  * @return The icon object that was being used
350  *
351  * @ingroup Toggle
352  */
353 EAPI Evas_Object *
354 elm_toggle_icon_unset(Evas_Object *obj)
355 {
356    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
357    Widget_Data *wd = elm_widget_data_get(obj);
358    if (!wd) return NULL;
359    if (!wd->icon) return NULL;
360    Evas_Object *icon = wd->icon;
361    elm_widget_sub_object_del(obj, wd->icon);
362    edje_object_part_unswallow(wd->tgl, wd->icon);
363    wd->icon = NULL;
364    return icon;
365 }
366
367 /**
368  * Sets the labels to be associated with the on and off states of the toggle.
369  *
370  * @param obj The toggle object
371  * @param onlabel The label displayed when the toggle is in the "on" state
372  * @param offlabel The label displayed when the toggle is in the "off" state
373  *
374  * @ingroup Toggle
375  */
376 EAPI void
377 elm_toggle_states_labels_set(Evas_Object *obj, const char *onlabel, const char *offlabel)
378 {
379    ELM_CHECK_WIDTYPE(obj, widtype);
380    Widget_Data *wd = elm_widget_data_get(obj);
381    if (!wd) return;
382    eina_stringshare_replace(&wd->ontext, onlabel);
383    eina_stringshare_replace(&wd->offtext, offlabel);
384    edje_object_part_text_set(wd->tgl, "elm.ontext", onlabel);
385    edje_object_part_text_set(wd->tgl, "elm.offtext", offlabel);
386    _sizing_eval(obj);
387 }
388
389
390 /**
391  * Gets the labels associated with the on and off states of the toggle.
392  *
393  * @param obj The toggle object
394  * @param onlabel A char** to place the onlabel of @p obj into
395  * @param offlabel A char** to place the offlabel of @p obj into
396  *
397  * @ingroup Toggle
398  */
399 EAPI void
400 elm_toggle_states_labels_get(const Evas_Object *obj, const char **onlabel, const char **offlabel)
401 {
402    if (onlabel) *onlabel = NULL;
403    if (offlabel) *offlabel = NULL;
404    ELM_CHECK_WIDTYPE(obj, widtype);
405    Widget_Data *wd = elm_widget_data_get(obj);
406    if (!wd) return;
407    if (onlabel) *onlabel = wd->ontext;
408    if (offlabel) *offlabel = wd->offtext;
409 }
410
411 /**
412  * Sets the state of the toggle to @p state.
413  *
414  * @param obj The toggle object
415  * @param state The state of @p obj
416  *
417  * @ingroup Toggle
418  */
419 EAPI void
420 elm_toggle_state_set(Evas_Object *obj, Eina_Bool state)
421 {
422    ELM_CHECK_WIDTYPE(obj, widtype);
423    Widget_Data *wd = elm_widget_data_get(obj);
424    if (!wd) return;
425    if (state != wd->state)
426      {
427         wd->state = state;
428         if (wd->statep) *wd->statep = wd->state;
429         if (wd->state)
430           edje_object_signal_emit(wd->tgl, "elm,state,toggle,on", "elm");
431         else
432           edje_object_signal_emit(wd->tgl, "elm,state,toggle,off", "elm");
433      }
434 }
435
436 /**
437  * Gets the state of the toggle to @p state.
438  *
439  * @param obj The toggle object
440  * @return The state of @p obj
441  *
442  * @ingroup Toggle
443  */
444 EAPI Eina_Bool
445 elm_toggle_state_get(const Evas_Object *obj)
446 {
447    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
448    Widget_Data *wd = elm_widget_data_get(obj);
449    if (!wd) return EINA_FALSE;
450    return wd->state;
451 }
452
453 /**
454  * Sets the state pointer of the toggle to @p statep.
455  *
456  * @param obj The toggle object
457  * @param statep The state pointer of @p obj
458  *
459  * @ingroup Toggle
460  */
461 EAPI void
462 elm_toggle_state_pointer_set(Evas_Object *obj, Eina_Bool *statep)
463 {
464    ELM_CHECK_WIDTYPE(obj, widtype);
465    Widget_Data *wd = elm_widget_data_get(obj);
466    if (!wd) return;
467    if (statep)
468      {
469         wd->statep = statep;
470         if (*wd->statep != wd->state)
471           {
472              wd->state = *wd->statep;
473              if (wd->state)
474                edje_object_signal_emit(wd->tgl, "elm,state,toggle,on", "elm");
475              else
476                edje_object_signal_emit(wd->tgl, "elm,state,toggle,off", "elm");
477           }
478      }
479    else
480      wd->statep = NULL;
481 }