e2a65e93354d60059e4efbcd00db21bf04764f81
[framework/uifw/elementary.git] / src / lib / elm_radio.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Radio Radio
6  * @ingroup Elementary
7  *
8  * The radio button allows for 1 or more selectors to be created to select 1
9  * of a set of options.
10  *
11  * Signals that you can add callbacks for are:
12  *
13  * changed - This is called whenever the user changes the state of one of the
14  * radio objects within the group of radio objects that work together.
15  *
16  * A radio object contains an indicator, an optional Label and an optional
17  * icon object. They work normally in groups of 2 or more. When you create a
18  * radio (if it is not the first member of the group), simply add it to the
19  * group by adding it to any other member of the group that already exists
20  * (or the first member) with elm_radio_group_add() with the second parameter
21  * being the existing group member. The radio object(s) will select from one
22  * of a set of integer values, so any value they are configuring needs to be
23  * mapped to a set of integers. To configure what value that radio object
24  * represents, use  elm_radio_state_value_set() to set the integer it
25  * represents. To set the value the whole group is to indicate use
26  * elm_radio_value_set() on any group member, and to get the groups value use
27  * elm_radio_value_get(). For convenience the radio objects are also able to
28  * directly set an integer (int) to the value that is selected. To specify
29  * the pointer to this integer to modify, use elm_radio_value_pointer_set().
30  * The radio objects will modify this directly. That implies the pointer must
31  * point to valid memory for as long as the radio objects exist.
32  */
33
34 typedef struct _Widget_Data Widget_Data;
35 typedef struct _Group Group;
36
37 struct _Group
38 {
39    int value;
40    int *valuep;
41    Eina_List *radios;
42 };
43
44 struct _Widget_Data
45 {
46    Evas_Object *radio;
47    Evas_Object *icon;
48    int value;
49    const char *label;
50    Eina_Bool state;
51    Group *group;
52 };
53
54 static const char *widtype = NULL;
55 static void _state_set(Evas_Object *obj, Eina_Bool state);
56 static void _del_hook(Evas_Object *obj);
57 static void _theme_hook(Evas_Object *obj);
58 static void _disable_hook(Evas_Object *obj);
59 static void _sizing_eval(Evas_Object *obj);
60 static void _changed_size_hints(void *data, Evas *e, Evas_Object *obj, void *event_info);
61 static void _sub_del(void *data, Evas_Object *obj, void *event_info);
62 static void _signal_radio_on(void *data, Evas_Object *obj, const char *emission, const char *source);
63
64 static const char SIG_CHANGED[] = "changed";
65 static const Evas_Smart_Cb_Description _signals[] = {
66   {SIG_CHANGED, ""},
67   {NULL, NULL}
68 };
69
70 static void
71 _del_hook(Evas_Object *obj)
72 {
73    Widget_Data *wd = elm_widget_data_get(obj);
74    if (!wd) return;
75    if (wd->label) eina_stringshare_del(wd->label);
76    wd->group->radios = eina_list_remove(wd->group->radios, obj);
77    if (!wd->group->radios) free(wd->group);
78    wd->group = NULL;
79    free(wd);
80 }
81
82 static void
83 _theme_hook(Evas_Object *obj)
84 {
85    Widget_Data *wd = elm_widget_data_get(obj);
86    if (!wd) return;
87    _elm_theme_object_set(obj, wd->radio, "radio", "base", elm_widget_style_get(obj));
88    if (wd->icon)
89      edje_object_signal_emit(wd->radio, "elm,state,icon,visible", "elm");
90    else
91      edje_object_signal_emit(wd->radio, "elm,state,icon,hidden", "elm");
92    if (wd->state)
93      edje_object_signal_emit(wd->radio, "elm,state,radio,on", "elm");
94    else
95      edje_object_signal_emit(wd->radio, "elm,state,radio,off", "elm");
96    if (wd->label)
97      edje_object_signal_emit(wd->radio, "elm,state,text,visible", "elm");
98    else
99      edje_object_signal_emit(wd->radio, "elm,state,text,hidden", "elm");
100    edje_object_part_text_set(wd->radio, "elm.text", wd->label);
101    edje_object_message_signal_process(wd->radio);
102    edje_object_scale_set(wd->radio, elm_widget_scale_get(obj) * _elm_config->scale);
103    _sizing_eval(obj);
104 }
105
106 static void
107 _disable_hook(Evas_Object *obj)
108 {
109    Widget_Data *wd = elm_widget_data_get(obj);
110    if (!wd) return;
111    if (elm_widget_disabled_get(obj))
112      {
113         edje_object_signal_emit(wd->radio, "elm,state,disabled", "elm");
114         if (wd->state) _state_set(obj, 0);
115      }
116    else
117      edje_object_signal_emit(wd->radio, "elm,state,enabled", "elm");
118 }
119
120 static void
121 _sizing_eval(Evas_Object *obj)
122 {
123    Widget_Data *wd = elm_widget_data_get(obj);
124    Evas_Coord minw = -1, minh = -1;
125    if (!wd) return;
126    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
127    edje_object_size_min_restricted_calc(wd->radio, &minw, &minh, minw, minh);
128    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
129    evas_object_size_hint_min_set(obj, minw, minh);
130    evas_object_size_hint_max_set(obj, -1, -1);
131 }
132
133 static void
134 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
135 {
136    Widget_Data *wd = elm_widget_data_get(data);
137    if (!wd) return;
138    if (obj != wd->icon) return;
139    _sizing_eval(data);
140 }
141
142 static void
143 _sub_del(void *data __UNUSED__, Evas_Object *obj, void *event_info)
144 {
145    Widget_Data *wd = elm_widget_data_get(obj);
146    Evas_Object *sub = event_info;
147    if (!wd) return;
148    if (sub == wd->icon)
149      {
150         edje_object_signal_emit(wd->radio, "elm,state,icon,hidden", "elm");
151         evas_object_event_callback_del_full
152           (sub, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _changed_size_hints, obj);
153         wd->icon = NULL;
154         _sizing_eval(obj);
155      }
156 }
157
158 static void
159 _state_set(Evas_Object *obj, Eina_Bool state)
160 {
161    Widget_Data *wd = elm_widget_data_get(obj);
162    if (!wd) return;
163    if ((state != wd->state) && (!elm_widget_disabled_get(obj)))
164      {
165         wd->state = state;
166         if (wd->state)
167           edje_object_signal_emit(wd->radio, "elm,state,radio,on", "elm");
168         else
169           edje_object_signal_emit(wd->radio, "elm,state,radio,off", "elm");
170      }
171 }
172
173 static void
174 _state_set_all(Widget_Data *wd)
175 {
176    const Eina_List *l;
177    Evas_Object *child, *selected = NULL;
178    Eina_Bool disabled = EINA_FALSE;
179    EINA_LIST_FOREACH(wd->group->radios, l, child)
180      {
181         Widget_Data *wd2 = elm_widget_data_get(child);
182         if (wd2->state) selected = child;
183         if (wd2->value == wd->group->value)
184           {
185              _state_set(child, 1);
186              if (!wd2->state) disabled = EINA_TRUE;
187           }
188         else _state_set(child, 0);
189      }
190    if (disabled && selected) _state_set(selected, 1);
191 }
192
193 static void
194 _signal_radio_on(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
195 {
196    Widget_Data *wd = elm_widget_data_get(data);
197    if (!wd) return;
198    if (wd->group->value == wd->value) return;
199    wd->group->value = wd->value;
200    if (wd->group->valuep) *(wd->group->valuep) = wd->group->value;
201    _state_set_all(wd);
202    evas_object_smart_callback_call(data, SIG_CHANGED, NULL);
203 }
204
205 /**
206   * Add a new radio to the parent
207   *
208   * @param[in] parent The parent object
209   * @return The new object or NULL if it cannot be created
210   *
211   * @ingroup Radio
212   */
213 EAPI Evas_Object *
214 elm_radio_add(Evas_Object *parent)
215 {
216    Evas_Object *obj;
217    Evas *e;
218    Widget_Data *wd;
219
220    wd = ELM_NEW(Widget_Data);
221    e = evas_object_evas_get(parent);
222    obj = elm_widget_add(e);
223    ELM_SET_WIDTYPE(widtype, "radio");
224    elm_widget_type_set(obj, "radio");
225    elm_widget_sub_object_add(parent, obj);
226    elm_widget_data_set(obj, wd);
227    elm_widget_del_hook_set(obj, _del_hook);
228    elm_widget_theme_hook_set(obj, _theme_hook);
229    elm_widget_disable_hook_set(obj, _disable_hook);
230
231    wd->radio = edje_object_add(e);
232    _elm_theme_object_set(obj, wd->radio, "radio", "base", "default");
233    edje_object_signal_callback_add(wd->radio, "elm,action,radio,on", "", _signal_radio_on, obj);
234    edje_object_signal_callback_add(wd->radio, "elm,action,radio,toggle", "", _signal_radio_on, obj);
235    elm_widget_resize_object_set(obj, wd->radio);
236
237    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
238
239    wd->group = calloc(1, sizeof(Group));
240    wd->group->radios = eina_list_append(wd->group->radios, obj);
241    wd->state = 0;
242
243    _sizing_eval(obj);
244
245    // TODO: convert Elementary to subclassing of Evas_Smart_Class
246    // TODO: and save some bytes, making descriptions per-class and not instance!
247    evas_object_smart_callbacks_descriptions_set(obj, _signals);
248    return obj;
249 }
250
251 /**
252  * Set the text label of the radio object
253  *
254  * @param[in] obj The radio object
255  * @param[in] label The text label string in UTF-8
256  *
257  * @ingroup Radio
258  */
259 EAPI void
260 elm_radio_label_set(Evas_Object *obj, const char *label)
261 {
262    ELM_CHECK_WIDTYPE(obj, widtype);
263    Widget_Data *wd = elm_widget_data_get(obj);
264    if (!wd) return;
265    eina_stringshare_replace(&wd->label, label);
266    if (label)
267      {
268         edje_object_signal_emit(wd->radio, "elm,state,text,visible", "elm");
269         edje_object_message_signal_process(wd->radio);
270      }
271    else
272      {
273         edje_object_signal_emit(wd->radio, "elm,state,text,hidden", "elm");
274         edje_object_message_signal_process(wd->radio);
275      }
276    edje_object_part_text_set(wd->radio, "elm.text", label);
277    _sizing_eval(obj);
278 }
279
280 /**
281  * Get the text label of the radio object
282  *
283  * @param[in] obj The radio object
284  * @return The text label string in UTF-8
285  *
286  * @ingroup Radio
287  */
288 EAPI const char *
289 elm_radio_label_get(const Evas_Object *obj)
290 {
291    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
292    Widget_Data *wd = elm_widget_data_get(obj);
293    if (!wd) return NULL;
294    return wd->label;
295 }
296
297 /**
298  * Set the icon object of the radio object
299  *
300  * Once the icon object is set, a previously set one will be deleted.
301  *
302  * @param[in] obj The radio object
303  * @param[in] icon The icon object
304  *
305  * @ingroup Radio
306  */
307 EAPI void
308 elm_radio_icon_set(Evas_Object *obj, Evas_Object *icon)
309 {
310    ELM_CHECK_WIDTYPE(obj, widtype);
311    Widget_Data *wd = elm_widget_data_get(obj);
312    if (!wd) return;
313    if (wd->icon == icon) return;
314    if (wd->icon) evas_object_del(wd->icon);
315    wd->icon = icon;
316    if (icon)
317      {
318         elm_widget_sub_object_add(obj, icon);
319         evas_object_event_callback_add(icon, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
320                                        _changed_size_hints, obj);
321         edje_object_part_swallow(wd->radio, "elm.swallow.content", icon);
322         edje_object_signal_emit(wd->radio, "elm,state,icon,visible", "elm");
323         edje_object_message_signal_process(wd->radio);
324      }
325    _sizing_eval(obj);
326 }
327
328 /**
329  * Get the icon object of the radio object
330  *
331  * @param[in] obj The radio object
332  * @return The icon object
333  *
334  * @ingroup Radio
335  */
336 EAPI Evas_Object *
337 elm_radio_icon_get(const Evas_Object *obj)
338 {
339    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
340    Widget_Data *wd = elm_widget_data_get(obj);
341    if (!wd) return NULL;
342    return wd->icon;
343 }
344
345 /**
346  * Add this radio to a group of other radio objects
347  *
348  * Radio objects work in groups. Each member should have a different integer
349  * value assigned. In order ro have them work as a group, they need to know
350  * about eacthother. This adds the given radio object to the group of which
351  * the group object indicated is a member.
352  *
353  * @param[in] obj The radio object
354  * @param[in] group The object whose group the object is to join
355  *
356  * @ingroup Radio
357  */
358 EAPI void
359 elm_radio_group_add(Evas_Object *obj, Evas_Object *group)
360 {
361    ELM_CHECK_WIDTYPE(obj, widtype);
362    Widget_Data *wd = elm_widget_data_get(obj);
363    Widget_Data *wd2 = elm_widget_data_get(group);
364    if (!wd) return;
365    if (!wd2)
366      {
367         if (eina_list_count(wd->group->radios) == 1)
368           return;
369         wd->group->radios = eina_list_remove(wd->group->radios, obj);
370         wd->group = calloc(1, sizeof(Group));
371         wd->group->radios = eina_list_append(wd->group->radios, obj);
372      }
373    else if (wd->group == wd2->group) return;
374    else
375      {
376         wd->group->radios = eina_list_remove(wd->group->radios, obj);
377         if (!wd->group->radios) free(wd->group);
378         wd->group = wd2->group;
379         wd->group->radios = eina_list_append(wd->group->radios, obj);
380      }
381    if (wd->value == wd->group->value) _state_set(obj, 1);
382    else _state_set(obj, 0);
383 }
384
385 /**
386  * Set the integer value that this radio object represents
387  *
388  * This sets the value of the radio.
389  *
390  * @param[in] obj The radio object
391  * @param[in] value The value to use if this radio object is selected
392  *
393  * @ingroup Radio
394  */
395 EAPI void
396 elm_radio_state_value_set(Evas_Object *obj, int value)
397 {
398    ELM_CHECK_WIDTYPE(obj, widtype);
399    Widget_Data *wd = elm_widget_data_get(obj);
400    if (!wd) return;
401    wd->value = value;
402    if (wd->value == wd->group->value) _state_set(obj, 1);
403    else _state_set(obj, 0);
404 }
405
406 /**
407  * Set the value of the radio.
408  *
409  * This sets the value of the radio group and will also set the value if
410  * pointed to, to the value supplied, but will not call any callbacks.
411  *
412  * @param[in] obj The radio object
413  * @param[in] value The value to use for the group
414  *
415  * @ingroup Radio
416  */
417 EAPI void
418 elm_radio_value_set(Evas_Object *obj, int value)
419 {
420    ELM_CHECK_WIDTYPE(obj, widtype);
421    Widget_Data *wd = elm_widget_data_get(obj);
422    if (!wd) return;
423    if (value == wd->group->value) return;
424    wd->group->value = value;
425    if (wd->group->valuep) *(wd->group->valuep) = wd->group->value;
426    _state_set_all(wd);
427 }
428
429 /**
430  * Get the state of the radio object
431  *
432  * @param[in] obj The radio object
433  * @return The integer state
434  *
435  * @ingroup Radio
436  */
437 EAPI int
438 elm_radio_value_get(const Evas_Object *obj)
439 {
440    ELM_CHECK_WIDTYPE(obj, widtype) 0;
441    Widget_Data *wd = elm_widget_data_get(obj);
442    if (!wd) return 0;
443    return wd->group->value;
444 }
445
446 /**
447  * Set a convenience pointer to a integer to change
448  *
449  * This sets a pointer to a integer, that, in addition to the radio objects
450  * state will also be modified directly. To stop setting the object pointed
451  * to simply use NULL as the valuep parameter. If valuep is not NULL, then
452  * when this is called, the radio objects state will also be modified to
453  * reflect the value of the integer valuep points to, just like calling
454  * elm_radio_value_set().
455  *
456  * @param[in] obj The radio object
457  * @param[in] valuep Pointer to the integer to modify
458  *
459  * @ingroup Radio
460  */
461 EAPI void
462 elm_radio_value_pointer_set(Evas_Object *obj, int *valuep)
463 {
464    ELM_CHECK_WIDTYPE(obj, widtype);
465    Widget_Data *wd = elm_widget_data_get(obj);
466    if (!wd) return;
467    if (valuep)
468      {
469         wd->group->valuep = valuep;
470         if (*(wd->group->valuep) != wd->group->value)
471           {
472              wd->group->value = *(wd->group->valuep);
473              _state_set_all(wd);
474           }
475      }
476    else
477      {
478         wd->group->valuep = NULL;
479      }
480 }