/** * File: combo_box.h * Author: AWTK Develop Team * Brief: combo_box * * Copyright (c) 2018 - 2023 Guangzhou ZHIYUAN Electronics Co.,Ltd. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * License file for more details. * */ /** * History: * ================================================================ * 2018-07-25 Li XianJing created * */ #include "tkc/mem.h" #include "tkc/utils.h" #include "base/layout.h" #include "base/window.h" #include "base/window_manager.h" #include "widgets/popup.h" #include "tkc/tokenizer.h" #include "widgets/button.h" #include "widgets/combo_box.h" #include "widgets/combo_box_item.h" #define COMBO_BOX_DEFAULT_MARGIN 1 static ret_t combo_box_on_button_click(void* ctx, event_t* e); static ret_t combo_box_sync_index_to_value(widget_t* widget, uint32_t index, bool_t only_set); const char* const s_combo_box_properties[] = {TK_EDIT_PROPS, WIDGET_PROP_OPTIONS, WIDGET_PROP_ITEM_HEIGHT, WIDGET_PROP_OPEN_WINDOW, WIDGET_PROP_SELECTED_INDEX, WIDGET_PROP_THEME_OF_POPUP, WIDGET_PROP_LOCALIZE_OPTIONS, NULL}; static ret_t combo_box_active(widget_t* widget); static ret_t combo_box_add_selected_index(widget_t* widget, int32_t delta); static widget_t* combo_box_create_self(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h); static ret_t combo_box_set_selected_index_ex(widget_t* widget, uint32_t index, widget_t* item); static ret_t combo_box_on_copy(widget_t* widget, widget_t* other) { combo_box_t* combo_box = COMBO_BOX(widget); combo_box_t* combo_box_other = COMBO_BOX(other); return_value_if_fail(combo_box != NULL && combo_box_other != NULL, RET_BAD_PARAMS); edit_on_copy(widget, other); combo_box->item_height = combo_box_other->item_height; combo_box->selected_index = combo_box_other->selected_index; combo_box->localize_options = combo_box_other->localize_options; if (combo_box_other->options != NULL) { combo_box_set_options(widget, combo_box_other->options); } if (combo_box_other->open_window != NULL) { combo_box->open_window = tk_str_copy(combo_box->open_window, combo_box_other->open_window); } if (combo_box_other->theme_of_popup != NULL) { combo_box->theme_of_popup = tk_str_copy(combo_box->theme_of_popup, combo_box_other->theme_of_popup); } combo_box->open_popup = combo_box_other->open_popup; combo_box->on_item_click = combo_box_other->on_item_click; combo_box->on_item_click_ctx = combo_box_other->on_item_click_ctx; combo_box->on_layout_combobox_popup = combo_box_other->on_layout_combobox_popup; return RET_OK; } static ret_t combo_box_on_destroy(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(widget != NULL && combo_box != NULL, RET_BAD_PARAMS); if (combo_box->init_popup_button_idle_id != TK_INVALID_ID) { idle_remove(combo_box->init_popup_button_idle_id); } str_reset(&(combo_box->text)); combo_box_reset_options(widget); TKMEM_FREE(combo_box->open_window); TKMEM_FREE(combo_box->theme_of_popup); edit_on_destroy(widget); if (combo_box->combobox_popup != NULL) { widget_destroy(combo_box->combobox_popup); } return RET_OK; } #define WIDGET_NAME_VALUE "value" static uint32_t combo_box_get_curr_text_size(widget_t* widget) { return_value_if_fail(widget != NULL, 0); widget_t* value_widget = widget_lookup(widget, WIDGET_NAME_VALUE, TRUE); if (value_widget == NULL) { value_widget = widget; } return value_widget->text.size; } static ret_t combo_box_update_status(widget_t* widget) { edit_t* edit = EDIT(widget); if (combo_box_get_curr_text_size(widget) == 0) { if (widget->focused) { widget_set_state(widget, WIDGET_STATE_EMPTY_FOCUS); } else { widget_set_state(widget, WIDGET_STATE_EMPTY); } } else { if (edit != NULL && edit->cancelable) { if (!widget->loading) { if (!wstr_equal(&(edit->saved_text), &(widget->text))) { widget_set_state(widget, WIDGET_STATE_CHANGED); return RET_OK; } } } if (widget->focused) { widget_set_state(widget, WIDGET_STATE_FOCUSED); } else { widget_set_state(widget, WIDGET_STATE_NORMAL); } } return RET_OK; } static ret_t combo_box_set_text_only(widget_t* widget, const char* text, const wchar_t* wtext, bool_t tr) { widget_t* value_widget = widget_lookup(widget, WIDGET_NAME_VALUE, TRUE); if (value_widget == NULL) { value_widget = widget; } else { TKMEM_FREE(widget->tr_text); wstr_clear(&(widget->text)); combo_box_update_status(value_widget); } if (tr) { widget_t* win = widget_get_window(value_widget); if (win != NULL) { text = locale_info_tr(widget_get_locale_info(widget), text); } wstr_set_utf8(&(value_widget->text), text); } else { if (wtext != NULL) { wstr_set(&(value_widget->text), wtext); } else { wstr_set_utf8(&(value_widget->text), text); } } combo_box_update_status(value_widget); return RET_OK; } static ret_t combo_box_set_text(widget_t* widget, const char* text, const wchar_t* wtext, bool_t tr) { widget_t* value_widget = widget_lookup(widget, WIDGET_NAME_VALUE, TRUE); if (value_widget == NULL) { value_widget = widget; } else { TKMEM_FREE(widget->tr_text); widget_set_text(widget, L""); } if (tr) { widget_set_tr_text(value_widget, text); } else { if (wtext != NULL) { widget_set_text(value_widget, wtext); } else { widget_set_text_utf8(value_widget, text); } } return RET_OK; } static ret_t combo_box_get_prop(widget_t* widget, const char* name, value_t* v) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(widget != NULL && combo_box != NULL, RET_BAD_PARAMS); if (tk_str_eq(name, WIDGET_PROP_OPEN_WINDOW)) { value_set_str(v, combo_box->open_window); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_THEME_OF_POPUP)) { value_set_str(v, combo_box->theme_of_popup); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_SELECTED_INDEX)) { value_set_int(v, combo_box->selected_index); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_VALUE)) { value_set_int(v, combo_box->value); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_LOCALIZE_OPTIONS)) { value_set_bool(v, combo_box->localize_options); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_OPTIONS)) { value_set_str(v, combo_box->options); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_ITEM_HEIGHT)) { value_set_int(v, combo_box->item_height); return RET_OK; } else { return edit_get_prop(widget, name, v); } } ret_t combo_box_parse_options(widget_t* widget, const char* str) { str_t s; int32_t i = 0; tokenizer_t tokenizer; tokenizer_t* t = &tokenizer; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL && str != NULL, RET_BAD_PARAMS); return_value_if_fail(str_init(&s, 100) != NULL, RET_OOM); combo_box_reset_options(widget); combo_box->options = tk_strdup(str); tokenizer_init(t, str, strlen(str), ";"); while (tokenizer_has_more(t)) { int32_t value = 0; const char* token = tokenizer_next(t); if (*token) { const char* text = strchr(token, ':'); if (text != NULL) { text++; value = tk_atoi(token); } else { text = token; value = i; } str_set(&s, text); str_unescape(&s); combo_box_append_option(widget, value, s.str); i++; } } str_reset(&s); tokenizer_deinit(t); return RET_OK; } ret_t combo_box_set_options(widget_t* widget, const char* options) { ret_t ret = combo_box_parse_options(widget, options); if (!widget->loading) { ret = combo_box_sync_index_to_value(widget, 0, FALSE); } return ret; } static ret_t combo_box_text_to_index(widget_t* widget, const char* text) { ret_t ret = RET_FAIL; combo_box_t* combo_box = COMBO_BOX(widget); combo_box_option_t* option = NULL; int32_t i = 0; return_value_if_fail(widget != NULL && combo_box != NULL, RET_BAD_PARAMS); option = combo_box_get_option(widget, combo_box->selected_index); if (option != NULL && tk_str_eq(text, option->text)) { return RET_OK; } option = combo_box->option_items; for (i = 0; option != NULL; option = option->next, i++) { if (tk_str_eq(text, option->text)) { combo_box->selected_index = i; ret = RET_OK; break; } } return ret; } static ret_t combo_box_set_prop(widget_t* widget, const char* name, const value_t* v) { if (tk_str_eq(name, WIDGET_PROP_OPEN_WINDOW)) { combo_box_set_open_window(widget, value_str(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_THEME_OF_POPUP)) { combo_box_set_theme_of_popup(widget, value_str(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_SELECTED_INDEX)) { combo_box_set_selected_index(widget, value_int(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_VALUE)) { combo_box_set_value(widget, value_int(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_LOCALIZE_OPTIONS)) { combo_box_set_localize_options(widget, value_bool(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_OPTIONS)) { combo_box_set_options(widget, value_str(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_ITEM_HEIGHT)) { combo_box_set_item_height(widget, value_uint32(v)); return RET_OK; } else if (tk_str_eq(name, WIDGET_PROP_TEXT)) { str_t str; str_init(&str, 0); edit_set_prop(widget, WIDGET_PROP_TEXT, v); str_from_wstr(&str, widget_get_text(widget)); combo_box_text_to_index(widget, str.str); str_reset(&str); return RET_OK; } else { return edit_set_prop(widget, name, v); } } static ret_t combo_box_on_layout_children_for_combobox_popup(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); if (combo_box->combobox_popup != NULL) { point_t p = {0, 0}; int32_t margin = COMBO_BOX_DEFAULT_MARGIN; int32_t item_height = combo_box->item_height; int32_t nr = combo_box_count_options(widget); int32_t h = nr * item_height + 2 * margin; if (combo_box->open_window != NULL) { h = combo_box->combobox_popup->h; } combo_box_combobox_popup_calc_position(widget, widget->w, h, &p); widget_move_resize(combo_box->combobox_popup, p.x, p.y, widget->w, h); } return RET_OK; } static ret_t combo_box_on_layout_children(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); widget_t* button = widget_lookup_by_type(widget, WIDGET_TYPE_BUTTON, TRUE); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); widget_layout_children_default(widget); if (button != NULL) { if (button->auto_created) { widget_move_resize(button, widget->w - widget->h, 0, widget->h, widget->h); } else { widget_layout(button); } } if (combo_box->on_layout_combobox_popup) { combo_box->on_layout_combobox_popup(widget); } else { combo_box_on_layout_children_for_combobox_popup(widget); } return RET_OK; } static uint32_t edit_get_right_margin(widget_t* widget) { int32_t right_margin = 0; style_t* style = widget != NULL ? widget->astyle : NULL; right_margin = widget_get_prop_int(widget, WIDGET_PROP_RIGHT_MARGIN, 0); if (right_margin == 0) { right_margin = style_get_int(style, STYLE_ID_MARGIN_RIGHT, 0); } if (right_margin == 0) { right_margin = style_get_int(style, STYLE_ID_MARGIN, 0); } return right_margin; } static ret_t combo_box_on_key_event(widget_t* widget, key_event_t* evt) { ret_t ret = RET_OK; edit_t* edit = EDIT(WIDGET(widget)); return_value_if_fail(edit != NULL, RET_BAD_PARAMS); if (evt->key == TK_KEY_UP) { ret = RET_STOP; if (evt->e.type == EVT_KEY_DOWN) { combo_box_add_selected_index(widget, -1); } } else if (evt->key == TK_KEY_DOWN) { ret = RET_STOP; if (evt->e.type == EVT_KEY_DOWN) { combo_box_add_selected_index(widget, 1); } } else if (widget_is_activate_key(widget, evt) && evt->e.type == EVT_KEY_UP) { if (edit->readonly) { ret = RET_STOP; combo_box_active(widget); } else if (key_code_is_enter(evt->key)) { ret = RET_STOP; combo_box_active(widget); } } return ret; } static ret_t combo_box_init_popup_button(widget_t* widget) { widget_t* popup = button_create(widget, 0, 0, 0, 0); popup->auto_created = TRUE; widget_use_style(popup, "combobox_down"); widget_invalidate_force(widget, NULL); return RET_OK; } static ret_t combo_box_on_event(widget_t* widget, event_t* e) { ret_t ret = RET_OK; combo_box_t* combo_box = COMBO_BOX(widget); edit_t* edit = EDIT(WIDGET(combo_box)); return_value_if_fail(combo_box != NULL && edit != NULL && e != NULL, RET_BAD_PARAMS); switch (e->type) { case EVT_WIDGET_LOAD: { /*If there is a value widget, sync the text to value widget*/ if (widget_lookup(widget, WIDGET_NAME_VALUE, TRUE) != NULL) { combo_box_sync_index_to_value(widget, combo_box->selected_index, FALSE); } if (combo_box_get_curr_text_size(widget) == 0 && widget->tr_text == NULL) { combo_box_sync_index_to_value(widget, 0, TRUE); } break; } case EVT_RESIZE: case EVT_MOVE_RESIZE: { if (edit_get_right_margin(widget) == 0) { edit->right_margin = widget->h; } break; } case EVT_KEY_DOWN: case EVT_KEY_UP: { key_event_t* evt = (key_event_t*)e; if (combo_box_on_key_event(widget, evt) == RET_STOP) { return RET_STOP; } break; } case EVT_POINTER_DOWN: { combo_box->pressed = TRUE; break; } case EVT_POINTER_DOWN_ABORT: { combo_box->pressed = FALSE; break; } case EVT_POINTER_UP: { if (widget->target == NULL && edit->readonly && combo_box->pressed) { combo_box_active(widget); return RET_STOP; } combo_box->pressed = FALSE; break; } case EVT_POINTER_LEAVE: return combo_box_update_status(widget); break; case EVT_POINTER_ENTER: if (combo_box_get_curr_text_size(widget) == 0) { widget_set_state(widget, WIDGET_STATE_EMPTY_OVER); } else { widget_set_state(widget, WIDGET_STATE_OVER); } return RET_OK; break; case EVT_FOCUS: combo_box_update_status(widget); break; case EVT_BLUR: combo_box_update_status(widget); break; default: break; } ret = edit_on_event(widget, e); return ret; } static ret_t combo_box_on_add_child(widget_t* widget, widget_t* child) { if (tk_str_eq(widget_get_type(child), WIDGET_TYPE_BUTTON)) { widget_t* button = widget_lookup_by_type(widget, WIDGET_TYPE_BUTTON, TRUE); if (button != NULL && button != child) { widget_destroy(button); } widget_on(child, EVT_CLICK, combo_box_on_button_click, widget); } return RET_FAIL; } static ret_t combo_box_on_idle_init_popup_button(const idle_info_t* idle) { widget_t* widget = NULL; combo_box_t* combo_box = NULL; return_value_if_fail(idle != NULL, RET_BAD_PARAMS); widget = WIDGET(idle->ctx); combo_box = COMBO_BOX(widget); if (!widget->destroying) { widget_t* button = widget_lookup_by_type(widget, WIDGET_TYPE_BUTTON, TRUE); if (button == NULL) { combo_box_init_popup_button(widget); } } combo_box->init_popup_button_idle_id = TK_INVALID_ID; return RET_OK; } static ret_t combo_box_idle_init_popup_button(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); if (combo_box->init_popup_button_idle_id == TK_INVALID_ID) { combo_box->init_popup_button_idle_id = idle_add(combo_box_on_idle_init_popup_button, widget); } return RET_OK; } static ret_t combo_box_on_remove_child(widget_t* widget, widget_t* child) { if (!widget->destroying) { if (tk_str_eq(widget_get_type(child), WIDGET_TYPE_BUTTON)) { widget_t* button = widget_lookup_by_type(widget, WIDGET_TYPE_BUTTON, TRUE); if (button == child) { widget_off_by_ctx(child, widget); combo_box_idle_init_popup_button(widget); } } } return RET_FAIL; } TK_DECL_VTABLE(combo_box) = {.size = sizeof(combo_box_t), .inputable = TRUE, .type = WIDGET_TYPE_COMBO_BOX, .focusable = TRUE, .pointer_cursor = WIDGET_CURSOR_EDIT, .space_key_to_activate = TRUE, .return_key_to_activate = TRUE, .clone_properties = s_combo_box_properties, .persistent_properties = s_combo_box_properties, .get_parent_vt = TK_GET_PARENT_VTABLE(edit), .create = combo_box_create_self, .on_paint_self = edit_on_paint_self, .set_prop = combo_box_set_prop, .get_prop = combo_box_get_prop, .on_add_child = combo_box_on_add_child, .on_remove_child = combo_box_on_remove_child, .on_layout_children = combo_box_on_layout_children, .on_destroy = combo_box_on_destroy, .on_copy = combo_box_on_copy, .on_event = combo_box_on_event}; widget_t* combo_box_create_self(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h) { widget_t* widget = edit_create_ex(parent, TK_REF_VTABLE(combo_box), x, y, w, h); combo_box_t* combo_box = COMBO_BOX(widget); edit_t* edit = EDIT(WIDGET(combo_box)); return_value_if_fail(combo_box != NULL && edit != NULL, NULL); widget_set_prop_int(widget, WIDGET_PROP_MARGIN, 0); widget_set_prop_int(widget, WIDGET_PROP_LEFT_MARGIN, 0); widget_set_prop_int(widget, WIDGET_PROP_RIGHT_MARGIN, 0); str_init(&(combo_box->text), 32); combo_box->localize_options = TRUE; return widget; } ret_t combo_box_set_on_item_click(widget_t* widget, event_func_t on_item_click, void* ctx) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); combo_box->on_item_click = on_item_click; combo_box->on_item_click_ctx = ctx; return RET_OK; } static ret_t combo_box_on_item_click(void* ctx, event_t* e) { widget_t* widget = WIDGET(ctx); widget_t* item = WIDGET(e->target); combo_box_t* combo_box = COMBO_BOX(ctx); return_value_if_fail(widget != NULL && item != NULL, RET_BAD_PARAMS); if (combo_box->on_item_click != NULL) { if (combo_box->on_item_click(combo_box->on_item_click_ctx, e) == RET_OK) { return RET_OK; } } combo_box_set_selected_index_ex(widget, widget_index_of(item), item); widget->target = NULL; widget->key_target = NULL; window_close(widget_get_window(item)); widget_set_focused_internal(widget, TRUE); return RET_OK; } static ret_t combo_box_visit_item(void* ctx, const void* data) { widget_t* iter = WIDGET(data); combo_box_t* combo_box = COMBO_BOX(ctx); return_value_if_fail(combo_box != NULL && iter != NULL, RET_BAD_PARAMS); if (tk_str_eq(widget_get_type(iter), WIDGET_TYPE_COMBO_BOX_ITEM)) { int32_t index = widget_index_of(iter); widget_on(iter, EVT_CLICK, combo_box_on_item_click, combo_box); if (index == combo_box->selected_index) { COMBO_BOX_ITEM(iter)->checked = TRUE; widget_set_need_update_style(iter); widget_set_focused(iter, TRUE); } } return RET_OK; } static ret_t combo_box_hook_items(combo_box_t* combo_box, widget_t* popup) { return widget_foreach(popup, combo_box_visit_item, combo_box); } static ret_t combo_box_create_popup_items(combo_box_t* combo_box, widget_t* parent) { combo_box_option_t* iter = NULL; return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); iter = combo_box->option_items; while (iter != NULL) { widget_t* item = combo_box_item_create(parent, 0, 0, 0, 0); widget_set_value(item, iter->value); if (combo_box->localize_options) { widget_set_tr_text(item, iter->text); } else { widget_set_text_utf8(item, iter->text); } iter = iter->next; } return RET_OK; } ret_t combo_box_combobox_popup_on_close_func(void* ctx, event_t* e) { combo_box_t* combo_box = COMBO_BOX(ctx); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); combo_box->combobox_popup = NULL; return RET_OK; } ret_t combo_box_combobox_popup_calc_position(widget_t* widget, wh_t popup_w, wh_t popup_h, point_t* p) { widget_t* wm = window_manager(); combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL && p != NULL, RET_BAD_PARAMS); memset(p, 0x00, sizeof(point_t)); widget_to_screen(widget, p); if ((p->y + widget->h + popup_h) < wm->h) { p->y += widget->h; } else if (p->y >= popup_h) { p->y -= combo_box->combobox_popup->h; } else { p->y = 0; } return RET_OK; } static widget_t* combo_box_create_popup(combo_box_t* combo_box) { value_t v; int32_t w = 0; int32_t h = 0; int32_t nr = 0; char params[128]; widget_t* win = NULL; int32_t item_height = 0; widget_t* combo_box_win = NULL; const char* applet_name = NULL; widget_t* widget = WIDGET(combo_box); int32_t margin = COMBO_BOX_DEFAULT_MARGIN; return_value_if_fail(combo_box != NULL && widget != NULL, NULL); item_height = combo_box->item_height; nr = combo_box_count_options(widget); w = widget->w; h = nr * item_height + 2 * margin; win = popup_create(NULL, 0, 0, w, h); combo_box_win = widget_get_window(widget); applet_name = widget_get_prop_str(combo_box_win, WIDGET_PROP_APPLET_NAME, NULL); widget_set_prop_str(win, WIDGET_PROP_APPLET_NAME, applet_name); value_set_bool(&v, TRUE); widget_set_prop(win, WIDGET_PROP_CLOSE_WHEN_CLICK_OUTSIDE, &v); widget_set_prop_str(win, WIDGET_PROP_THEME, "combobox_popup"); tk_snprintf(params, sizeof(params) - 1, "default(m=%d, r=%d c=%d)", margin, nr, 1); widget_set_children_layout(win, params); widget_use_style(win, "combobox_popup"); combo_box_create_popup_items(combo_box, win); widget_layout(win); combo_box->combobox_popup = win; widget_on(win, EVT_WINDOW_CLOSE, combo_box_combobox_popup_on_close_func, widget); return win; } static ret_t combo_box_active(widget_t* widget) { point_t p = {0, 0}; widget_t* win = NULL; widget_t* combo_box_win = NULL; const char* applet_name = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(widget != NULL && combo_box != NULL, RET_BAD_PARAMS); if (combo_box->open_window) { win = window_open(combo_box->open_window); combo_box_win = widget_get_window(widget); applet_name = widget_get_prop_str(combo_box_win, WIDGET_PROP_APPLET_NAME, NULL); widget_set_prop_str(win, WIDGET_PROP_APPLET_NAME, applet_name); combo_box->combobox_popup = win; widget_on(win, EVT_WINDOW_CLOSE, combo_box_combobox_popup_on_close_func, widget); } if (win != NULL) { widget_resize(win, widget->w, win->h); widget_layout_children(win); } else { if (combo_box->open_popup) { win = combo_box->open_popup(widget); return_value_if_fail(win != NULL, RET_FAIL); widget_resize(win, tk_max_int(win->w, widget->w), win->h); widget_layout_children(win); } else { win = combo_box_create_popup(combo_box); return_value_if_fail(win != NULL, RET_FAIL); } } if (combo_box->theme_of_popup != NULL) { widget_set_prop_str(win, WIDGET_PROP_THEME, combo_box->theme_of_popup); } widget_set_prop_str(win, WIDGET_PROP_MOVE_FOCUS_PREV_KEY, "up"); widget_set_prop_str(win, WIDGET_PROP_MOVE_FOCUS_NEXT_KEY, "down"); combo_box_hook_items(combo_box, win); combo_box_combobox_popup_calc_position(widget, win->w, win->h, &p); widget_move(win, p.x, p.y); return RET_OK; } static ret_t combo_box_on_button_click(void* ctx, event_t* e) { return combo_box_active(WIDGET(ctx)); } widget_t* combo_box_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h) { widget_t* widget = combo_box_create_self(parent, x, y, w, h); return_value_if_fail(widget != NULL, NULL); combo_box_init_popup_button(widget); combo_box_set_item_height(widget, 30); return widget; } ret_t combo_box_remove_option_by_index(widget_t* widget, uint32_t index) { uint32_t i = 0; combo_box_option_t* iter = NULL; combo_box_option_t* prev = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); iter = combo_box->option_items; prev = combo_box->option_items; while (iter != NULL) { if (i == index) { if (iter == combo_box->option_items) { combo_box->option_items = iter->next; } else { prev->next = iter->next; } TKMEM_FREE(iter->text); TKMEM_FREE(iter); return RET_OK; } i++; prev = iter; iter = iter->next; } return RET_NOT_FOUND; } ret_t combo_box_remove_option(widget_t* widget, int32_t value) { combo_box_option_t* iter = NULL; combo_box_option_t* prev = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); iter = combo_box->option_items; prev = combo_box->option_items; while (iter != NULL) { if (iter->value == value) { if (iter == combo_box->option_items) { combo_box->option_items = iter->next; } else { prev->next = iter->next; } TKMEM_FREE(iter->text); TKMEM_FREE(iter); return RET_OK; } prev = iter; iter = iter->next; } return RET_NOT_FOUND; } ret_t combo_box_reset_options(widget_t* widget) { combo_box_option_t* iter = NULL; combo_box_option_t* next = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); iter = combo_box->option_items; while (iter != NULL) { next = iter->next; TKMEM_FREE(iter->text); TKMEM_FREE(iter); iter = next; } combo_box->option_items = NULL; TKMEM_FREE(combo_box->options); return RET_OK; } ret_t combo_box_set_open_window(widget_t* widget, const char* open_window) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(widget != NULL, RET_BAD_PARAMS); TKMEM_FREE(combo_box->open_window); combo_box->open_window = tk_strdup(open_window); return RET_OK; } ret_t combo_box_set_theme_of_popup(widget_t* widget, const char* theme_of_popup) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(widget != NULL, RET_BAD_PARAMS); TKMEM_FREE(combo_box->theme_of_popup); combo_box->theme_of_popup = tk_strdup(theme_of_popup); return RET_OK; } ret_t combo_box_append_option(widget_t* widget, int32_t value, const char* text) { combo_box_option_t* iter = NULL; combo_box_option_t* option = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL && text != NULL, RET_BAD_PARAMS); option = TKMEM_ZALLOC(combo_box_option_t); return_value_if_fail(option != NULL, RET_OOM); option->value = value; option->text = tk_strdup(text); if (option->text == NULL) { TKMEM_FREE(option); return RET_OOM; } if (combo_box->option_items != NULL) { iter = combo_box->option_items; while (iter->next != NULL) iter = iter->next; iter->next = option; } else { combo_box->option_items = option; } return RET_OK; } int32_t combo_box_count_options(widget_t* widget) { int32_t nr = 0; combo_box_option_t* iter = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, nr); iter = combo_box->option_items; while (iter != NULL) { nr++; iter = iter->next; } return nr; } combo_box_option_t* combo_box_get_option(widget_t* widget, uint32_t index) { uint32_t i = 0; combo_box_option_t* iter = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, NULL); iter = combo_box->option_items; while (iter != NULL) { if (i == index) { return iter; } i++; iter = iter->next; } return NULL; } bool_t combo_box_has_option_text(widget_t* widget, const char* text) { combo_box_option_t* iter = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, FALSE); iter = combo_box->option_items; while (iter != NULL) { if (tk_str_eq(iter->text, text)) { return TRUE; } iter = iter->next; } return FALSE; } int32_t combo_box_find_option(widget_t* widget, int32_t value) { uint32_t i = 0; combo_box_option_t* iter = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, 0); iter = combo_box->option_items; while (iter != NULL) { if (iter->value == value) { return i; } i++; iter = iter->next; } return 0; } static int32_t combo_box_find_option_of_text(widget_t* widget, const char* text) { uint32_t i = 0; combo_box_option_t* iter = NULL; combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, 0); iter = combo_box->option_items; while (iter != NULL) { if (tk_str_eq(iter->text, text)) { return i; } i++; iter = iter->next; } return 0; } static ret_t combo_box_sync_index_to_value(widget_t* widget, uint32_t index, bool_t only_set) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(widget != NULL && combo_box != NULL, RET_BAD_PARAMS); if (combo_box->option_items != NULL) { combo_box_option_t* option = combo_box_get_option(widget, index); if (option != NULL) { combo_box->value = option->value; if (only_set) { combo_box_set_text_only(widget, option->text, NULL, combo_box->localize_options); } else { combo_box_set_text(widget, option->text, NULL, combo_box->localize_options); } } } return RET_OK; } static ret_t combo_box_set_selected_index_ex(widget_t* widget, uint32_t index, widget_t* item) { edit_t* edit = EDIT(widget); combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_OK); if (combo_box->selected_index != index) { value_change_event_t evt; value_change_event_init(&evt, EVT_VALUE_WILL_CHANGE, widget); value_set_uint32(&(evt.old_value), combo_box->selected_index); value_set_uint32(&(evt.new_value), index); if (widget_dispatch(widget, (event_t*)&evt) != RET_STOP) { if (widget->emitter != NULL) { emitter_disable(widget->emitter); } combo_box->selected_index = index; if (item != NULL) { combo_box->value = COMBO_BOX_ITEM(item)->value; if (item->tr_text != NULL) { combo_box_set_text(widget, item->tr_text, NULL, TRUE); } else { combo_box_set_text(widget, NULL, item->text.str, FALSE); } } else { combo_box_sync_index_to_value(widget, index, FALSE); } if (widget->emitter != NULL) { emitter_enable(widget->emitter); } evt.e.type = EVT_VALUE_CHANGED; widget_dispatch(widget, (event_t*)&evt); widget_invalidate(widget, NULL); } } else { combo_box_sync_index_to_value(widget, index, FALSE); } if (edit->select_none_when_focused || edit->readonly || combo_box->combobox_popup == NULL) { text_edit_unselect(edit->model); } else { text_edit_select_all(edit->model); } return widget_invalidate_force(widget, NULL); } static ret_t combo_box_add_selected_index(widget_t* widget, int32_t delta) { combo_box_t* combo_box = COMBO_BOX(widget); uint32_t nr = combo_box_count_options(widget); if (nr > 0) { uint32_t selected_index = (combo_box->selected_index + delta + nr) % nr; combo_box_set_selected_index(widget, selected_index); } return RET_OK; } ret_t combo_box_set_selected_index(widget_t* widget, uint32_t index) { return combo_box_set_selected_index_ex(widget, index, NULL); } ret_t combo_box_set_value(widget_t* widget, int32_t value) { int32_t index = combo_box_find_option(widget, value); return combo_box_set_selected_index(widget, index); } int32_t combo_box_get_value(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, 0); return combo_box->value; } ret_t combo_box_set_item_height(widget_t* widget, uint32_t item_height) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); combo_box->item_height = item_height; return RET_OK; } ret_t combo_box_set_localize_options(widget_t* widget, bool_t localize_options) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); combo_box->localize_options = localize_options; return RET_OK; } const char* combo_box_get_text(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, NULL); if (widget->tr_text != NULL && EDIT(widget)->readonly) { return widget->tr_text; } else { str_from_wstr(&(combo_box->text), widget->text.str); return combo_box->text.str; } } const char* combo_box_get_text_of_selected(widget_t* widget) { combo_box_t* combo_box = COMBO_BOX(widget); combo_box_option_t* option = NULL; return_value_if_fail(combo_box != NULL, NULL); option = combo_box_get_option(widget, combo_box->selected_index); return_value_if_fail(option != NULL, NULL); return option->text; } ret_t combo_box_set_selected_index_by_text(widget_t* widget, const char* text) { return combo_box_set_selected_index(widget, combo_box_find_option_of_text(widget, text)); } widget_t* combo_box_cast(widget_t* widget) { return_value_if_fail(WIDGET_IS_INSTANCE_OF(widget, combo_box), NULL); return widget; } ret_t combo_box_set_custom_open_popup( widget_t* widget, combo_box_custom_open_popup_t open_popup, combo_box_custom_on_layout_combobox_popup_t on_layout_combobox_popup) { combo_box_t* combo_box = COMBO_BOX(widget); return_value_if_fail(combo_box != NULL, RET_BAD_PARAMS); combo_box->open_popup = open_popup; combo_box->on_layout_combobox_popup = on_layout_combobox_popup; return RET_OK; }