77d444107b37d92358d6e217c06744d2f5fa2cd5
[framework/uifw/elementary.git] / src / lib / elm_segment_control.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 typedef struct _Widget_Data Widget_Data;
5
6 struct _Widget_Data
7 {
8    Evas_Object *obj;
9    Evas_Object *base;
10    Eina_List *seg_items;
11    int item_count;
12    Elm_Segment_Item *selected_item;
13    int item_width;
14 };
15
16 struct _Elm_Segment_Item
17 {
18    Elm_Widget_Item base;
19    Evas_Object *icon;
20    const char *label;
21    int seg_index;
22 };
23
24 static const char *widtype = NULL;
25 static void _sizing_eval(Evas_Object *obj);
26 static void _del_hook(Evas_Object *obj);
27 static void _theme_hook(Evas_Object *obj);
28 static void _disable_hook(Evas_Object *obj);
29 static void _item_free(Elm_Segment_Item *it);
30 static void _segment_off(Elm_Segment_Item *it);
31 static void _segment_on(Elm_Segment_Item *it);
32 static void _position_items(Widget_Data *wd);
33 static void _on_move_resize(void *data, Evas *e __UNUSED__, Evas_Object *obj
34                             __UNUSED__, void *event_info __UNUSED__);
35 static void _mouse_up(void *data, Evas *e __UNUSED__, Evas_Object *obj
36                       __UNUSED__, void *event_info __UNUSED__);
37 static void _mouse_down(void *data, Evas *e __UNUSED__, Evas_Object *obj
38                         __UNUSED__, void *event_info __UNUSED__);
39 static void _swallow_item_objects(Elm_Segment_Item *it);
40 static void _update_list(Widget_Data *wd);
41 static Elm_Segment_Item * _item_find(const Evas_Object *obj, int index);
42 static Elm_Segment_Item* _item_new(Evas_Object *obj, Evas_Object *icon,
43                                    const char *label);
44
45 static const char SIG_CHANGED[] = "changed";
46
47 static const Evas_Smart_Cb_Description _signals[] = {
48    {SIG_CHANGED, ""},
49    {NULL, NULL}
50 };
51
52 static void
53 _sizing_eval(Evas_Object *obj)
54 {
55    Widget_Data *wd;
56    Evas_Coord minw = -1, minh = -1;
57    Evas_Coord w, h;
58
59    wd = elm_widget_data_get(obj);
60    if (!wd) return;
61
62    elm_coords_finger_size_adjust(wd->item_count, &minw, 1, &minh);
63    edje_object_size_min_restricted_calc(wd->base, &minw, &minh, minw, minh);
64    elm_coords_finger_size_adjust(wd->item_count, &minw, 1, &minh);
65
66    evas_object_size_hint_min_get(obj, &w, &h);
67    if (w > minw) minw = w;
68    if (h > minh) minh = h;
69    evas_object_size_hint_min_set(obj, minw, minh);
70 }
71
72 static void
73 _del_hook(Evas_Object *obj)
74 {
75    Elm_Segment_Item *it;
76    Widget_Data *wd;
77
78    wd = elm_widget_data_get(obj);
79    if (!wd) return;
80
81    EINA_LIST_FREE(wd->seg_items, it) _item_free(it);
82
83    free(wd);
84 }
85
86 static void
87 _theme_hook(Evas_Object *obj)
88 {
89    Eina_List *l;
90    Eina_Bool rtl;
91    Elm_Segment_Item *it;
92    Widget_Data *wd;
93
94    wd = elm_widget_data_get(obj);
95    if (!wd) return;
96
97    _elm_widget_mirrored_reload(obj);
98    rtl = elm_widget_mirrored_get(obj);
99    edje_object_mirrored_set(wd->base, rtl);
100
101    _elm_theme_object_set(obj, wd->base, "segment_control", "base",
102                          elm_widget_style_get(obj));
103    edje_object_scale_set(wd->base, elm_widget_scale_get(wd->base)
104                          *_elm_config->scale);
105
106    EINA_LIST_FOREACH(wd->seg_items, l, it)
107      {
108         _elm_theme_object_set(obj, it->base.view, "segment_control",
109                               "item", elm_widget_style_get(obj));
110         edje_object_scale_set(it->base.view, elm_widget_scale_get(it->base.view)
111                               *_elm_config->scale);
112         edje_object_mirrored_set(it->base.view, rtl);
113      }
114
115    _update_list(wd);
116 }
117
118 static void
119 _disable_hook(Evas_Object *obj)
120 {
121    Widget_Data *wd;
122
123    wd = elm_widget_data_get(obj);
124    if (!wd) return;
125    _update_list(wd);
126 }
127
128 // TODO:  Elm_widget elm_widget_focus_list_next_get  supports only Elm_widget list,
129 // Not the Elm_Widget_item. Focus switching with in widget not supported until
130 // it is supported in elm_widget
131 #if 0
132 static void *
133 _elm_list_data_get(const Eina_List *list)
134 {
135    Elm_Segment_Item *it = eina_list_data_get(list);
136
137    if (it) return NULL;
138
139    edje_object_signal_emit(it->base.view, "elm,state,segment,selected", "elm");
140    return it->base.view;
141 }
142
143 static Eina_Bool
144 _focus_next_hook(const Evas_Object *obj, Elm_Focus_Direction dir,
145                  Evas_Object **next)
146 {
147    static int count=0;
148    Widget_Data *;
149    const Eina_List *items;
150    void *(*list_data_get) (const Eina_List *list);
151
152    wd = elm_widget_data_get(obj);
153    if ((!wd)) return EINA_FALSE;
154
155    /* Focus chain */
156    /* TODO: Change this to use other chain */
157    if ((items = elm_widget_focus_custom_chain_get(obj)))
158      list_data_get = eina_list_data_get;
159    else
160      {
161         items = wd->seg_items;
162         list_data_get = _elm_list_data_get;
163         if (!items) return EINA_FALSE;
164      }
165    return elm_widget_focus_list_next_get(obj, items, list_data_get, dir, next);
166 }
167 #endif
168
169 static void
170 _item_free(Elm_Segment_Item *it)
171 {
172    Widget_Data *wd;
173
174    if (!it) return;
175
176    wd = elm_widget_item_data_get(it);
177    if (!wd) return;
178
179    if (wd->selected_item == it) wd->selected_item = NULL;
180    if (wd->seg_items) wd->seg_items = eina_list_remove(wd->seg_items, it);
181
182    elm_widget_item_pre_notify_del(it);
183
184    if (it->icon) evas_object_del(it->icon);
185    if (it->label) eina_stringshare_del(it->label);
186
187    elm_widget_item_del(it);
188 }
189
190 static void
191 _segment_off(Elm_Segment_Item *it)
192 {
193    Widget_Data *wd;
194
195    if (!it) return;
196
197    wd = elm_widget_item_data_get(it);
198    if (!wd) return;
199
200    edje_object_signal_emit(it->base.view, "elm,state,segment,normal", "elm");
201
202    if (wd->selected_item == it) wd->selected_item = NULL;
203 }
204
205 static void
206 _segment_on(Elm_Segment_Item *it)
207 {
208    Widget_Data *wd;
209
210    if (!it) return;
211
212    wd = elm_widget_item_data_get(it);
213    if (!wd) return;
214    if (it == wd->selected_item) return;
215
216    if (wd->selected_item) _segment_off(wd->selected_item);
217
218    edje_object_signal_emit(it->base.view, "elm,state,segment,selected", "elm");
219
220    wd->selected_item = it;
221    evas_object_smart_callback_call(wd->obj, SIG_CHANGED, (void *)(unsigned long)it->seg_index);
222 }
223
224 static void
225 _position_items(Widget_Data *wd)
226 {
227    Eina_List *l;
228    Elm_Segment_Item *it;
229    Eina_Bool rtl;
230    int bx, by, bw, bh, pos;
231
232    wd->item_count = eina_list_count(wd->seg_items);
233    if (wd->item_count <= 0) return;
234
235    evas_object_geometry_get(wd->base, &bx, &by, &bw, &bh);
236    wd->item_width = bw / wd->item_count;
237    rtl = elm_widget_mirrored_get(wd->obj);
238
239    if (rtl)
240      pos = bx + bw - wd->item_width;
241    else
242      pos = bx;
243
244    EINA_LIST_FOREACH(wd->seg_items, l, it)
245      {
246         evas_object_move(it->base.view, pos, by);
247         evas_object_resize(it->base.view, wd->item_width, bh);
248         if (rtl)
249           pos -= wd->item_width;
250         else
251           pos += wd->item_width;
252      }
253    _sizing_eval(wd->obj);
254 }
255
256 static void
257 _on_move_resize(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
258                 void *event_info __UNUSED__)
259 {
260    Widget_Data *wd;
261
262    wd = elm_widget_data_get(data);
263    if (!wd) return;
264
265    _position_items(wd);
266
267 }
268
269 static void
270 _mouse_up(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
271           void *event_info)
272 {
273    Widget_Data *wd;
274    Elm_Segment_Item *it;
275    Evas_Event_Mouse_Up *ev;
276    Evas_Coord x, y, w, h;
277
278    it = data;
279    if (!it) return;
280
281    wd = elm_widget_item_data_get(it);
282    if (!wd) return;
283
284    if (elm_widget_disabled_get(wd->obj)) return;
285
286    if (it == wd->selected_item) return;
287
288    ev = event_info;
289    evas_object_geometry_get(it->base.view, &x, &y, &w, &h);
290
291    if ((ev->canvas.x >= x) && (ev->output.x <= (x + w)) && (ev->canvas.y >= y)
292        && (ev->canvas.y <= (y + h)))
293      _segment_on(it);
294    else
295      edje_object_signal_emit(it->base.view, "elm,state,segment,normal", "elm");
296 }
297
298 static void
299 _mouse_down(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
300             void *event_info __UNUSED__)
301 {
302    Widget_Data *wd;
303    Elm_Segment_Item *it;
304
305    it = data;
306    if (!it) return;
307
308    wd = elm_widget_item_data_get(it);
309    if (!wd) return;
310
311    if (elm_widget_disabled_get(wd->obj)) return;
312
313    if (it == wd->selected_item) return;
314
315    edje_object_signal_emit(it->base.view, "elm,state,segment,pressed", "elm");
316 }
317
318 static void
319 _swallow_item_objects(Elm_Segment_Item *it)
320 {
321    if (!it) return;
322
323    if (it->icon)
324      {
325         edje_object_part_swallow(it->base.view, "elm.swallow.icon", it->icon);
326         edje_object_signal_emit(it->base.view, "elm,state,icon,visible", "elm");
327      }
328    else
329      edje_object_signal_emit(it->base.view, "elm,state,icon,hidden", "elm");
330
331    if (it->label)
332      {
333         edje_object_part_text_set(it->base.view, "elm.text", it->label);
334         edje_object_signal_emit(it->base.view, "elm,state,text,visible", "elm");
335      }
336    else
337      edje_object_signal_emit(it->base.view, "elm,state,text,hidden", "elm");
338    edje_object_message_signal_process(it->base.view);
339 }
340
341 static void
342 _update_list(Widget_Data *wd)
343 {
344    Eina_List *l;
345    Elm_Segment_Item *it;
346    Eina_Bool rtl;
347    int idx = 0;
348
349    _position_items(wd);
350
351    if (wd->item_count == 1)
352      {
353         it = eina_list_nth(wd->seg_items, 0);
354         it->seg_index = 0;
355
356         //Set the segment type
357         edje_object_signal_emit(it->base.view,
358                                 "elm,type,segment,single", "elm");
359
360         //Set the segment state
361         if (wd->selected_item == it)
362           edje_object_signal_emit(it->base.view,
363                                   "elm,state,segment,selected", "elm");
364         else
365           edje_object_signal_emit(it->base.view,
366                                   "elm,state,segment,normal", "elm");
367
368         if (elm_widget_disabled_get(wd->obj))
369           edje_object_signal_emit(it->base.view, "elm,state,disabled", "elm");
370
371         _swallow_item_objects(it);
372         return;
373      }
374
375    rtl = elm_widget_mirrored_get(wd->obj);
376    EINA_LIST_FOREACH(wd->seg_items, l, it)
377      {
378         it->seg_index = idx;
379
380         //Set the segment type
381         if (idx == 0)
382           {
383              if (rtl)
384                edje_object_signal_emit(it->base.view,
385                                        "elm,type,segment,right", "elm");
386              else
387                edje_object_signal_emit(it->base.view,
388                                        "elm,type,segment,left", "elm");
389           }
390         else if (idx == (wd->item_count - 1))
391           {
392              if (rtl)
393                edje_object_signal_emit(it->base.view,
394                                        "elm,type,segment,left", "elm");
395              else
396                edje_object_signal_emit(it->base.view,
397                                        "elm,type,segment,right", "elm");
398           }
399         else
400           edje_object_signal_emit(it->base.view,
401                                   "elm,type,segment,middle", "elm");
402
403         //Set the segment state
404         if (wd->selected_item == it)
405           edje_object_signal_emit(it->base.view,
406                                   "elm,state,segment,selected", "elm");
407         else
408           edje_object_signal_emit(it->base.view,
409                                   "elm,state,segment,normal", "elm");
410
411         if (elm_widget_disabled_get(wd->obj))
412           edje_object_signal_emit(it->base.view, "elm,state,disabled", "elm");
413
414         _swallow_item_objects(it);
415         idx++;
416      }
417 }
418
419 static Elm_Segment_Item *
420 _item_find(const Evas_Object *obj, int idx)
421 {
422    Widget_Data *wd;
423    Elm_Segment_Item *it;
424
425    wd = elm_widget_data_get(obj);
426    if (!wd) return NULL;
427
428    it = eina_list_nth(wd->seg_items, idx);
429    return it;
430 }
431
432 static Elm_Segment_Item*
433 _item_new(Evas_Object *obj, Evas_Object *icon, const char *label)
434 {
435    Elm_Segment_Item *it;
436    Widget_Data *wd;
437
438    wd = elm_widget_data_get(obj);
439    if (!wd) return NULL;
440
441    it = elm_widget_item_new(obj, Elm_Segment_Item);
442    if (!it) return NULL;
443    elm_widget_item_data_set(it, wd);
444
445    it->base.view = edje_object_add(evas_object_evas_get(obj));
446    edje_object_scale_set(it->base.view, elm_widget_scale_get(it->base.view)
447                          *_elm_config->scale);
448    evas_object_smart_member_add(it->base.view, obj);
449    elm_widget_sub_object_add(obj, it->base.view);
450    evas_object_clip_set(it->base.view, evas_object_clip_get(obj));
451
452    _elm_theme_object_set(obj, it->base.view, "segment_control", "item",
453                          elm_object_style_get(obj));
454    edje_object_mirrored_set(it->base.view,
455                             elm_widget_mirrored_get(it->base.widget));
456
457    if (label)
458      eina_stringshare_replace(&it->label, label);
459    if (it->label)
460      edje_object_signal_emit(it->base.view, "elm,state,text,visible", "elm");
461    else
462      edje_object_signal_emit(it->base.view, "elm,state,text,hidden", "elm");
463    edje_object_message_signal_process(it->base.view);
464    edje_object_part_text_set(it->base.view, "elm.text", label);
465
466    it->icon = icon;
467    if (it->icon) elm_widget_sub_object_add(it->base.view, it->icon);
468    evas_object_event_callback_add(it->base.view, EVAS_CALLBACK_MOUSE_DOWN,
469                                   _mouse_down, it);
470    evas_object_event_callback_add(it->base.view, EVAS_CALLBACK_MOUSE_UP,
471                                   _mouse_up, it);
472    evas_object_show(it->base.view);
473
474    return it;
475 }
476
477 EAPI Evas_Object *
478 elm_segment_control_add(Evas_Object *parent)
479 {
480    Evas_Object *obj;
481    Evas *e;
482    Widget_Data *wd;
483
484    ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL);
485
486    ELM_SET_WIDTYPE(widtype, "segment_control");
487    elm_widget_type_set(obj, "segment_control");
488    elm_widget_sub_object_add(parent, obj);
489    elm_widget_data_set(obj, wd);
490    elm_widget_del_hook_set(obj, _del_hook);
491    elm_widget_theme_hook_set(obj, _theme_hook);
492    elm_widget_disable_hook_set(obj, _disable_hook);
493
494    // TODO: Focus switch support to Elm_widget_Item not supported yet.
495 #if 0
496    elm_widget_focus_next_hook_set(obj, _focus_next_hook);
497 #endif
498
499    wd->obj = obj;
500
501    wd->base = edje_object_add(e);
502    edje_object_scale_set(wd->base, elm_widget_scale_get(wd->base)
503                          *_elm_config->scale);
504    _elm_theme_object_set(obj, wd->base, "segment_control", "base", "default");
505    elm_widget_resize_object_set(obj, wd->base);
506
507    evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE,
508                                   _on_move_resize, obj);
509    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE,
510                                   _on_move_resize, obj);
511
512    evas_object_smart_callbacks_descriptions_set(obj, _signals);
513
514    return obj;
515 }
516
517 EAPI Elm_Segment_Item *
518 elm_segment_control_item_add(Evas_Object *obj, Evas_Object *icon,
519                              const char *label)
520 {
521    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
522    Elm_Segment_Item *it;
523    Widget_Data *wd;
524
525    wd = elm_widget_data_get(obj);
526    if (!wd) return NULL;
527
528    it = _item_new(obj, icon, label);
529    if (!it) return NULL;
530
531    wd->seg_items = eina_list_append(wd->seg_items, it);
532    _update_list(wd);
533
534    return it;
535 }
536
537 EAPI Elm_Segment_Item *
538 elm_segment_control_item_insert_at(Evas_Object *obj, Evas_Object *icon,
539                                    const char *label, int idx)
540 {
541    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
542    Elm_Segment_Item *it, *it_rel;
543    Widget_Data *wd;
544
545    wd = elm_widget_data_get(obj);
546    if (!wd) return NULL;
547    if (idx < 0) idx = 0;
548
549    it = _item_new(obj, icon, label);
550    if (!it) return NULL;
551
552    it_rel = _item_find(obj, idx);
553    if (it_rel)
554      wd->seg_items = eina_list_prepend_relative(wd->seg_items, it, it_rel);
555    else
556      wd->seg_items = eina_list_append(wd->seg_items, it);
557
558    _update_list(wd);
559    return it;
560 }
561
562 EAPI void
563 elm_segment_control_item_del(Elm_Segment_Item *it)
564 {
565    ELM_WIDGET_ITEM_WIDTYPE_CHECK_OR_RETURN(it);
566    Widget_Data *wd;
567
568    wd = elm_widget_item_data_get(it);
569    if (!wd) return;
570
571    _item_free(it);
572    _update_list(wd);
573 }
574
575 EAPI void
576 elm_segment_control_item_del_at(Evas_Object *obj, int idx)
577 {
578    ELM_CHECK_WIDTYPE(obj, widtype);
579    Elm_Segment_Item *it;
580    Widget_Data *wd;
581
582    wd = elm_widget_data_get(obj);
583    if (!wd) return;
584
585    it = _item_find(obj, idx);
586    if (!it) return;
587    _item_free(it);
588    _update_list(wd);
589 }
590
591 EAPI const char*
592 elm_segment_control_item_label_get(const Evas_Object *obj, int idx)
593 {
594    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
595    Elm_Segment_Item *it;
596
597    it = _item_find(obj, idx);
598    if (it) return it->label;
599
600    return NULL;
601 }
602
603 EAPI void
604 elm_segment_control_item_label_set(Elm_Segment_Item* it, const char* label)
605 {
606    ELM_WIDGET_ITEM_WIDTYPE_CHECK_OR_RETURN(it);
607    Widget_Data *wd;
608
609    wd = elm_widget_item_data_get(it);
610    if (!wd) return;
611
612    eina_stringshare_replace(&it->label, label);
613    if (it->label)
614      edje_object_signal_emit(it->base.view, "elm,state,text,visible", "elm");
615    else
616      edje_object_signal_emit(it->base.view, "elm,state,text,hidden", "elm");
617    edje_object_message_signal_process(it->base.view);
618    //label can be NULL also.
619    edje_object_part_text_set(it->base.view, "elm.text", it->label);
620 }
621
622 EAPI Evas_Object *
623 elm_segment_control_item_icon_get(const Evas_Object *obj, int idx)
624 {
625    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
626    Elm_Segment_Item *it;
627
628    it = _item_find(obj, idx);
629    if (it) return it->icon;
630
631    return NULL;
632 }
633
634 EAPI void
635 elm_segment_control_item_icon_set(Elm_Segment_Item *it, Evas_Object *icon)
636 {
637    ELM_WIDGET_ITEM_WIDTYPE_CHECK_OR_RETURN(it);
638
639    //Remove the existing icon
640    if (it->icon)
641      {
642         edje_object_part_unswallow(it->base.view, it->icon);
643         evas_object_del(it->icon);
644         it->icon = NULL;
645      }
646
647    it->icon = icon;
648    if (it->icon)
649      {
650         elm_widget_sub_object_add(it->base.view, it->icon);
651         edje_object_part_swallow(it->base.view, "elm.swallow.icon", it->icon);
652         edje_object_signal_emit(it->base.view, "elm,state,icon,visible", "elm");
653      }
654    else
655      edje_object_signal_emit(it->base.view, "elm,state,icon,hidden", "elm");
656 }
657
658 EAPI int
659 elm_segment_control_item_count_get(const Evas_Object *obj)
660 {
661    ELM_CHECK_WIDTYPE(obj, widtype) 0;
662    Widget_Data *wd;
663
664    wd = elm_widget_data_get(obj);
665    if (!wd) return 0;
666
667    return eina_list_count(wd->seg_items);
668 }
669
670 EAPI Evas_Object*
671 elm_segment_control_item_object_get(const Elm_Segment_Item *it)
672 {
673    ELM_WIDGET_ITEM_WIDTYPE_CHECK_OR_RETURN(it, NULL);
674
675    return it->base.view;
676 }
677
678 EAPI Elm_Segment_Item*
679 elm_segment_control_item_selected_get(const Evas_Object *obj)
680 {
681    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
682    Widget_Data *wd;
683
684    wd = elm_widget_data_get(obj);
685    if (!wd) return NULL;
686
687    return wd->selected_item;
688 }
689
690 EAPI void
691 elm_segment_control_item_selected_set(Elm_Segment_Item *it, Eina_Bool selected)
692 {
693    ELM_WIDGET_ITEM_WIDTYPE_CHECK_OR_RETURN(it);
694    Widget_Data *wd;
695
696    wd = elm_widget_item_data_get(it);
697    if (!wd) return;
698
699    if (it == wd->selected_item)
700      {
701         //already in selected state.
702         if (selected) return;
703
704         //unselect case
705         _segment_off(it);
706      }
707    else if (selected)
708      _segment_on(it);
709
710    return;
711 }
712
713 EAPI Elm_Segment_Item *
714 elm_segment_control_item_get(const Evas_Object *obj, int idx)
715 {
716    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
717    Elm_Segment_Item *it;
718
719    it = _item_find(obj, idx);
720
721    return it;
722 }
723
724 EAPI int
725 elm_segment_control_item_index_get(const Elm_Segment_Item *it)
726 {
727    ELM_WIDGET_ITEM_WIDTYPE_CHECK_OR_RETURN(it, -1);
728
729    return it->seg_index;
730 }