1 #include <Elementary.h>
3 #include "elm_widget_flipselector.h"
5 /* TODO: ideally, the default theme would use map{} blocks on the TEXT
6 parts to implement their fading in/out propertly (as in the clock
8 /* TODO: if one ever wants to extend it to receiving generic widgets
9 as items, be my guest. in this case, remember to implement the
10 items tooltip infra. */
11 /* TODO: fix default theme image borders for looong strings as item
13 /* TODO: set text elipsis on labels if one enforces mininum size on
14 * the overall widget less the required for displaying it. */
15 /* TODO: find a way to, in the default theme, to detect we are
16 * bootstrapping (receiving the 1st message) and populate the downmost
17 * TEXT parts with the same text as the upmost, where appropriate. */
19 EAPI const char ELM_FLIPSELECTOR_SMART_NAME[] = "elm_flipselector";
21 #define FLIP_FIRST_INTERVAL (0.85)
22 #define FLIP_MIN_INTERVAL (0.1)
23 #define MSG_FLIP_DOWN (1)
24 #define MSG_FLIP_UP (2)
25 #define MAX_LEN_DEFAULT (50)
27 #define DATA_GET eina_list_data_get
29 static const char SIG_SELECTED[] = "selected";
30 static const char SIG_UNDERFLOWED[] = "underflowed";
31 static const char SIG_OVERFLOWED[] = "overflowed";
32 static const Evas_Smart_Cb_Description _smart_callbacks[] = {
34 {SIG_UNDERFLOWED, ""},
39 EVAS_SMART_SUBCLASS_NEW
40 (ELM_FLIPSELECTOR_SMART_NAME, _elm_flipselector,
41 Elm_Flipselector_Smart_Class, Elm_Layout_Smart_Class,
42 elm_layout_smart_class_get, _smart_callbacks);
45 _elm_flipselector_smart_sizing_eval(Evas_Object *obj)
48 Evas_Coord minw = -1, minh = -1, w, h;
50 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
52 if (sd->evaluating) return;
54 elm_coords_finger_size_adjust(1, &minw, 2, &minh);
56 sd->evaluating = EINA_TRUE;
60 const char *label = elm_object_item_text_get(DATA_GET(sd->sentinel));
62 tmp = strdup(elm_layout_text_get(obj, "elm.top"));
63 elm_layout_text_set(obj, "elm.top", label);
66 edje_object_size_min_restricted_calc
67 (ELM_WIDGET_DATA(sd)->resize_obj, &minw, &minh, minw, minh);
68 elm_coords_finger_size_adjust(1, &minw, 2, &minh);
69 evas_object_size_hint_min_get(obj, &w, &h);
73 elm_layout_text_set(obj, "elm.top", tmp);
77 if (w > minw) minw = w;
78 if (h > minh) minh = h;
80 sd->evaluating = EINA_FALSE;
82 evas_object_size_hint_min_set(obj, minw, minh);
86 _update_view(Evas_Object *obj)
89 Elm_Flipselector_Item *item;
91 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
94 item = DATA_GET(sd->current);
95 if (item) label = item->label;
97 elm_layout_text_set(obj, "elm.top", label ? label : "");
98 elm_layout_text_set(obj, "elm.bottom", label ? label : "");
100 edje_object_message_signal_process(ELM_WIDGET_DATA(sd)->resize_obj);
104 _item_text_set_hook(Elm_Object_Item *it,
109 Elm_Flipselector_Item *item;
113 if (part && strcmp(part, "default")) return;
115 item = (Elm_Flipselector_Item *)it;
116 ELM_FLIPSELECTOR_DATA_GET(WIDGET(item), sd);
118 if (!sd->items) return;
120 l = eina_list_data_find_list(sd->items, item);
123 eina_stringshare_del(item->label);
124 item->label = eina_stringshare_add_length(label, sd->max_len);
126 if (strlen(label) > strlen(elm_object_item_text_get(DATA_GET(sd->sentinel))))
129 if (sd->current == l)
131 _update_view(WIDGET(item));
132 elm_layout_sizing_eval(WIDGET(item));
137 _item_text_get_hook(const Elm_Object_Item *it,
140 if (part && strcmp(part, "default")) return NULL;
142 return ((Elm_Flipselector_Item *)it)->label;
146 _item_signal_emit_hook(Elm_Object_Item *it,
147 const char *emission,
150 Elm_Flipselector_Item *item = (Elm_Flipselector_Item *)it;
152 edje_object_signal_emit(VIEW(item), emission, source);
156 _flipselector_walk(Elm_Flipselector_Smart_Data *sd)
160 ERR("walking was negative. fixed!\n");
167 _sentinel_eval(Elm_Flipselector_Smart_Data *sd)
169 Elm_Flipselector_Item *it;
178 sd->sentinel = sd->items;
180 EINA_LIST_FOREACH(sd->items, l, it)
182 if (strlen(elm_object_item_text_get((Elm_Object_Item *)it)) >
183 strlen(elm_object_item_text_get(DATA_GET(sd->sentinel))))
188 /* TODO: create a flag to avoid looping here all times */
190 _flipselector_process_deletions(Elm_Flipselector_Smart_Data *sd)
193 Elm_Flipselector_Item *it;
194 Eina_Bool skip = EINA_TRUE;
195 Eina_Bool sentinel_eval = EINA_FALSE;
197 sd->walking++; /* avoid nested deletions */
199 EINA_LIST_FOREACH(sd->items, l, it)
201 if (!it->deleted) continue;
203 if (sd->current == l)
205 if (sd->current == sd->sentinel) sentinel_eval = EINA_TRUE;
206 sd->current = eina_list_prev(sd->current);
208 sd->items = eina_list_remove(sd->items, it);
210 if (!sd->current) sd->current = sd->items;
212 elm_widget_item_del(it);
215 if (eina_list_count(sd->items) <= 1)
216 elm_layout_signal_emit
217 (ELM_WIDGET_DATA(sd)->obj, "elm,state,button,hidden", "elm");
219 elm_layout_signal_emit
220 (ELM_WIDGET_DATA(sd)->obj, "elm,state,button,visible", "elm");
223 if (!skip) _update_view(ELM_WIDGET_DATA(sd)->obj);
225 if (sentinel_eval) _sentinel_eval(sd);
231 _flipselector_unwalk(Elm_Flipselector_Smart_Data *sd)
237 ERR("walking became negative. fixed!\n");
240 if (sd->walking) return;
242 _flipselector_process_deletions(sd);
246 _on_item_changed(Elm_Flipselector_Smart_Data *sd)
248 Elm_Flipselector_Item *item;
250 item = DATA_GET(sd->current);
252 if (sd->deleting) return;
255 item->func((void *)item->base.data, WIDGET(item), item);
257 evas_object_smart_callback_call
258 (ELM_WIDGET_DATA(sd)->obj, SIG_SELECTED, item);
262 _send_msg(Elm_Flipselector_Smart_Data *sd,
266 Edje_Message_String msg;
269 edje_object_message_send
270 (ELM_WIDGET_DATA(sd)->resize_obj, EDJE_MESSAGE_STRING, flipside, &msg);
271 edje_object_message_signal_process(ELM_WIDGET_DATA(sd)->resize_obj);
273 _on_item_changed(sd);
277 _item_del_pre_hook(Elm_Object_Item *it)
279 Elm_Flipselector_Item *item, *item2;
282 item = (Elm_Flipselector_Item *)it;
283 ELM_FLIPSELECTOR_DATA_GET(WIDGET(item), sd);
287 item->deleted = EINA_TRUE;
291 _flipselector_walk(sd);
293 EINA_LIST_FOREACH(sd->items, l, item2)
297 if (sd->current == l)
299 sd->current = l->prev;
300 if (!sd->current) sd->current = l->next;
303 item2 = sd->current->data;
304 _send_msg(sd, MSG_FLIP_DOWN, (char *)item2->label);
306 else _send_msg(sd, MSG_FLIP_DOWN, "");
308 sd->items = eina_list_remove_list(sd->items, l);
313 eina_stringshare_del(item->label);
315 _flipselector_unwalk(sd);
320 static Elm_Flipselector_Item *
321 _item_new(Evas_Object *obj,
327 Elm_Flipselector_Item *it;
329 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
331 it = elm_widget_item_new(obj, Elm_Flipselector_Item);
332 if (!it) return NULL;
334 elm_widget_item_del_pre_hook_set(it, _item_del_pre_hook);
335 elm_widget_item_text_set_hook_set(it, _item_text_set_hook);
336 elm_widget_item_text_get_hook_set(it, _item_text_get_hook);
337 elm_widget_item_signal_emit_hook_set(it, _item_signal_emit_hook);
340 if (len > sd->max_len) len = sd->max_len;
342 it->label = eina_stringshare_add_length(label, len);
344 it->base.data = data;
346 /* TODO: no view here, but if one desires general contents in the
352 _elm_flipselector_smart_theme(Evas_Object *obj)
356 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
358 if (!ELM_WIDGET_CLASS(_elm_flipselector_parent_sc)->theme(obj))
361 max_len = edje_object_data_get(ELM_WIDGET_DATA(sd)->resize_obj, "max_len");
362 if (!max_len) sd->max_len = MAX_LEN_DEFAULT;
365 sd->max_len = atoi(max_len);
367 if (!sd->max_len) sd->max_len = MAX_LEN_DEFAULT;
371 elm_layout_sizing_eval(obj);
377 _flip_up(Elm_Flipselector_Smart_Data *sd)
379 Elm_Flipselector_Item *item;
381 if (!sd->current) return;
383 if (sd->deleting) return;
384 if (sd->current == sd->items)
386 sd->current = eina_list_last(sd->items);
387 evas_object_smart_callback_call
388 (ELM_WIDGET_DATA(sd)->obj, SIG_UNDERFLOWED, NULL);
391 sd->current = eina_list_prev(sd->current);
393 item = DATA_GET(sd->current);
396 _send_msg(sd, MSG_FLIP_UP, (char *)item->label);
400 _flip_down(Elm_Flipselector_Smart_Data *sd)
402 Elm_Flipselector_Item *item;
404 if (!sd->current) return;
406 if (sd->deleting) return;
407 sd->current = eina_list_next(sd->current);
410 sd->current = sd->items;
411 evas_object_smart_callback_call
412 (ELM_WIDGET_DATA(sd)->obj, SIG_OVERFLOWED, NULL);
415 item = DATA_GET(sd->current);
418 _send_msg(sd, MSG_FLIP_DOWN, (char *)item->label);
422 _elm_flipselector_smart_event(Evas_Object *obj,
423 Evas_Object *src __UNUSED__,
424 Evas_Callback_Type type,
427 Evas_Event_Key_Down *ev;
428 Eina_Bool is_up = EINA_TRUE;
430 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
432 if (elm_widget_disabled_get(obj)) return EINA_FALSE;
434 if (type != EVAS_CALLBACK_KEY_DOWN) return EINA_FALSE;
437 if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
439 if ((!strcmp(ev->keyname, "Down")) || (!strcmp(ev->keyname, "KP_Down")))
441 else if ((strcmp(ev->keyname, "Up")) && (strcmp(ev->keyname, "KP_Up")))
444 if (sd->spin) ecore_timer_del(sd->spin);
447 /* TODO: if direction setting via API is not coming in, replace
448 these calls by flip_{next,prev} */
449 _flipselector_walk(sd);
451 if (is_up) _flip_up(sd);
454 _flipselector_unwalk(sd);
456 ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
462 _signal_val_up(void *data)
464 ELM_FLIPSELECTOR_DATA_GET(data, sd);
466 _flipselector_walk(sd);
468 if (sd->interval > FLIP_MIN_INTERVAL) sd->interval = sd->interval / 1.05;
470 ecore_timer_interval_set(sd->spin, sd->interval);
474 _flipselector_unwalk(sd);
476 return ECORE_CALLBACK_RENEW;
480 _signal_val_up_start(void *data,
481 Evas_Object *obj __UNUSED__,
482 const char *emission __UNUSED__,
483 const char *source __UNUSED__)
485 ELM_FLIPSELECTOR_DATA_GET(data, sd);
487 sd->interval = sd->first_interval;
489 if (sd->spin) ecore_timer_del(sd->spin);
490 sd->spin = ecore_timer_add(sd->interval, _signal_val_up, data);
492 _signal_val_up(data);
496 _signal_val_down(void *data)
498 ELM_FLIPSELECTOR_DATA_GET(data, sd);
500 _flipselector_walk(sd);
502 if (sd->interval > FLIP_MIN_INTERVAL) sd->interval = sd->interval / 1.05;
503 ecore_timer_interval_set(sd->spin, sd->interval);
507 _flipselector_unwalk(sd);
509 return ECORE_CALLBACK_RENEW;
513 _signal_val_down_start(void *data,
514 Evas_Object *obj __UNUSED__,
515 const char *emission __UNUSED__,
516 const char *source __UNUSED__)
518 ELM_FLIPSELECTOR_DATA_GET(data, sd);
520 sd->interval = sd->first_interval;
522 if (sd->spin) ecore_timer_del(sd->spin);
523 sd->spin = ecore_timer_add(sd->interval, _signal_val_down, data);
525 _signal_val_down(data);
529 _signal_val_change_stop(void *data,
530 Evas_Object *obj __UNUSED__,
531 const char *emission __UNUSED__,
532 const char *source __UNUSED__)
534 ELM_FLIPSELECTOR_DATA_GET(data, sd);
536 if (sd->spin) ecore_timer_del(sd->spin);
541 _elm_flipselector_smart_add(Evas_Object *obj)
543 EVAS_SMART_DATA_ALLOC(obj, Elm_Flipselector_Smart_Data);
545 ELM_WIDGET_CLASS(_elm_flipselector_parent_sc)->base.add(obj);
548 (obj, "flipselector", "base", elm_widget_style_get(obj));
550 elm_layout_signal_callback_add
551 (obj, "elm,action,up,start", "", _signal_val_up_start, obj);
552 elm_layout_signal_callback_add
553 (obj, "elm,action,up,stop", "", _signal_val_change_stop, obj);
554 elm_layout_signal_callback_add
555 (obj, "elm,action,down,start", "", _signal_val_down_start, obj);
556 elm_layout_signal_callback_add
557 (obj, "elm,action,down,stop", "", _signal_val_change_stop, obj);
559 priv->first_interval = FLIP_FIRST_INTERVAL;
560 priv->evaluating = EINA_FALSE;
562 elm_widget_can_focus_set(obj, EINA_TRUE);
564 _elm_flipselector_smart_theme(obj);
568 _elm_flipselector_smart_del(Evas_Object *obj)
570 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
572 sd->deleting = EINA_TRUE;
574 if (sd->walking) ERR("flipselector deleted while walking.\n");
577 elm_widget_item_del(DATA_GET(sd->items));
579 if (sd->spin) ecore_timer_del(sd->spin);
581 ELM_WIDGET_CLASS(_elm_flipselector_parent_sc)->base.del(obj);
585 _elm_flipselector_smart_set_user(Elm_Flipselector_Smart_Class *sc)
587 ELM_WIDGET_CLASS(sc)->base.add = _elm_flipselector_smart_add;
588 ELM_WIDGET_CLASS(sc)->base.del = _elm_flipselector_smart_del;
590 ELM_WIDGET_CLASS(sc)->theme = _elm_flipselector_smart_theme;
591 ELM_WIDGET_CLASS(sc)->event = _elm_flipselector_smart_event;
593 /* not a 'focus chain manager' */
594 ELM_WIDGET_CLASS(sc)->focus_next = NULL;
595 ELM_WIDGET_CLASS(sc)->focus_direction = NULL;
597 ELM_LAYOUT_CLASS(sc)->sizing_eval = _elm_flipselector_smart_sizing_eval;
600 EAPI const Elm_Flipselector_Smart_Class *
601 elm_flipselector_smart_class_get(void)
603 static Elm_Flipselector_Smart_Class _sc =
604 ELM_FLIPSELECTOR_SMART_CLASS_INIT_NAME_VERSION
605 (ELM_FLIPSELECTOR_SMART_NAME);
606 static const Elm_Flipselector_Smart_Class *class = NULL;
607 Evas_Smart_Class *esc = (Evas_Smart_Class *)&_sc;
612 _elm_flipselector_smart_set(&_sc);
613 esc->callbacks = _smart_callbacks;
620 elm_flipselector_add(Evas_Object *parent)
624 EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
626 obj = elm_widget_add(_elm_flipselector_smart_class_new(), parent);
627 if (!obj) return NULL;
629 if (!elm_widget_sub_object_add(parent, obj))
630 ERR("could not add %p as sub object of %p", obj, parent);
636 elm_flipselector_flip_next(Evas_Object *obj)
638 ELM_FLIPSELECTOR_CHECK(obj);
639 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
641 if (sd->spin) ecore_timer_del(sd->spin);
644 _flipselector_walk(sd);
646 _flipselector_unwalk(sd);
650 elm_flipselector_flip_prev(Evas_Object *obj)
652 ELM_FLIPSELECTOR_CHECK(obj);
654 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
656 if (sd->spin) ecore_timer_del(sd->spin);
659 _flipselector_walk(sd);
661 _flipselector_unwalk(sd);
664 EAPI Elm_Object_Item *
665 elm_flipselector_item_append(Evas_Object *obj,
667 void (*func)(void *, Evas_Object *, void *),
670 Elm_Flipselector_Item *item;
672 ELM_FLIPSELECTOR_CHECK(obj) NULL;
673 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
675 item = _item_new(obj, label, func, data);
676 if (!item) return NULL;
678 sd->items = eina_list_append(sd->items, item);
681 sd->current = sd->items;
686 (strlen(elm_object_item_text_get((Elm_Object_Item *)item)) >
687 strlen(elm_object_item_text_get(DATA_GET(sd->sentinel)))))
689 sd->sentinel = eina_list_last(sd->items);
690 elm_layout_sizing_eval(obj);
693 if (eina_list_count(sd->items) > 1)
694 elm_layout_signal_emit(obj, "elm,state,button,visible", "elm");
696 return (Elm_Object_Item *)item;
699 EAPI Elm_Object_Item *
700 elm_flipselector_item_prepend(Evas_Object *obj,
702 void (*func)(void *, Evas_Object *, void *),
705 Elm_Flipselector_Item *item;
707 ELM_FLIPSELECTOR_CHECK(obj) NULL;
708 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
710 item = _item_new(obj, label, func, data);
711 if (!item) return NULL;
713 sd->items = eina_list_prepend(sd->items, item);
716 sd->current = sd->items;
721 (strlen(elm_object_item_text_get((Elm_Object_Item *)item)) >
722 strlen(elm_object_item_text_get(DATA_GET(sd->sentinel)))))
724 sd->sentinel = sd->items;
725 elm_layout_sizing_eval(obj);
728 if (eina_list_count(sd->items) >= 2)
729 elm_layout_signal_emit(obj, "elm,state,button,visible", "elm");
731 return (Elm_Object_Item *)item;
734 EAPI const Eina_List *
735 elm_flipselector_items_get(const Evas_Object *obj)
737 ELM_FLIPSELECTOR_CHECK(obj) NULL;
738 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
743 EAPI Elm_Object_Item *
744 elm_flipselector_first_item_get(const Evas_Object *obj)
746 Elm_Flipselector_Item *it;
749 ELM_FLIPSELECTOR_CHECK(obj) NULL;
750 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
752 if (!sd->items) return NULL;
754 EINA_LIST_FOREACH(sd->items, l, it)
756 if (it->deleted) continue;
757 return (Elm_Object_Item *)it;
763 EAPI Elm_Object_Item *
764 elm_flipselector_last_item_get(const Evas_Object *obj)
766 Elm_Flipselector_Item *it;
769 ELM_FLIPSELECTOR_CHECK(obj) NULL;
770 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
772 if (!sd->items) return NULL;
774 EINA_LIST_REVERSE_FOREACH(sd->items, l, it)
776 if (it->deleted) continue;
777 return (Elm_Object_Item *)it;
783 EAPI Elm_Object_Item *
784 elm_flipselector_selected_item_get(const Evas_Object *obj)
786 ELM_FLIPSELECTOR_CHECK(obj) NULL;
787 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
789 return DATA_GET(sd->current);
793 elm_flipselector_item_selected_set(Elm_Object_Item *it,
796 Elm_Flipselector_Item *item, *_item, *cur;
797 int flipside = MSG_FLIP_UP;
800 ELM_FLIPSELECTOR_ITEM_CHECK_OR_RETURN(it);
802 item = (Elm_Flipselector_Item *)it;
803 ELM_FLIPSELECTOR_DATA_GET(WIDGET(item), sd);
805 cur = DATA_GET(sd->current);
806 if ((selected) && (cur == item)) return;
808 _flipselector_walk(sd);
810 if ((!selected) && (cur == item))
812 EINA_LIST_FOREACH(sd->items, l, _item)
817 _send_msg(sd, MSG_FLIP_UP, (char *)_item->label);
821 _flipselector_unwalk(sd);
825 EINA_LIST_FOREACH(sd->items, l, _item)
827 if (_item == cur) flipside = MSG_FLIP_DOWN;
832 _send_msg(sd, flipside, (char *)item->label);
837 _flipselector_unwalk(sd);
841 elm_flipselector_item_selected_get(const Elm_Object_Item *it)
843 Elm_Flipselector_Item *item;
845 ELM_FLIPSELECTOR_ITEM_CHECK_OR_RETURN(it, EINA_FALSE);
847 item = (Elm_Flipselector_Item *)it;
848 ELM_FLIPSELECTOR_DATA_GET(WIDGET(item), sd);
850 return eina_list_data_get(sd->current) == item;
853 EAPI Elm_Object_Item *
854 elm_flipselector_item_prev_get(const Elm_Object_Item *it)
856 Elm_Flipselector_Item *item = (Elm_Flipselector_Item *)it;
859 ELM_FLIPSELECTOR_ITEM_CHECK_OR_RETURN(it, NULL);
860 ELM_FLIPSELECTOR_DATA_GET(WIDGET(item), sd);
862 if ((!sd->items)) return NULL;
864 l = eina_list_data_find_list(sd->items, it);
865 if (l && l->prev) return DATA_GET(l->prev);
870 EAPI Elm_Object_Item *
871 elm_flipselector_item_next_get(const Elm_Object_Item *it)
874 Elm_Flipselector_Item *item = (Elm_Flipselector_Item *)it;
876 ELM_FLIPSELECTOR_ITEM_CHECK_OR_RETURN(it, NULL);
877 ELM_FLIPSELECTOR_DATA_GET(WIDGET(item), sd);
879 if ((!sd->items)) return NULL;
881 l = eina_list_data_find_list(sd->items, it);
882 if (l && l->next) return DATA_GET(l->next);
888 elm_flipselector_first_interval_set(Evas_Object *obj,
891 ELM_FLIPSELECTOR_CHECK(obj);
892 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
894 sd->first_interval = interval;
898 elm_flipselector_first_interval_get(const Evas_Object *obj)
900 ELM_FLIPSELECTOR_CHECK(obj) 0;
901 ELM_FLIPSELECTOR_DATA_GET(obj, sd);
903 return sd->first_interval;