/* * functions, callbacks and data types for widgets * * Copyright (c) 2015 Ultimaker B.V. * Author: Olliver Schinagl * * SPDX-License-Identifier: AGPL-3.0+ */ #include #include #include "eulogium.h" #include "gettext.h" #include "procedures.h" #include "ui_input.h" #include "ui_widgets.h" #include "ui_wizards.h" Elm_Object_Item *ui_stack_push(Evas_Object *navi, Evas_Object *content, const enum navi_page_state page_state) /* TODO replace with just the navi pointer */ { Elm_Object_Item *navi_item; if ((!navi) || (!content)) { EINA_LOG_CRIT("navi or content where NULL"); return NULL; } navi_item = elm_naviframe_item_simple_push(navi, content); if (!navi_item) { EINA_LOG_CRIT("Unable to push item onto navistack"); return NULL; } elm_object_item_data_set(navi_item, (void *)(uintptr_t)page_state); return navi_item; } void _event_handler_from_navi_del(const Evas_Object *navi, const char *str) { Ecore_Event_Handler *event; event = evas_object_data_get(navi, str); if (event) ecore_event_handler_del(event); } /* Search through the navistack in reverse order. This loop is almost always * aborted very early on. We basically 'abuse' the loop to skip over invalid * pages, which is actually unlikely and thus the first item of the stack * (the tail) gets popped immediately. Additionally there is a safeguard in * place to not pop the very first page of the stack, as that is our * 'main-menu'. TODO Improve doc, this is just an early note. */ Eina_Bool ui_stack_pop(const Evas_Object *navi) { Eina_List *navi_list, *l; Elm_Object_Item *navi_item; enum navi_page_state page_state; if (!navi) { EINA_LOG_CRIT("Naviframe was NULL"); return EINA_FALSE; } /* clean navi page event handlers */ _event_handler_from_navi_del(navi, "event_inc"); /* TODO possibly use an array of event handlers if we get more then 3 */ _event_handler_from_navi_del(navi, "event_dec"); /* TODO possibly use an array of event handlers if we get more then 3 */ /* FIXME: bug here, when we start eulogium and start a print job, * eulogium restarts midway the print job, the dialog with the * print cleaned pops up, but apparently no bottom has been generated * yet. Make sure to call eulogium_main_menu (new name) first before * process handling. Call moved, but we may still have init issues. */ navi_item = elm_naviframe_top_item_get(navi); page_state = (enum navi_page_state)(uintptr_t)elm_object_item_data_get(navi_item); if (page_state == PAGE_BOTTOM) { EINA_LOG_WARN("Not popping bottom frame cowboy"); return EINA_TRUE; } navi_list = elm_naviframe_items_get(navi); l = eina_list_last(navi_list); l = eina_list_prev(l); /* Skip the top entry, it is the currently displayed window and thus * always valid, we also know the last item is not the bottom of the * stack. Because we are looking 1 item ahead, if l becomes NULL, we * must be on the bottom page, which may never be invalid by * definition. */ /* XXX TODO: if page_state is PAGE_ERROR, we should treat it specially */ for (; l; l = eina_list_prev(l)) { navi_item = eina_list_data_get(l); if (!navi_item) { EINA_LOG_ERR("No item in list?!"); continue; } page_state = (enum navi_page_state)(uintptr_t)elm_object_item_data_get(navi_item); /* XXX toto, use pointer of _navi_page_state */ if (page_state == PAGE_INVALID) { EINA_LOG_WARN("Page on stack invalid, skipping"); continue; } /* All other page_states, PAGE_NORMAL, PAGE_PRINTING */ elm_naviframe_item_pop_to(navi_item); eina_list_free(navi_list); return EINA_TRUE; } eina_list_free(navi_list); return EINA_FALSE; } void ui_stack_page_invalidate(Evas_Object *navi, const enum navi_page_state page_state) { Eina_List *navi_list, *l; Elm_Object_Item *navi_item; enum navi_page_state page; navi_list = elm_naviframe_items_get(navi); EINA_LIST_FOREACH(navi_list, l, navi_item) { page = (enum navi_page_state)(uintptr_t)elm_object_item_data_get(navi_item); if (page == page_state) elm_object_item_data_set(navi_item, (void *)PAGE_INVALID); } eina_list_free(navi_list); } void ui_stack_pop_cb(void *data, Evas_Object *eo EINA_UNUSED, void *event_info EINA_UNUSED) { Evas_Object *navi = data; ui_stack_pop(navi); } static void _cb_cleaned_print_bed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { Evas_Object *navi = data; procedure_print_printer_cleaned(); ui_stack_pop(navi); } /* XXX move to ui_windows.c */ Evas_Object *ui_clean_print_bed(Evas_Object *navi) { Evas_Object *_top, *_bottom, *obj; _top = elm_box_add(navi); evas_object_show(_top); obj = elm_label_add(_top); elm_object_text_set(obj, _("Print finished!
Remove the print
for next
print job.")); evas_object_show(obj); elm_box_pack_end(_top, obj); _bottom = elm_button_add(navi); elm_object_text_set(_bottom, _("Print removed")); evas_object_smart_callback_add(_bottom, "clicked", _cb_cleaned_print_bed, navi); evas_object_show(_bottom); return eulogium_split_screen(navi, _top, _bottom); } static void _dial_send_update(void *data, Evas_Object *eo, void *event_info EINA_UNUSED) { struct settings_dial_data *dial_data = data; dial_data->value = elm_slider_value_get(eo); if (dial_data->method_set) dial_data->method_set(dial_data->proc_key); } static Eina_Bool _dial_change_delay(void *data) { Evas_Object *dial = data; evas_object_data_set(dial, "delay", NULL); evas_object_smart_callback_call(dial, "changed", NULL); return ECORE_CALLBACK_CANCEL; } static Eina_Bool _spinner_change(void *data, int type, void *event_info EINA_UNUSED) { struct settings_dial_data *dial_data = data; /* TODO we now just tell the backend that there is a change of + or - 1 * step size. * ideally the event_info is filled with the step size/speed of the mouse_wheel events * giving us the step size directly. */ //value = elm_slider_value_get(dial); //step = elm_slider_step_get(dial); if (type == INPUT_MOUSE_WHEEL_UP) dial_data->value = -1.0 * dial_data->step; if (type == INPUT_MOUSE_WHEEL_DOWN) dial_data->value = +1.0 * dial_data->step; if (dial_data->method_set) dial_data->method_set(dial_data->proc_key); return ECORE_CALLBACK_PASS_ON; } static Eina_Bool _dial_change(void *data, int type, void *event_info EINA_UNUSED) { Evas_Object *dial = data; Ecore_Timer *delay; double value, step; /* TODO XXX we call this dirty little hack because debian's v1.8 of elm does not offer us to actually call drag,left * evas_object_smart_callback_call(dial, "drag,left", NULL); would have been much cleaner and we didn't need to do * the val+step get/set thing and thus also benefit from delay,changed which we now 'fake' badly. Also a common user,changed * signal handler on the other end would help on making it less ugly. */ value = elm_slider_value_get(dial); step = elm_slider_step_get(dial); if (type == INPUT_MOUSE_WHEEL_UP) elm_slider_value_set(dial, value - step); if (type == INPUT_MOUSE_WHEEL_DOWN) elm_slider_value_set(dial, value + step); evas_object_smart_callback_call(dial, "changed", NULL); delay = evas_object_data_get(dial, "delay"); ecore_timer_del(delay); delay = ecore_timer_add(0.5, _dial_change_delay, dial); /* TODO make define for delay */ evas_object_data_set(dial, "delay", delay); return ECORE_CALLBACK_PASS_ON; } /* HACK for some reason elm_slider_units_format_function_set does not supply us with any other variables * other then the value. Doing anything fancy thus becomes impossible. We therefore create these local * pointers which we set whenever a dial screen is generated. This brings us to the big caveat. Right * now, we only support one single active dial (unit/format) because of this XXX */ static const char *__dial_units_format = NULL; static char *_dial_units_format(double val) { char *buf; uint_fast32_t buf_size; buf_size = DIAL_MAX_FORMAT_SIZE; buf = malloc(buf_size); snprintf(buf, buf_size, __dial_units_format, val); return buf; } static void _dial_units_format_free(char *buf) { free(buf); } static void _dial_units_end_object_text_set(Evas_Object *end, struct settings_dial_data *dial_data) { char *buf; buf = malloc(DIAL_MAX_FORMAT_SIZE * sizeof(char)); if (dial_data->value_end < 0) snprintf(buf, DIAL_MAX_FORMAT_SIZE, "%s", dial_data->unit); else snprintf(buf, DIAL_MAX_FORMAT_SIZE, dial_data->format_end, dial_data->value_end, dial_data->unit); elm_object_text_set(end, buf); free(buf); } static Eina_Bool _timer_dial_units_update(void *data) { Evas_Object *dial = data; struct settings_dial_data *dial_data; dial_data = evas_object_data_get(dial, "dial_data"); if (dial_data->proc_key > PROC_NONE) procedure_metadata_key_get(dial_data->proc_key); _dial_units_end_object_text_set(dial, dial_data); return ECORE_CALLBACK_RENEW; } static void _timer_del(void *data, Evas *e EINA_UNUSED, Evas_Object *eo EINA_UNUSED, void *event_info EINA_UNUSED) { Ecore_Timer *timer = data; if (timer) ecore_timer_del(timer); } Evas_Object *ui_widget_spinner(Evas_Object *parent, struct eulogium_data *eulogium, struct settings_dial_data *dial_data) { Evas_Object *_top, *_bottom; Ecore_Event_Handler *handler; _top = elm_label_add(parent); elm_object_text_set(_top, _(dial_data->label)); evas_object_size_hint_weight_set(_top, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_top, EVAS_HINT_FILL, EVAS_HINT_FILL); handler = ecore_event_handler_add(INPUT_MOUSE_WHEEL_UP, _spinner_change, dial_data); evas_object_data_set(parent, "event_inc", handler); handler = ecore_event_handler_add(INPUT_MOUSE_WHEEL_DOWN, _spinner_change, dial_data); evas_object_data_set(parent, "event_dec", handler); /* XXX do this better (via dial_data?) */ evas_object_show(_top); _bottom = elm_button_add(parent); elm_object_text_set(_bottom, _("Click when done")); /* TODO, make define for this text */ evas_object_size_hint_weight_set(_bottom, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_bottom, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(_bottom, "clicked", ui_stack_pop_cb, eulogium->navi); evas_object_show(_bottom); /* remove handler on window destroy? */ return eulogium_split_screen(parent, _top, _bottom); } Evas_Object *ui_widget_spinner_add(Evas_Object *parent, struct settings_dial_data *dial_data) { Evas_Object *obj, *_top, *_bottom; // Ecore_Event_Handler *handler; _top = elm_box_add(parent); evas_object_size_hint_weight_set(_top, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_top, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_show(_top); obj = elm_label_add(_top); /* TODO make label sit inverted at the top */ elm_object_text_set(obj, "test"); evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_show(obj); elm_box_pack_end(_top, obj); obj = elm_spinner_add(parent); evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL); //handler = ecore_event_handler_add(INPUT_MOUSE_WHEEL_UP, _spinner_change, dial_data); //evas_object_data_set(parent, "event_inc", handler); //handler = ecore_event_handler_add(INPUT_MOUSE_WHEEL_DOWN, _spinner_change, dial_data); //evas_object_data_set(parent, "event_dec", handler); /* XXX do this better (via dial_data?) */ evas_object_show(obj); elm_box_pack_end(_top, obj); _bottom = elm_button_add(parent); elm_object_text_set(_bottom, _("Click when done")); /* TODO, make define for this text */ evas_object_size_hint_weight_set(_bottom, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_bottom, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(_bottom, "clicked", ui_stack_pop_cb, parent); evas_object_show(_bottom); /* remove handler on window destroy? */ return eulogium_split_screen(parent, _top, _bottom); } Evas_Object *ui_widget_slider(Evas_Object *parent, struct eulogium_data *eulogium, struct settings_dial_data *dial_data) { Evas_Object *_top, *_bottom, *obj; Ecore_Timer *timer_value_end = NULL; Ecore_Event_Handler *handler; _top = elm_slider_add(parent); elm_object_text_set(_top, _(dial_data->label)); elm_object_focus_allow_set(_top, EINA_FALSE); __dial_units_format = dial_data->format; elm_slider_units_format_function_set(_top, _dial_units_format, _dial_units_format_free); elm_slider_min_max_set(_top, dial_data->min, dial_data->max); elm_slider_step_set(_top, dial_data->step); elm_slider_value_set(_top, dial_data->value); evas_object_size_hint_weight_set(_top, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_top, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(_top, "changed", _dial_send_update, dial_data); handler = ecore_event_handler_add(INPUT_MOUSE_WHEEL_UP, _dial_change, _top); evas_object_data_set(parent, "event_inc", handler); handler = ecore_event_handler_add(INPUT_MOUSE_WHEEL_DOWN, _dial_change, _top); evas_object_data_set(parent, "event_dec", handler); evas_object_show(_top); /* if type is tar/cur do something different TODO */ obj = elm_label_add(_top); evas_object_data_set(obj, "dial_data", dial_data); _dial_units_end_object_text_set(obj, dial_data); elm_object_part_content_set(_top, "end", obj); evas_object_show(obj); timer_value_end = ecore_timer_add(0.5, _timer_dial_units_update, obj); /* XXX replace with signal? */ /* XXX also we only want to pass the obj (dial) to the screen update timer */ evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _timer_del, timer_value_end); /* XXX TODO add box to put this in */ #if 0 obj = elm_label_add(_top); elm_object_text_set(obj, _(dial_data->help); evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_object_show(obj); elm_box_pack_end(_top, obj); #endif _bottom = elm_button_add(parent); elm_object_text_set(_bottom, _("Click when done")); /* TODO, make define for this text */ evas_object_size_hint_weight_set(_bottom, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_bottom, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_smart_callback_add(_bottom, "clicked", ui_stack_pop_cb, eulogium->navi); evas_object_show(_bottom); return eulogium_split_screen(parent, _top, _bottom); } static void _widget_progress_del(void *data, Evas *e EINA_UNUSED, Evas_Object *eo EINA_UNUSED, void *event_info EINA_UNUSED) { Ecore_Event_Handler *handler = data; if (handler) ecore_event_handler_del(handler); } #define SECOND 1L #define MINUTE (60L * SECOND) #define HOUR (60L * MINUTE) #define DAY (24L * HOUR) #define WEEK (7L * DAY) #define MONTH (4L * WEEK) #define YEAR (52L * WEEK) static Eina_Bool _widget_progress_status_update(void *data, int type EINA_UNUSED, void *ev) { Evas_Object *progress_status = data; const struct print_data *progress = ev; int_fast32_t time = 0; char buf[35], *str; /* 35 is a random approximation */ if (progress->time < SECOND) { str = N_("Time unknown"); } if (progress->time > SECOND) { str = N_("Time left: %d sec%s"); time = progress->time; } if (progress->time > MINUTE) { str = N_("Time left: %d min%s"); time = progress->time / MINUTE; } if (progress->time > HOUR) { str = N_("Time left: %d hour%s"); time = progress->time / HOUR; } if (progress->time > DAY) { str = N_("Time left: %d day%s"); time = progress->time / DAY; } if (progress->time > WEEK) { str = N_("Time left: %d week%s"); time = progress->time / WEEK; } if (progress->time > MONTH) { str = N_("Time left: %d month%s"); time = progress->time / MONTH; } if (progress->time > YEAR) { str = N_("Time left: %d year%s"); time = progress->time / YEAR; } snprintf(buf, sizeof(buf), str, (int)time, (time > 1) ? "s" : ""); /* TODO: check for real i10n time funcs */ elm_object_text_set(progress_status, _(buf)); return ECORE_CALLBACK_PASS_ON; } Evas_Object *widgets_progress_status_add(Evas_Object *parent, const struct print_data *progress, const struct event_info *event) { Evas_Object *progress_status; Ecore_Event_Handler *handler; progress_status = elm_label_add(parent); elm_object_text_set(progress_status, _("Unknown status")); elm_label_slide_mode_set(progress_status, ELM_LABEL_SLIDE_MODE_NONE); evas_object_size_hint_align_set(progress_status, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_show(progress_status); _widget_progress_status_update((void *)progress_status, event->type, (void *)progress); handler = ecore_event_handler_add(event->type, _widget_progress_status_update, progress_status); evas_object_event_callback_add(progress_status, EVAS_CALLBACK_DEL, _widget_progress_del, handler); evas_object_show(progress_status); return progress_status; } static Eina_Bool _widget_progress_bar_update(void *data, int type EINA_UNUSED, void *ev) { Evas_Object *progress_bar = data; const struct print_data *progress = ev; if (progress->value < 0) { elm_progressbar_pulse_set(progress_bar, EINA_TRUE); elm_progressbar_pulse(progress_bar, EINA_TRUE); } else { elm_progressbar_pulse_set(progress_bar, EINA_FALSE); elm_progressbar_pulse(progress_bar, EINA_FALSE); elm_progressbar_value_set(progress_bar, progress->value); } return ECORE_CALLBACK_PASS_ON; } Evas_Object *widgets_progress_bar_add(Evas_Object *parent, const struct print_data *progress, const struct event_info *event) { Evas_Object *progress_bar; Ecore_Event_Handler *handler; progress_bar = elm_progressbar_add(parent); elm_progressbar_horizontal_set(progress_bar, EINA_TRUE); elm_progressbar_unit_format_set(progress_bar, progress->format); evas_object_size_hint_align_set(progress_bar, EVAS_HINT_FILL, EVAS_HINT_FILL); _widget_progress_bar_update((void *)progress_bar, event->type, (void *)progress); handler = ecore_event_handler_add(event->type, _widget_progress_bar_update, progress_bar); evas_object_event_callback_add(progress_bar, EVAS_CALLBACK_DEL, _widget_progress_del, handler); evas_object_show(progress_bar); return progress_bar; } Evas_Object *ui_widget_progress(Evas_Object *parent, const struct print_data *progress, const struct event_info *event) { Evas_Object *_top, *_bottom, *content, *obj; _top = elm_box_add(parent); evas_object_size_hint_weight_set(_top, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(_top, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_homogeneous_set(_top, EINA_FALSE); evas_object_show(_top); obj = elm_label_add(_top); elm_object_style_set(obj, "title"); elm_object_text_set(obj, _(progress->title)); /* TODO move title outside of the ui element when splitting widgets further */ elm_label_slide_mode_set(obj, ELM_LABEL_SLIDE_MODE_NONE); evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_show(obj); elm_box_pack_end(_top, obj); obj = widgets_progress_status_add(_top, progress, event); elm_box_pack_end(_top, obj); obj = elm_label_add(_top); elm_object_text_set(obj, progress->jobname); elm_label_slide_mode_set(obj, ELM_LABEL_SLIDE_MODE_AUTO); //elm_label_slide_speed_set(object, 2); elm_object_style_set(obj, "slide_bounce"); elm_label_slide_go(obj); /* XXX only slide when strlen > 20? as a workaround to this not working automatically. */ evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL); evas_object_show(obj); elm_box_pack_end(_top, obj); obj = widgets_progress_bar_add(_top, progress, event); elm_box_pack_end(_top, obj); _bottom = eulogium_dual_button_add(parent, &but_print_tune, &but_print_abort_confirm); evas_object_show(_bottom); content = eulogium_split_screen(parent, _top, _bottom); return content; } void ui_init(void) { wizard_init_all(); /* XXX this does not belong here! init from main/init */ }