1 #include <Elementary.h>
6 * @defgroup Spinner Spinner
9 * A spinner is a widget which allows the user to increase or decrease
10 * numeric values. By default the spinner will not wrap and has a label
11 * of "%.0f" (just showing the integer value of the double).
13 * A spinner has a label that is formatted with floating
14 * point values and thus accepts a printf-style format string, like
17 * Signals that you can add callbacks for are:
19 * changed - Whenever the spinner value is changed by the user.
21 * delay,changed - A short time after the value is changed by the user.
22 * This will be called only when the user stops dragging for a very short
23 * period or when they release their finger/mouse, so it avoids possibly
24 * expensive reactions to the value change.
26 typedef struct _Widget_Data Widget_Data;
27 typedef struct _Elm_Spinner_Special_Value Elm_Spinner_Special_Value;
31 Evas_Object *spinner, *ent;
33 double val, val_min, val_max, orig_val, step;
34 double drag_start_pos, spin_speed, interval, first_interval;
35 Ecore_Timer *delay, *spin;
36 Eina_List *special_values;
38 Eina_Bool entry_visible : 1;
39 Eina_Bool dragging : 1;
40 Eina_Bool editable : 1;
43 struct _Elm_Spinner_Special_Value {
48 static const char *widtype = NULL;
49 static void _del_hook(Evas_Object *obj);
50 static void _disable_hook(Evas_Object *obj);
51 static void _write_label(Evas_Object *obj);
52 static void _sizing_eval(Evas_Object *obj);
53 //static void _changed_size_hints(void *data, Evas *e, Evas_Object *obj, void *event_info);
54 static Eina_Bool _value_set(Evas_Object *obj, double delta);
57 _del_hook(Evas_Object *obj)
59 Elm_Spinner_Special_Value *sv;
60 Widget_Data *wd = elm_widget_data_get(obj);
62 if (wd->label) eina_stringshare_del(wd->label);
63 if (wd->delay) ecore_timer_del(wd->delay);
64 if (wd->spin) ecore_timer_del(wd->spin);
65 if (wd->special_values)
66 EINA_LIST_FREE(wd->special_values, sv)
68 eina_stringshare_del(sv->label);
75 _disable_hook(Evas_Object *obj)
77 Widget_Data *wd = elm_widget_data_get(obj);
79 if (elm_widget_disabled_get(obj))
80 edje_object_signal_emit(wd->spinner, "elm,state,disabled", "elm");
82 edje_object_signal_emit(wd->spinner, "elm,state,enabled", "elm");
86 _signal_emit_hook(Evas_Object *obj, const char *emission, const char *source)
88 Widget_Data *wd = elm_widget_data_get(obj);
90 edje_object_signal_emit(wd->spinner, emission, source);
94 _signal_callback_add_hook(Evas_Object *obj, const char *emission, const char *source, void (*func_cb) (void *data, Evas_Object *o, const char *emission, const char *source), void *data)
96 Widget_Data *wd = elm_widget_data_get(obj);
98 edje_object_signal_callback_add(wd->spinner, emission,
99 source, func_cb, data);
103 _signal_callback_del_hook(Evas_Object *obj, const char *emission, const char *source, void (*func_cb) (void *data, Evas_Object *o, const char *emission, const char *source))
105 Widget_Data *wd = elm_widget_data_get(obj);
106 if (!wd) return NULL;
107 return edje_object_signal_callback_del(wd->spinner, emission, source,
112 _theme_hook(Evas_Object *obj)
114 Widget_Data *wd = elm_widget_data_get(obj);
116 _elm_theme_object_set(obj, wd->spinner, "spinner", "base", elm_widget_style_get(obj));
117 edje_object_part_swallow(wd->spinner, "elm.swallow.entry", wd->ent);
119 edje_object_message_signal_process(wd->spinner);
120 edje_object_scale_set(wd->spinner, elm_widget_scale_get(obj) * _elm_config->scale);
121 if (elm_widget_focus_get(obj))
122 edje_object_signal_emit(wd->spinner, "elm,action,focus", "elm");
124 edje_object_signal_emit(wd->spinner, "elm,action,unfocus", "elm");
129 _on_focus_hook(void *data __UNUSED__, Evas_Object *obj)
131 Widget_Data *wd = elm_widget_data_get(obj);
133 if (elm_widget_focus_get(obj))
134 edje_object_signal_emit(wd->spinner, "elm,action,focus", "elm");
136 edje_object_signal_emit(wd->spinner, "elm,action,unfocus", "elm");
140 _delay_change(void *data)
142 Widget_Data *wd = elm_widget_data_get(data);
143 if (!wd) return ECORE_CALLBACK_CANCEL;
145 evas_object_smart_callback_call(data, "delay,changed", NULL);
146 return ECORE_CALLBACK_CANCEL;
150 _entry_show(Widget_Data *wd)
152 char buf[32], fmt[32] = "%0.f";
154 /* try to construct just the format from given label
155 * completely ignoring pre/post words
159 const char *start = strchr(wd->label, '%');
166 start = strchr(start + 2, '%');
171 const char *itr, *end = NULL;
172 for (itr = start + 1; *itr != '\0'; itr++)
174 /* allowing '%d' is quite dangerous, remove it? */
175 if ((*itr == 'd') || (*itr == 'f'))
182 if ((end) && ((size_t)(end - start + 1) < sizeof(fmt)))
184 memcpy(fmt, start, end - start);
185 fmt[end - start] = '\0';
189 snprintf(buf, sizeof(buf), fmt, wd->val);
190 elm_entry_entry_set(wd->ent, buf);
194 _write_label(Evas_Object *obj)
197 Elm_Spinner_Special_Value *sv;
198 Widget_Data *wd = elm_widget_data_get(obj);
201 EINA_LIST_FOREACH(wd->special_values, l, sv)
202 if (sv->value == wd->val)
204 snprintf(buf, sizeof(buf), "%s", sv->label);
208 snprintf(buf, sizeof(buf), wd->label, wd->val);
210 snprintf(buf, sizeof(buf), "%.0f", wd->val);
213 edje_object_part_text_set(wd->spinner, "elm.text", buf);
214 if (wd->entry_visible) _entry_show(wd);
218 _value_set(Evas_Object *obj, double delta)
220 Widget_Data *wd = elm_widget_data_get(obj);
222 if (!wd) return EINA_FALSE;
223 new_val = wd->val + delta;
226 while (new_val < wd->val_min)
227 new_val = wd->val_max + new_val + 1 - wd->val_min;
228 while (new_val > wd->val_max)
229 new_val = wd->val_min + new_val - wd->val_max - 1;
233 if (new_val < wd->val_min)
234 new_val = wd->val_min;
235 else if (new_val > wd->val_max)
236 new_val = wd->val_max;
239 if (new_val == wd->val) return EINA_FALSE;
242 evas_object_smart_callback_call(obj, "changed", NULL);
243 if (wd->delay) ecore_timer_del(wd->delay);
244 wd->delay = ecore_timer_add(0.2, _delay_change, obj);
250 _sizing_eval(Evas_Object *obj)
252 Widget_Data *wd = elm_widget_data_get(obj);
253 Evas_Coord minw = -1, minh = -1;
255 elm_coords_finger_size_adjust(1, &minw, 1, &minh);
256 edje_object_size_min_restricted_calc(wd->spinner, &minw, &minh, minw, minh);
257 elm_coords_finger_size_adjust(1, &minw, 1, &minh);
258 evas_object_size_hint_min_set(obj, minw, minh);
259 evas_object_size_hint_max_set(obj, -1, -1);
264 _changed_size_hints(void *data, Evas *e, Evas_Object *obj, void *event_info)
271 _val_set(Evas_Object *obj)
273 Widget_Data *wd = elm_widget_data_get(obj);
276 if (wd->val_max > wd->val_min)
277 pos = ((wd->val - wd->val_min) / (wd->val_max - wd->val_min));
278 if (pos < 0.0) pos = 0.0;
279 else if (pos > 1.0) pos = 1.0;
280 edje_object_part_drag_value_set(wd->spinner, "elm.dragable.slider",
285 _drag(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
287 Widget_Data *wd = elm_widget_data_get(data);
288 double pos = 0.0, offset, delta;
290 if (wd->entry_visible) return;
291 edje_object_part_drag_value_get(wd->spinner, "elm.dragable.slider",
294 delta = (pos - wd->drag_start_pos) * offset;
295 if (_value_set(data, delta)) _write_label(data);
296 wd->drag_start_pos = pos;
301 _drag_start(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
303 Widget_Data *wd = elm_widget_data_get(data);
306 edje_object_part_drag_value_get(wd->spinner, "elm.dragable.slider",
308 wd->drag_start_pos = pos;
312 _drag_stop(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
314 Widget_Data *wd = elm_widget_data_get(data);
316 wd->drag_start_pos = 0;
317 edje_object_part_drag_value_set(wd->spinner, "elm.dragable.slider", 0.0, 0.0);
321 _hide_entry(Evas_Object *obj)
323 Widget_Data *wd = elm_widget_data_get(obj);
325 edje_object_signal_emit(wd->spinner, "elm,state,inactive", "elm");
326 wd->entry_visible = 0;
330 _reset_value(Evas_Object *obj)
332 Widget_Data *wd = elm_widget_data_get(obj);
335 elm_spinner_value_set(obj, wd->orig_val);
339 _apply_entry_value(Evas_Object *obj)
341 Widget_Data *wd = elm_widget_data_get(obj);
348 str = elm_entry_entry_get(wd->ent);
350 val = strtod(str, &end);
351 if ((*end != '\0') && (!isspace(*end))) return;
352 elm_spinner_value_set(obj, val);
356 _toggle_entry(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
358 Widget_Data *wd = elm_widget_data_get(data);
365 if (elm_widget_disabled_get(data)) return;
366 if (!wd->editable) return;
367 if (wd->entry_visible) _apply_entry_value(data);
370 wd->orig_val = wd->val;
371 edje_object_signal_emit(wd->spinner, "elm,state,active", "elm");
373 elm_entry_select_all(wd->ent);
374 elm_widget_focus_set(wd->ent, 1);
375 wd->entry_visible = 1;
380 _spin_value(void *data)
382 Widget_Data *wd = elm_widget_data_get(data);
383 if (!wd) return ECORE_CALLBACK_CANCEL;
384 if (_value_set(data, wd->spin_speed)) _write_label(data);
385 wd->interval = wd->interval / 1.05;
386 ecore_timer_interval_set(wd->spin, wd->interval);
387 return ECORE_CALLBACK_RENEW;
391 _val_inc_start(Evas_Object *obj)
393 Widget_Data *wd = elm_widget_data_get(obj);
395 wd->interval = wd->first_interval;
396 wd->spin_speed = wd->step;
397 if (wd->spin) ecore_timer_del(wd->spin);
398 wd->spin = ecore_timer_add(wd->interval, _spin_value, obj);
403 _val_inc_stop(Evas_Object *obj)
405 Widget_Data *wd = elm_widget_data_get(obj);
407 wd->interval = wd->first_interval;
409 if (wd->spin) ecore_timer_del(wd->spin);
414 _val_dec_start(Evas_Object *obj)
416 Widget_Data *wd = elm_widget_data_get(obj);
418 wd->interval = wd->first_interval;
419 wd->spin_speed = -wd->step;
420 if (wd->spin) ecore_timer_del(wd->spin);
421 wd->spin = ecore_timer_add(wd->interval, _spin_value, obj);
426 _val_dec_stop(Evas_Object *obj)
428 Widget_Data *wd = elm_widget_data_get(obj);
430 wd->interval = wd->first_interval;
432 if (wd->spin) ecore_timer_del(wd->spin);
437 _button_inc_start(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
439 Widget_Data *wd = elm_widget_data_get(data);
441 if (wd->entry_visible)
446 _val_inc_start(data);
450 _button_inc_stop(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
452 Widget_Data *wd = elm_widget_data_get(data);
458 _button_dec_start(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
460 Widget_Data *wd = elm_widget_data_get(data);
462 if (wd->entry_visible)
467 _val_dec_start(data);
471 _button_dec_stop(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
473 Widget_Data *wd = elm_widget_data_get(data);
479 _entry_activated(void *data, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
481 Widget_Data *wd = elm_widget_data_get(data);
483 _apply_entry_value(data);
484 evas_object_smart_callback_call(data, "changed", NULL);
485 if (wd->delay) ecore_timer_del(wd->delay);
486 wd->delay = ecore_timer_add(0.2, _delay_change, data);
490 _entry_event_key_down(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info)
492 Evas_Event_Key_Down *ev = event_info;
493 Widget_Data *wd = elm_widget_data_get(data);
495 if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
496 if (!strcmp(ev->keyname, "Up")) _val_inc_start(data);
497 else if (!strcmp(ev->keyname, "Down")) _val_dec_start(data);
501 _entry_event_key_up(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info)
503 Evas_Event_Key_Down *ev = event_info;
504 Widget_Data *wd = elm_widget_data_get(data);
506 if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
507 if (!strcmp(ev->keyname, "Up")) _val_inc_stop(data);
508 else if (!strcmp(ev->keyname, "Down")) _val_dec_stop(data);
509 else if (!strcmp(ev->keyname, "Escape")) _reset_value(data);
513 * Add a new spinner to the parent
515 * @param parent The parent object
516 * @return The new object or NULL if it cannot be created
521 elm_spinner_add(Evas_Object *parent)
527 wd = ELM_NEW(Widget_Data);
528 e = evas_object_evas_get(parent);
529 obj = elm_widget_add(e);
530 ELM_SET_WIDTYPE(widtype, "spinner");
531 elm_widget_type_set(obj, "spinner");
532 elm_widget_sub_object_add(parent, obj);
533 elm_widget_data_set(obj, wd);
534 elm_widget_del_hook_set(obj, _del_hook);
535 elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL);
536 elm_widget_theme_hook_set(obj, _theme_hook);
537 elm_widget_disable_hook_set(obj, _disable_hook);
538 elm_widget_signal_emit_hook_set(obj, _signal_emit_hook);
539 elm_widget_signal_callback_add_hook_set(obj, _signal_callback_add_hook);
540 elm_widget_signal_callback_del_hook_set(obj, _signal_callback_del_hook);
547 wd->first_interval = 0.85;
548 wd->entry_visible = 0;
549 wd->editable = EINA_TRUE;
551 wd->spinner = edje_object_add(e);
552 _elm_theme_object_set(obj, wd->spinner, "spinner", "base", "default");
553 elm_widget_resize_object_set(obj, wd->spinner);
554 edje_object_signal_callback_add(wd->spinner, "drag", "*", _drag, obj);
555 edje_object_signal_callback_add(wd->spinner, "drag,start", "*",
557 edje_object_signal_callback_add(wd->spinner, "drag,stop", "*",
559 edje_object_signal_callback_add(wd->spinner, "drag,step", "*",
561 edje_object_signal_callback_add(wd->spinner, "drag,page", "*",
564 edje_object_signal_callback_add(wd->spinner, "elm,action,increment,start",
565 "*", _button_inc_start, obj);
566 edje_object_signal_callback_add(wd->spinner, "elm,action,increment,stop",
567 "*", _button_inc_stop, obj);
568 edje_object_signal_callback_add(wd->spinner, "elm,action,decrement,start",
569 "*", _button_dec_start, obj);
570 edje_object_signal_callback_add(wd->spinner, "elm,action,decrement,stop",
571 "*", _button_dec_stop, obj);
572 edje_object_part_drag_value_set(wd->spinner, "elm.dragable.slider",
575 evas_object_event_callback_add(wd->spinner, EVAS_CALLBACK_KEY_DOWN,
576 _entry_event_key_down, obj);
577 evas_object_event_callback_add(wd->spinner, EVAS_CALLBACK_KEY_UP,
578 _entry_event_key_up, obj);
580 wd->ent = elm_entry_add(obj);
581 elm_entry_single_line_set(wd->ent, 1);
582 evas_object_smart_callback_add(wd->ent, "activated", _entry_activated, obj);
583 edje_object_part_swallow(wd->spinner, "elm.swallow.entry", wd->ent);
584 edje_object_signal_callback_add(wd->spinner, "elm,action,entry,toggle",
585 "*", _toggle_entry, obj);
593 * Set the format string of the label area
595 * If NULL, this sets the format to "%.0f". If not it sets the format
596 * string for the label text. The label text is provided a floating point
597 * value, so the label text can display up to 1 floating point value. Note that
598 * this is optional. Use a format string such as "%1.2f meters" for example.
600 * @param obj The spinner object
601 * @param fmt The format string for the label display
606 elm_spinner_label_format_set(Evas_Object *obj, const char *fmt)
608 ELM_CHECK_WIDTYPE(obj, widtype);
609 Widget_Data *wd = elm_widget_data_get(obj);
611 eina_stringshare_replace(&wd->label, fmt);
617 * Get the label format of the spinner
619 * @param obj The spinner object
620 * @return The text label format string in UTF-8
625 elm_spinner_label_format_get(const Evas_Object *obj)
627 ELM_CHECK_WIDTYPE(obj, widtype) NULL;
628 Widget_Data *wd = elm_widget_data_get(obj);
629 if (!wd) return NULL;
634 * Set the minimum and maximum values for the spinner
636 * Maximum must be greater than minimum.
638 * @param obj The spinner object
639 * @param min The minimum value
640 * @param max The maximum value
645 elm_spinner_min_max_set(Evas_Object *obj, double min, double max)
647 ELM_CHECK_WIDTYPE(obj, widtype);
648 Widget_Data *wd = elm_widget_data_get(obj);
650 if ((wd->val_min == min) && (wd->val_max == max)) return;
653 if (wd->val < wd->val_min) wd->val = wd->val_min;
654 if (wd->val > wd->val_max) wd->val = wd->val_max;
660 * Get the minimum and maximum values of the spinner
662 * @param obj The spinner object
663 * @param min The minimum value
664 * @param max The maximum value
669 elm_spinner_min_max_get(const Evas_Object *obj, double *min, double *max)
673 ELM_CHECK_WIDTYPE(obj, widtype);
674 Widget_Data *wd = elm_widget_data_get(obj);
676 if (min) *min = wd->val_min;
677 if (max) *max = wd->val_max;
681 * Set the step for the spinner
683 * @param obj The spinner object
684 * @param step The step value
689 elm_spinner_step_set(Evas_Object *obj, double step)
691 ELM_CHECK_WIDTYPE(obj, widtype);
692 Widget_Data *wd = elm_widget_data_get(obj);
698 * Get the step of the spinner
700 * @param obj The spinner object
701 * @return The step value
706 elm_spinner_step_get(const Evas_Object *obj)
708 ELM_CHECK_WIDTYPE(obj, widtype) 0.0;
709 Widget_Data *wd = elm_widget_data_get(obj);
714 * Set the value the spinner indicates
716 * @param obj The spinner object
717 * @param val The value (must be beween min and max for the spinner)
722 elm_spinner_value_set(Evas_Object *obj, double val)
724 ELM_CHECK_WIDTYPE(obj, widtype);
725 Widget_Data *wd = elm_widget_data_get(obj);
727 if (wd->val == val) return;
729 if (wd->val < wd->val_min) wd->val = wd->val_min;
730 if (wd->val > wd->val_max) wd->val = wd->val_max;
736 * Get the value the spinner has
738 * @param obj The spinner object
739 * @return The value of the spinner
744 elm_spinner_value_get(const Evas_Object *obj)
746 ELM_CHECK_WIDTYPE(obj, widtype) 0.0;
747 Widget_Data *wd = elm_widget_data_get(obj);
753 * Sets whether the spinner should wrap when it reaches its
754 * minimum/maximum value
756 * @param obj The spinner object
757 * @param wrap True if it should wrap, false otherwise
762 elm_spinner_wrap_set(Evas_Object *obj, Eina_Bool wrap)
764 ELM_CHECK_WIDTYPE(obj, widtype);
765 Widget_Data *wd = elm_widget_data_get(obj);
771 * Gets whether the spinner should wrap when it reaches its
772 * minimum/maximum value
774 * @param obj The spinner object
775 * @return Bool value of wrap option
776 * (0 = disabled, 1 = enabled)
781 elm_spinner_wrap_get(const Evas_Object *obj)
783 ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
784 Widget_Data *wd = elm_widget_data_get(obj);
785 if (!wd) return EINA_FALSE;
790 * Set a special value to display in the place of the numerical one.
792 * @param obj The spinner object
793 * @param value The value to be replaced
794 * @param label The label to be used
799 elm_spinner_special_value_add(Evas_Object *obj, double value, const char *label)
801 Elm_Spinner_Special_Value *sv;
802 ELM_CHECK_WIDTYPE(obj, widtype);
803 Widget_Data *wd = elm_widget_data_get(obj);
806 sv = calloc(1, sizeof(*sv));
809 sv->label = eina_stringshare_add(label);
811 wd->special_values = eina_list_append(wd->special_values, sv);
816 * Set whether the spinner can be directly edited by the user or not.
817 * Default is editable.
819 * @param obj The spinner object
820 * @param editable Bool value of the edit option
821 * (EINA_FALSE = not editable, EINA_TRUE = editable)
824 elm_spinner_editable_set(Evas_Object *obj, Eina_Bool editable)
826 ELM_CHECK_WIDTYPE(obj, widtype);
827 Widget_Data *wd = elm_widget_data_get(obj);
829 wd->editable = editable;
833 * Gets whether the spinner is editable.
835 * @param obj The spinner object
836 * @return Bool value of edit option
837 * (EINA_FALSE = not editable, EINA_TRUE = editable)
840 elm_spinner_editable_get(const Evas_Object *obj)
842 ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
843 Widget_Data *wd = elm_widget_data_get(obj);
844 if (!wd) return EINA_FALSE;
849 * Set the interval for the spinner
851 * @param obj The spinner object
852 * @param interval The interval value in seconds
854 * The interval value is decreased while the user increments or decrements
855 * the spinner value. The next interval value is the previous interval / 1.05,
856 * so it speed up a bit. Default value is 0.85 seconds.
861 elm_spinner_interval_set(Evas_Object *obj, double interval)
863 ELM_CHECK_WIDTYPE(obj, widtype);
864 Widget_Data *wd = elm_widget_data_get(obj);
866 wd->first_interval = interval;
870 * Get the interval of the spinner
872 * @param obj The spinner object
873 * @return The value of the first interval in seconds
875 * The interval value is decreased while the user increments or decrements
876 * the spinner value. The next interval value is the previous interval / 1.05,
877 * so it speed up a bit. Default value is 0.85 seconds.
882 elm_spinner_interval_get(const Evas_Object *obj)
884 ELM_CHECK_WIDTYPE(obj, widtype) 0.0;
885 Widget_Data *wd = elm_widget_data_get(obj);
887 return wd->first_interval;