elementry / entry, menu, notify, list, gengrid, actionslider, image, icon, anchorview...
[framework/uifw/elementary.git] / src / lib / elm_button.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Button Button
6  *
7  * This is a push-button. Press it and run some function. It can contain
8  * a simple label and icon object.
9  *
10  * Signals that you can add callbacks for are:
11  *
12  * "clicked" - the user clicked the button
13  * "repeated" - the user pressed the button without releasing it
14  * "unpressed" - when the button is unpressed (released)
15  */
16
17 typedef struct _Widget_Data Widget_Data;
18
19 struct _Widget_Data
20 {
21    Evas_Object *btn, *icon;
22    const char *label;
23    Eina_Bool autorepeat;
24    Eina_Bool repeating;
25    double ar_threshold;
26    double ar_interval;
27    Ecore_Timer *timer;
28 };
29
30 static const char *widtype = NULL;
31 static void _del_hook(Evas_Object *obj);
32 static void _theme_hook(Evas_Object *obj);
33 static void _disable_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_clicked(void *data, Evas_Object *obj, const char *emission, const char *source);
38 static void _signal_pressed(void *data, Evas_Object *obj, const char *emission, const char *source);
39 static void _signal_unpressed(void *data, Evas_Object *obj, const char *emission, const char *source);
40 static void _on_focus_hook(void *data, Evas_Object *obj);
41 static void _activate(Evas_Object *obj);
42 static void _activate_hook(Evas_Object *obj);
43 static Eina_Bool _event_hook(Evas_Object *obj, Evas_Object *src,
44                              Evas_Callback_Type type, void *event_info);
45
46 static const char SIG_CLICKED[] = "clicked";
47 static const char SIG_REPEATED[] = "repeated";
48 static const char SIG_UNPRESSED[] = "unpressed";
49 static const Evas_Smart_Cb_Description _signals[] = {
50        {SIG_CLICKED, ""},
51        {SIG_REPEATED, ""},
52        {SIG_UNPRESSED, ""},
53        {NULL, NULL}
54 };
55
56 static Eina_Bool
57 _event_hook(Evas_Object *obj, Evas_Object *src __UNUSED__, Evas_Callback_Type type, void *event_info)
58 {
59    if (type != EVAS_CALLBACK_KEY_DOWN) return EINA_FALSE;
60    Evas_Event_Key_Down *ev = event_info;
61    Widget_Data *wd = elm_widget_data_get(obj);
62    if (!wd) return EINA_FALSE;
63    if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
64    if (elm_widget_disabled_get(obj)) return EINA_FALSE;
65    if ((strcmp(ev->keyname, "Return")) &&
66        (strcmp(ev->keyname, "KP_Enter")) &&
67        (strcmp(ev->keyname, "space")))
68      return EINA_FALSE;
69    _activate(obj);
70    ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
71    edje_object_signal_emit(wd->btn, "elm,anim,activate", "elm");
72    return EINA_TRUE;
73 }
74
75 static void
76 _del_hook(Evas_Object *obj)
77 {
78    Widget_Data *wd = elm_widget_data_get(obj);
79    if (!wd) return;
80    if (wd->label) eina_stringshare_del(wd->label);
81    free(wd);
82 }
83
84 static void
85 _on_focus_hook(void *data __UNUSED__, Evas_Object *obj)
86 {
87    Widget_Data *wd = elm_widget_data_get(obj);
88    if (!wd) return;
89    if (elm_widget_focus_get(obj))
90      {
91         edje_object_signal_emit(wd->btn, "elm,action,focus", "elm");
92         evas_object_focus_set(wd->btn, EINA_TRUE);
93      }
94    else
95      {
96         edje_object_signal_emit(wd->btn, "elm,action,unfocus", "elm");
97         evas_object_focus_set(wd->btn, EINA_FALSE);
98      }
99 }
100
101 static void
102 _mirrored_set(Evas_Object *obj, Eina_Bool rtl)
103 {
104    Widget_Data *wd = elm_widget_data_get(obj);
105    if (!wd) return;
106    edje_object_mirrored_set(wd->btn, rtl);
107 }
108
109 static void
110 _theme_hook(Evas_Object *obj)
111 {
112    Widget_Data *wd = elm_widget_data_get(obj);
113    const char *str;
114    if (!wd) return;
115    _elm_widget_mirrored_reload(obj);
116    _mirrored_set(obj, elm_widget_mirrored_get(obj));
117    _elm_theme_object_set(obj, wd->btn, "button", "base", elm_widget_style_get(obj));
118    if (wd->icon)
119      edje_object_part_swallow(wd->btn, "elm.swallow.content", wd->icon);
120    if (wd->label)
121      edje_object_signal_emit(wd->btn, "elm,state,text,visible", "elm");
122    else
123      edje_object_signal_emit(wd->btn, "elm,state,text,hidden", "elm");
124    if (wd->icon)
125      edje_object_signal_emit(wd->btn, "elm,state,icon,visible", "elm");
126    else
127      edje_object_signal_emit(wd->btn, "elm,state,icon,hidden", "elm");
128    edje_object_part_text_set(wd->btn, "elm.text", wd->label);
129    if (elm_object_disabled_get(obj))
130      edje_object_signal_emit(wd->btn, "elm,state,disabled", "elm");
131    edje_object_message_signal_process(wd->btn);
132    edje_object_scale_set(wd->btn, elm_widget_scale_get(obj) * _elm_config->scale);
133    str = edje_object_data_get(wd->btn, "focus_highlight");
134    if ((str) && (!strcmp(str, "on")))
135      elm_widget_highlight_in_theme_set(obj, EINA_TRUE);
136    else
137      elm_widget_highlight_in_theme_set(obj, EINA_FALSE);
138    _sizing_eval(obj);
139 }
140
141 static void
142 _disable_hook(Evas_Object *obj)
143 {
144    Widget_Data *wd = elm_widget_data_get(obj);
145    if (!wd) return;
146    if (elm_widget_disabled_get(obj))
147      edje_object_signal_emit(wd->btn, "elm,state,disabled", "elm");
148    else
149      edje_object_signal_emit(wd->btn, "elm,state,enabled", "elm");
150 }
151
152 static void
153 _signal_emit_hook(Evas_Object *obj, const char *emission, const char *source)
154 {
155    Widget_Data *wd = elm_widget_data_get(obj);
156    if (!wd) return;
157    edje_object_signal_emit(wd->btn, emission, source);
158 }
159
160 static void
161 _signal_callback_add_hook(Evas_Object *obj, const char *emission, const char *source, Edje_Signal_Cb func_cb, void *data)
162 {
163    Widget_Data *wd = elm_widget_data_get(obj);
164    if (!wd) return;
165    edje_object_signal_callback_add(wd->btn, emission, source, func_cb, data);
166 }
167
168 static void
169 _signal_callback_del_hook(Evas_Object *obj, const char *emission, const char *source, Edje_Signal_Cb func_cb, void *data)
170 {
171    Widget_Data *wd = elm_widget_data_get(obj);
172    edje_object_signal_callback_del_full(wd->btn, emission, source, func_cb,
173                                         data);
174 }
175
176 static void
177 _sizing_eval(Evas_Object *obj)
178 {
179    Widget_Data *wd = elm_widget_data_get(obj);
180    Evas_Coord minw = -1, minh = -1;
181
182    if (!wd) return;
183    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
184    edje_object_size_min_restricted_calc(wd->btn, &minw, &minh, minw, minh);
185    elm_coords_finger_size_adjust(1, &minw, 1, &minh);
186    evas_object_size_hint_min_set(obj, minw, minh);
187 }
188
189 static void
190 _changed_size_hints(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__)
191 {
192    Widget_Data *wd = elm_widget_data_get(data);
193    if (!wd) return;
194    if (obj != wd->icon) return;
195    _sizing_eval(data);
196 }
197
198 static void
199 _sub_del(void *data __UNUSED__, Evas_Object *obj, void *event_info)
200 {
201    Widget_Data *wd = elm_widget_data_get(obj);
202    Evas_Object *sub = event_info;
203    if (!wd) return;
204    if (sub == wd->icon)
205      {
206         edje_object_signal_emit(wd->btn, "elm,state,icon,hidden", "elm");
207         evas_object_event_callback_del_full(sub, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
208                                             _changed_size_hints, obj);
209         wd->icon = NULL;
210         edje_object_message_signal_process(wd->btn);
211         _sizing_eval(obj);
212      }
213 }
214
215 static void
216 _activate(Evas_Object *obj)
217 {
218    Widget_Data *wd = elm_widget_data_get(obj);
219    if (!wd) return;
220    if (wd->timer)
221      {
222         ecore_timer_del(wd->timer);
223         wd->timer = NULL;
224      }
225    wd->repeating = EINA_FALSE;
226    evas_object_smart_callback_call(obj, SIG_CLICKED, NULL);
227 }
228
229 static void
230 _activate_hook(Evas_Object *obj)
231 {
232    _activate(obj);
233 }
234
235 static void
236 _signal_clicked(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
237 {
238    _activate(data);
239 }
240
241 static Eina_Bool
242 _autorepeat_send(void *data)
243 {
244    Widget_Data *wd = elm_widget_data_get(data);
245    if (!wd) return ECORE_CALLBACK_CANCEL;
246
247    evas_object_smart_callback_call(data, SIG_REPEATED, NULL);
248    if (!wd->repeating)
249      {
250         wd->timer = NULL;
251         return ECORE_CALLBACK_CANCEL;
252      }
253
254    return ECORE_CALLBACK_RENEW;
255 }
256
257 static Eina_Bool
258 _autorepeat_initial_send(void *data)
259 {
260    Widget_Data *wd = elm_widget_data_get(data);
261    if (!wd) return ECORE_CALLBACK_CANCEL;
262
263    if (wd->timer) ecore_timer_del(wd->timer);
264    wd->repeating = EINA_TRUE;
265    _autorepeat_send(data);
266    wd->timer = ecore_timer_add(wd->ar_interval, _autorepeat_send, data);
267
268    return ECORE_CALLBACK_CANCEL;
269 }
270
271 static void
272 _signal_pressed(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
273 {
274    Widget_Data *wd = elm_widget_data_get(data);
275    if (!wd) return;
276
277    if ((wd->autorepeat) && (!wd->repeating))
278      {
279         if (wd->ar_threshold <= 0.0)
280           _autorepeat_initial_send(data); /* call immediately */
281         else
282           wd->timer = ecore_timer_add(wd->ar_threshold, _autorepeat_initial_send, data);
283      }
284 }
285
286 static void
287 _signal_unpressed(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
288 {
289    Widget_Data *wd = elm_widget_data_get(data);
290    if (!wd) return;
291
292    if (wd->timer)
293      {
294         ecore_timer_del(wd->timer);
295         wd->timer = NULL;
296      }
297    wd->repeating = EINA_FALSE;
298    evas_object_smart_callback_call(data, SIG_UNPRESSED, NULL);
299 }
300
301 /**
302  * Add a new button to the parent
303  * @param parent The parent object
304  * @return The new object or NULL if it cannot be created
305  *
306  * @ingroup Button
307  */
308 EAPI Evas_Object *
309 elm_button_add(Evas_Object *parent)
310 {
311    Evas_Object *obj;
312    Evas *e;
313    Widget_Data *wd;
314
315    ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL);
316
317    ELM_SET_WIDTYPE(widtype, "button");
318    elm_widget_type_set(obj, "button");
319    elm_widget_sub_object_add(parent, obj);
320    elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL);
321    elm_widget_data_set(obj, wd);
322    elm_widget_del_hook_set(obj, _del_hook);
323    elm_widget_theme_hook_set(obj, _theme_hook);
324    elm_widget_disable_hook_set(obj, _disable_hook);
325    elm_widget_can_focus_set(obj, EINA_TRUE);
326    elm_widget_activate_hook_set(obj, _activate_hook);
327    elm_widget_event_hook_set(obj, _event_hook);
328    elm_widget_signal_emit_hook_set(obj, _signal_emit_hook);
329    elm_widget_signal_callback_add_hook_set(obj, _signal_callback_add_hook);
330    elm_widget_signal_callback_del_hook_set(obj, _signal_callback_del_hook);
331
332    wd->btn = edje_object_add(e);
333    _elm_theme_object_set(obj, wd->btn, "button", "base", "default");
334    edje_object_signal_callback_add(wd->btn, "elm,action,click", "",
335                                    _signal_clicked, obj);
336    edje_object_signal_callback_add(wd->btn, "elm,action,press", "",
337                                    _signal_pressed, obj);
338    edje_object_signal_callback_add(wd->btn, "elm,action,unpress", "",
339                                    _signal_unpressed, obj);
340    elm_widget_resize_object_set(obj, wd->btn);
341
342    evas_object_smart_callback_add(obj, "sub-object-del", _sub_del, obj);
343
344    _theme_hook(obj);
345
346    // TODO: convert Elementary to subclassing of Evas_Smart_Class
347    // TODO: and save some bytes, making descriptions per-class and not instance!
348    evas_object_smart_callbacks_descriptions_set(obj, _signals);
349    return obj;
350 }
351
352 /**
353  * Set the label used in the button
354  *
355  * @param obj The button object
356  * @param label The text will be written on the button
357  *
358  * @ingroup Button
359  */
360 EAPI void
361 elm_button_label_set(Evas_Object *obj, const char *label)
362 {
363    ELM_CHECK_WIDTYPE(obj, widtype);
364    Widget_Data *wd = elm_widget_data_get(obj);
365    if (!wd) return;
366    eina_stringshare_replace(&wd->label, label);
367    if (label)
368      edje_object_signal_emit(wd->btn, "elm,state,text,visible", "elm");
369    else
370      edje_object_signal_emit(wd->btn, "elm,state,text,hidden", "elm");
371    edje_object_message_signal_process(wd->btn);
372    edje_object_part_text_set(wd->btn, "elm.text", label);
373    _sizing_eval(obj);
374 }
375
376 EAPI const char *
377 elm_button_label_get(const Evas_Object *obj)
378 {
379    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
380    Widget_Data *wd = elm_widget_data_get(obj);
381    if (!wd) return NULL;
382    return wd->label;
383 }
384
385 /**
386  * Set the icon used for the button
387  *
388  * Once the icon object is set, a previously set one will be deleted
389  * If you want to keep that old content object, use the
390  * elm_button_icon_unset() function.
391  *
392  * @param obj The button object
393  * @param icon The icon object for the button
394  *
395  * @ingroup Button
396  */
397 EAPI void
398 elm_button_icon_set(Evas_Object *obj, Evas_Object *icon)
399 {
400    ELM_CHECK_WIDTYPE(obj, widtype);
401    Widget_Data *wd = elm_widget_data_get(obj);
402    if (!wd) return;
403    if (wd->icon == icon) return;
404    if (wd->icon) evas_object_del(wd->icon);
405    wd->icon = icon;
406    if (icon)
407      {
408         elm_widget_sub_object_add(obj, icon);
409         evas_object_event_callback_add(icon, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
410                                        _changed_size_hints, obj);
411         edje_object_part_swallow(wd->btn, "elm.swallow.content", icon);
412         edje_object_signal_emit(wd->btn, "elm,state,icon,visible", "elm");
413         edje_object_message_signal_process(wd->btn);
414      }
415    _sizing_eval(obj);
416 }
417
418 /**
419  * Get the icon used for the button
420  *
421  * Return the icon object which is set for this widget.
422  *
423  * @param obj The button object
424  * @return The icon object that is being used
425  *
426  * @ingroup Button
427  */
428 EAPI Evas_Object *
429 elm_button_icon_get(const Evas_Object *obj)
430 {
431    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
432    Widget_Data *wd = elm_widget_data_get(obj);
433    if (!wd) return NULL;
434    return wd->icon;
435 }
436
437 /**
438  * Unset the icon used for the button
439  *
440  * Unparent and return the icon object which was set for this widget.
441  *
442  * @param obj The button object
443  * @return The icon object that was being used
444  *
445  * @ingroup Button
446  */
447 EAPI Evas_Object *
448 elm_button_icon_unset(Evas_Object *obj)
449 {
450    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
451    Widget_Data *wd = elm_widget_data_get(obj);
452    if (!wd) return NULL;
453    if (!wd->icon) return NULL;
454    Evas_Object *icon = wd->icon;
455    elm_widget_sub_object_del(obj, wd->icon);
456    edje_object_part_unswallow(wd->btn, wd->icon);
457    wd->icon = NULL;
458    return icon;
459 }
460
461 /**
462  * Turn on/off the autorepeat event generated when the user keeps pressing on the button
463  *
464  * @param obj The button object
465  * @param on  A bool to turn on/off the event
466  *
467  * @ingroup Button
468  */
469 EAPI void
470 elm_button_autorepeat_set(Evas_Object *obj, Eina_Bool on)
471 {
472    ELM_CHECK_WIDTYPE(obj, widtype);
473    Widget_Data *wd = elm_widget_data_get(obj);
474    if (!wd) return;
475    if (wd->timer)
476      {
477         ecore_timer_del(wd->timer);
478         wd->timer = NULL;
479      }
480    wd->autorepeat = on;
481    wd->repeating = EINA_FALSE;
482 }
483
484 /**
485  * Get if autorepeat event is on
486  *
487  * @param obj The button object
488  * @return If autorepeat is on
489  *
490  * @ingroup Button
491  */
492 EAPI Eina_Bool
493 elm_button_autorepeat_get(const Evas_Object *obj)
494 {
495    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
496    Widget_Data *wd = elm_widget_data_get(obj);
497    if (!wd) return EINA_FALSE;
498    return wd->autorepeat;
499 }
500
501 /**
502  * Set the initial timeout before the autorepeat event is generated
503  *
504  * @param obj The button object
505  * @param t   Timeout
506  *
507  * @ingroup Button
508  */
509 EAPI void
510 elm_button_autorepeat_initial_timeout_set(Evas_Object *obj, double t)
511 {
512    ELM_CHECK_WIDTYPE(obj, widtype);
513    Widget_Data *wd = elm_widget_data_get(obj);
514    if (!wd) return;
515    if (wd->ar_threshold == t) return;
516    if (wd->timer)
517      {
518         ecore_timer_del(wd->timer);
519         wd->timer = NULL;
520      }
521    wd->ar_threshold = t;
522 }
523
524 /**
525  * Get the initial timeout before the autorepeat event is generated
526  *
527  * @param obj The button object
528  * @return Timeout
529  *
530  * @ingroup Button
531  */
532 EAPI double
533 elm_button_autorepeat_initial_timeout_get(const Evas_Object *obj)
534 {
535    ELM_CHECK_WIDTYPE(obj, widtype) 0.0;
536    Widget_Data *wd = elm_widget_data_get(obj);
537    if (!wd) return 0.0;
538    return wd->ar_threshold;
539 }
540
541 /**
542  * Set the interval between each generated autorepeat event
543  *
544  * @param obj The button object
545  * @param t   Interval
546  *
547  * @ingroup Button
548  */
549 EAPI void
550 elm_button_autorepeat_gap_timeout_set(Evas_Object *obj, double t)
551 {
552    ELM_CHECK_WIDTYPE(obj, widtype);
553    Widget_Data *wd = elm_widget_data_get(obj);
554    if (!wd) return;
555    if (wd->ar_interval == t) return;
556
557    wd->ar_interval = t;
558    if ((wd->repeating) && (wd->timer)) ecore_timer_interval_set(wd->timer, t);
559 }
560
561 /**
562  * Get the interval between each generated autorepeat event
563  *
564  * @param obj The button object
565  * @return Interval
566  *
567  * @ingroup Button
568  */
569 EAPI double
570 elm_button_autorepeat_gap_timeout_get(const Evas_Object *obj)
571 {
572    ELM_CHECK_WIDTYPE(obj, widtype) 0.0;
573    Widget_Data *wd = elm_widget_data_get(obj);
574    if (!wd) return 0.0;
575    return wd->ar_interval;
576 }