--- a/ui/gtk/button.c Sun May 23 09:44:43 2021 +0200 +++ b/ui/gtk/button.c Sat Jan 04 16:38:48 2025 +0100 @@ -31,19 +31,48 @@ #include "button.h" #include "container.h" -#include <ucx/mempool.h> +#include <cx/allocator.h> #include "../common/context.h" #include "../common/object.h" -UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { +void ui_button_set_icon_name(GtkWidget *button, const char *icon) { + if(!icon) { + return; + } + +#ifdef UI_GTK4 + gtk_button_set_icon_name(GTK_BUTTON(button), icon); +#else +#if GTK_CHECK_VERSION(2, 6, 0) + GtkWidget *image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON); + if(image) { + gtk_button_set_image(GTK_BUTTON(button), image); + } +#else + // TODO +#endif +#endif +} + +GtkWidget* ui_create_button( + UiObject *obj, + const char *label, + const char *icon, + ui_callback onclick, + void *userdata, + int event_value, + bool activate_event) +{ GtkWidget *button = gtk_button_new_with_label(label); + ui_button_set_icon_name(button, icon); - if(f) { + if(onclick) { UiEventData *event = malloc(sizeof(UiEventData)); event->obj = obj; - event->userdata = data; - event->callback = f; - event->value = 0; + event->userdata = userdata; + event->callback = onclick; + event->value = event_value; + event->customdata = NULL; g_signal_connect( button, @@ -55,11 +84,25 @@ "destroy", G_CALLBACK(ui_destroy_userdata), event); + if(activate_event) { + g_signal_connect( + button, + "activate", + G_CALLBACK(ui_button_clicked), + event); + } } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, button, FALSE); - + return button; +} + +UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) { + UiObject* current = uic_current_obj(obj); + GtkWidget *button = ui_create_button(obj, args.label, args.icon, args.onclick, args.onclickdata, 0, FALSE); + ui_set_name_and_style(button, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, button, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, button, FALSE); return button; } @@ -86,81 +129,318 @@ gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE); } -void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) { +void ui_toggled_obs(void *widget, UiVarEventData *event) { + UiInteger *i = event->var->value; UiEvent e; e.obj = event->obj; e.window = event->obj->window; e.document = event->obj->ctx->document; e.eventdata = event->var->value; - e.intval = gtk_toggle_tool_button_get_active(widget); + e.intval = i->get(i); - UiInteger *i = event->var->value; ui_notify_evt(i->observers, &e); } -UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var) { - GtkWidget *button = gtk_check_button_new_with_label(label); +static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = gtk_toggle_button_get_active(widget); + event->callback(&e, event->userdata); +} + +static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) { + if(gtk_toggle_button_get_active(widget)) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); + } +} + +void ui_setup_togglebutton( + UiObject *obj, + GtkWidget *togglebutton, + const char *label, + const char *icon, + const char *varname, + UiInteger *value, + ui_callback onchange, + void *onchangedata, + int enable_state) +{ + if(label) { + gtk_button_set_label(GTK_BUTTON(togglebutton), label); + } + ui_button_set_icon_name(togglebutton, icon); - // bind value - if(var) { - UiInteger *value = var->value; - value->obj = GTK_TOGGLE_BUTTON(button); - value->get = ui_toggle_button_get; - value->set = ui_toggle_button_set; - gtk_toggle_button_set_active(value->obj, value->value); + ui_bind_togglebutton( + obj, + togglebutton, + ui_toggle_button_get, + ui_toggle_button_set, + varname, + value, + (ui_toggled_func)ui_toggled_callback, + onchange, + onchangedata, + (ui_toggled_func)ui_togglebutton_enable_state_callback, + enable_state + ); +} + +void ui_bind_togglebutton( + UiObject *obj, + GtkWidget *widget, + int64_t (*getfunc)(UiInteger*), + void (*setfunc)(UiInteger*, int64_t), + const char *varname, + UiInteger *value, + void (*toggled_callback)(void*, void*), + ui_callback onchange, + void *onchangedata, + void (*enable_state_func)(void*, void*), + int enable_state) +{ + UiObject* current = uic_current_obj(obj); + UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER); + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = getfunc; + value->set = setfunc; UiVarEventData *event = malloc(sizeof(UiVarEventData)); event->obj = obj; event->var = var; event->observers = NULL; + event->callback = NULL; + event->userdata = NULL; g_signal_connect( - button, - "clicked", + widget, + "toggled", G_CALLBACK(ui_toggled_obs), event); g_signal_connect( - button, + widget, "destroy", G_CALLBACK(ui_destroy_vardata), event); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, button, FALSE); + if(onchange) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = onchangedata; + event->callback = onchange; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + widget, + "toggled", + G_CALLBACK(toggled_callback), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + if(enable_state > 0) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = NULL; + event->callback = NULL; + event->value = enable_state; + event->customdata = NULL; + + g_signal_connect( + widget, + "toggled", + G_CALLBACK(enable_state_func), + event); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } +} + +static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); - return button; + ui_setup_togglebutton( + current, + widget, + args.label, + args.icon, + args.varname, + args.value, + args.onchange, + args.onchangedata, + args.enable_group); + ui_set_name_and_style(widget, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, widget, args.groups); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); + + return widget; +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { + return togglebutton_create(obj, gtk_toggle_button_new(), args); +} + +#if GTK_MAJOR_VERSION >= 4 + +int64_t ui_check_button_get(UiInteger *integer) { + GtkCheckButton *button = integer->obj; + integer->value = (int)gtk_check_button_get_active(button); + return integer->value; +} + +void ui_check_button_set(UiInteger *integer, int64_t value) { + GtkCheckButton *button = integer->obj; + integer->value = value; + gtk_check_button_set_active(button, value != 0 ? TRUE : FALSE); +} + +static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = gtk_check_button_get_active(widget); + event->callback(&e, event->userdata); } -UIWIDGET ui_checkbox(UiObject *obj, char *label, UiInteger *value) { - UiVar *var = NULL; - if(value) { - var = malloc(sizeof(UiVar)); - var->value = value; - var->type = UI_VAR_SPECIAL; +static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) { + if(gtk_check_button_get_active(widget)) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); } - return ui_checkbox_var(obj, label, var); +} + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *widget = gtk_check_button_new_with_label(args.label); + ui_bind_togglebutton( + obj, + widget, + ui_check_button_get, + ui_check_button_set, + args.varname, + args.value, + (ui_toggled_func)ui_checkbox_callback, + args.onchange, + args.onchangedata, + (ui_toggled_func)ui_checkbox_enable_state, + args.enable_group); + + ui_set_name_and_style(widget, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, widget, args.groups); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); + + return widget; } -UIWIDGET ui_checkbox_nv(UiObject *obj, char *label, char *varname) { - UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER); - return ui_checkbox_var(obj, label, var); +#else +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + return togglebutton_create(obj, gtk_check_button_new(), args); +} +#endif + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { +#ifdef UI_GTK3 + return NULL; // TODO +#else + return ui_checkbox_create(obj, args); +#endif } +#if GTK_MAJOR_VERSION >= 4 +#define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label) +#define RADIOBUTTON_SET_GROUP(button, group) +#define RADIOBUTTON_GET_GROUP(button) GTK_CHECK_BUTTON(button) +#define RADIOBUTTON_GET_ACTIVE(button) gtk_check_button_get_active(GTK_CHECK_BUTTON(button)) +#else +#define RADIOBUTTON_NEW(group, label) gtk_radio_button_new_with_label(group, label) +#define RADIOBUTTON_SET_GROUP(button, group) /* noop */ +#define RADIOBUTTON_GET_GROUP(button) gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)) +#define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) +#endif -UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var) { +static void radiobutton_toggled(void *widget, UiEventData *event) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = RADIOBUTTON_GET_ACTIVE(widget); + event->callback(&e, event->userdata); +} + +typedef struct UiRadioButtonData { + UiInteger *value; + UiVarEventData *eventdata; + UiBool first; +} UiRadioButtonData; + +static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) { + if(data->first) { + ui_destroy_vardata(w, data->eventdata); + g_slist_free(data->value->obj); + data->value->obj = NULL; + data->value->get = NULL; + data->value->set = NULL; + } else { + free(data->eventdata); + } + free(data); +} + +UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + GSList *rg = NULL; UiInteger *rgroup; + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + + UiBool first = FALSE; if(var) { rgroup = var->value; rg = rgroup->obj; + if(!rg) { + first = TRUE; + } } - GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, label); - rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton)); - + GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args.label); + ui_set_name_and_style(rbutton, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, rbutton, args.groups); if(rgroup) { +#if GTK_MAJOR_VERSION >= 4 + if(rg) { + gtk_check_button_set_group(GTK_CHECK_BUTTON(rbutton), rg->data); + } + rg = g_slist_prepend(rg, rbutton); +#else + gtk_radio_button_set_group(GTK_RADIO_BUTTON(rbutton), rg); + rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton)); +#endif + rgroup->obj = rg; rgroup->get = ui_radiobutton_get; rgroup->set = ui_radiobutton_set; @@ -171,41 +451,53 @@ event->obj = obj; event->var = var; event->observers = NULL; + event->callback = NULL; + event->userdata = NULL; + + UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData)); + rbdata->value = rgroup; + rbdata->eventdata = event; + rbdata->first = first; g_signal_connect( rbutton, - "clicked", + "toggled", G_CALLBACK(ui_radio_obs), event); g_signal_connect( rbutton, "destroy", - G_CALLBACK(ui_destroy_vardata), + G_CALLBACK(destroy_radiobutton), + rbdata); + } + + if(args.onchange) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = args.onchangedata; + event->callback = args.onchange; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + rbutton, + "toggled", + G_CALLBACK(radiobutton_toggled), + event); + g_signal_connect( + rbutton, + "destroy", + G_CALLBACK(ui_destroy_userdata), event); } - UiContainer *ct = uic_get_current_container(obj); - ct->add(ct, rbutton, FALSE); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, rbutton, FALSE); return rbutton; } -UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) { - UiVar *var = NULL; - if(rgroup) { - var = malloc(sizeof(UiVar)); - var->value = rgroup; - var->type = UI_VAR_SPECIAL; - } - return ui_radiobutton_var(obj, label, var); -} - -UIWIDGET ui_radiobutton_nv(UiObject *obj, char *label, char *varname) { - UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_INTEGER); - return ui_radiobutton_var(obj, label, var); -} - -void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) { +void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event) { UiInteger *i = event->var->value; UiEvent e; @@ -218,6 +510,41 @@ ui_notify_evt(i->observers, &e); } +#if GTK_MAJOR_VERSION >= 4 +int64_t ui_radiobutton_get(UiInteger *value) { + int selection = 0; + GSList *ls = value->obj; + int i = 0; + guint len = g_slist_length(ls); + while(ls) { + if(gtk_check_button_get_active(GTK_CHECK_BUTTON(ls->data))) { + selection = len - i - 1; + break; + } + ls = ls->next; + i++; + } + + value->value = selection; + return selection; +} + +void ui_radiobutton_set(UiInteger *value, int64_t i) { + GSList *ls = value->obj; + int s = g_slist_length(ls) - 1 - i; + int j = 0; + while(ls) { + if(j == s) { + gtk_check_button_set_active(GTK_CHECK_BUTTON(ls->data), TRUE); + break; + } + ls = ls->next; + j++; + } + + value->value = i; +} +#else int64_t ui_radiobutton_get(UiInteger *value) { int selection = 0; GSList *ls = value->obj; @@ -251,4 +578,4 @@ value->value = i; } - +#endif