# HG changeset patch # User Olaf Wintermann # Date 1755881797 -7200 # Node ID 6105e9d9b7e187439bdaaca46d0effbac987f420 # Parent 4ee6a23bdcbfd35ed1d48db90d756555b8e209f0 add linkbutton (GTK) diff -r 4ee6a23bdcbf -r 6105e9d9b7e1 application/main.c --- a/application/main.c Fri Aug 22 10:22:55 2025 +0200 +++ b/application/main.c Fri Aug 22 18:56:37 2025 +0200 @@ -199,6 +199,9 @@ UiGeneric *web; UiString *list_input; UiList *list11; + UiString *link; + UiString *link_label; + UiString *link_uri; } MyDocument; MyDocument *doc1; @@ -342,6 +345,10 @@ ui_list_append(doc->list11, "Item 2"); ui_list_append(doc->list11, "Item 3"); + doc->link = ui_string_new(docctx, "link"); + doc->link_label = ui_string_new(docctx, "link_label"); + doc->link_uri = ui_string_new(docctx, "link_uri"); + //doc->text = ui_text_new(docctx, "text"); return doc; } @@ -525,6 +532,17 @@ action_list_selection(event, userdata); } +static void action_link(UiEvent *event, void *userdata) { + printf("action_link: %s\n", event->eventdata); +} + +static void action_change_link(UiEvent *event, void *userdata) { + MyDocument *doc = event->document; + char *label = ui_get(doc->link_label); + char *uri = ui_get(doc->link_uri); + ui_linkbutton_value_set(doc->link, label, uri); +} + void application_startup(UiEvent *event, void *data) { // global list UiContext *global = ui_global_context(); @@ -704,6 +722,16 @@ ui_tab(obj, "Tab 11") { ui_grid(obj, .margin = 10, .rowspacing = 10, .columnspacing = 10, .fill = TRUE) { ui_listview(obj, .varname = "list11", .multiselection = FALSE, .onselection = action_list_selection, .onactivate = action_list_activate, .hexpand = TRUE, .hfill = TRUE, .vexpand = TRUE, .vfill = TRUE); + ui_newline(obj); + + ui_linkbutton(obj, .varname = "link", .label = "Linkbutton", .onclick = action_link); + ui_newline(obj); + + ui_textfield(obj, .varname = "link_label"); + ui_newline(obj); + ui_textfield(obj, .varname = "link_uri"); + ui_newline(obj); + ui_button(obj, .label = "Update Link", .onclick = action_change_link); } } } diff -r 4ee6a23bdcbf -r 6105e9d9b7e1 ui/gtk/button.c --- a/ui/gtk/button.c Fri Aug 22 10:22:55 2025 +0200 +++ b/ui/gtk/button.c Fri Aug 22 18:56:37 2025 +0200 @@ -32,6 +32,8 @@ #include "button.h" #include "container.h" #include +#include +#include #include "../common/context.h" #include "../common/object.h" @@ -602,3 +604,231 @@ value->value = i; } #endif + + +static void ui_destroy_linkbutton(GtkWidget *widget, UiLinkButton *data) { + free(data->link); + free(data); +} + +static const char* linkbutton_get_uri(UiLinkButton *link) { + if(link->type == UI_LINK_BUTTON) { + return link->link; + } else { + return gtk_link_button_get_uri(GTK_LINK_BUTTON(link->widget)); + } +} + +static void linkbutton_set_uri(UiLinkButton *link, const char *uri) { + if(link->type == UI_LINK_BUTTON) { + free(link->link); + link->link = uri ? strdup(uri) : NULL; + } else { + gtk_link_button_set_uri(GTK_LINK_BUTTON(link->widget), uri); + } +} + +static gboolean linkbutton_get_visited(UiLinkButton *link) { + if(link->type == UI_LINK_BUTTON) { + return FALSE; + } else { + gtk_link_button_get_visited(GTK_LINK_BUTTON(link->widget)); + } +} + +static void linkbutton_set_visited(UiLinkButton *link, gboolean visited) { + if(link->type != UI_LINK_BUTTON) { + gtk_link_button_set_visited(GTK_LINK_BUTTON(link->widget), visited); + } +} + +/* + * Apply linkbutton settings from json. Expects jsonvalue to be a valid + * json object. + * + * { + * "label": "label text", + * "uri": "http://example.com", + * "visited": true + * } + * + */ +static void linkbutton_apply_value(UiLinkButton *link, const char *jsonvalue) { + CxJson json; + cxJsonInit(&json, NULL); + cxJsonFill(&json, jsonvalue); + + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) { + if(cxJsonIsObject(value)) { + CxJsonValue *label = cxJsonObjGet(value, "label"); + CxJsonValue *uri = cxJsonObjGet(value, "uri"); + CxJsonValue *visited = cxJsonObjGet(value, "visited"); + if(label) { + gtk_button_set_label(GTK_BUTTON(link->widget), cxJsonIsString(label) ? cxJsonAsString(label) : NULL); + } + if(uri) { + linkbutton_set_uri(link, cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL); + + } + if(visited) { + linkbutton_set_visited(link, cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE); + } + } + cxJsonValueFree(value); + } + cxJsonDestroy(&json); +} + +static char* create_linkbutton_jsonvalue(const char *label, const char *uri, gboolean include_null, gboolean visited, gboolean set_visited) { + CxJsonValue *obj = cxJsonCreateObj(NULL); + if(label) { + cxJsonObjPutString(obj, CX_STR("label"), label); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); + } + + if(uri) { + cxJsonObjPutString(obj, CX_STR("uri"), uri); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); + } + + if(set_visited) { + cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); + } + + CxJsonWriter writer = cxJsonWriterCompact(); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); + cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); + cxJsonValueFree(obj); + cxBufferTerminate(&buf); + + return buf.space; +} + +static char* linkbutton_get_value(UiLinkButton *link) { + const char *label = gtk_button_get_label(GTK_BUTTON(link->widget)); + const char *uri = linkbutton_get_uri(link); + gboolean visited = linkbutton_get_visited(link); + return create_linkbutton_jsonvalue(label, uri, TRUE, visited, TRUE); +} + +static void linkbutton_clicked(GtkWidget *widget, UiLinkButton *data) { + if(data->onclick) { + UiEvent e; + e.obj = data->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = (char*)linkbutton_get_uri(data); + e.eventdatatype = UI_EVENT_DATA_STRING; + e.intval = 0; + e.set = ui_get_setop(); + data->onclick(&e, data->onclickdata); + } +} + +static gboolean linkbutton_activate_link(GtkLinkButton *self, UiLinkButton *data) { + linkbutton_clicked(data->widget, data); + return data->nofollow; +} + +UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { + UiLinkButton *data = malloc(sizeof(UiLinkButton)); + memset(data, 0, sizeof(UiLinkButton)); + data->obj = obj; + data->type = args->type; + data->nofollow = args->nofollow; + data->onclick = args->onclick; + data->onclickdata = args->onclickdata; + + GtkWidget *button; + if(args->type == UI_LINK_BUTTON) { + button = gtk_button_new(); + g_signal_connect( + button, + "clicked", + G_CALLBACK(linkbutton_clicked), + data); + } else { + button = gtk_link_button_new("file:///"); + g_signal_connect( + button, + "activate-link", + G_CALLBACK(linkbutton_activate_link), + data); + } + gtk_button_set_label(GTK_BUTTON(button), args->label); + g_object_set_data(G_OBJECT(button), "ui_linkbutton", data); + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_destroy_linkbutton), + data); + + data->widget = button; + + UiObject* current = uic_current_obj(obj); + UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *str = var->value; + char *current_value = ui_get(str); + if(current_value) { + linkbutton_apply_value(data, current_value); + } + + str->obj = data; + str->get = ui_linkbutton_get; + str->set = ui_linkbutton_set; + } + + ui_set_name_and_style(button, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, button, args->groups); + UI_APPLY_LAYOUT2(current, args); + current->container->add(current->container, button); + + return button; +} + +char* ui_linkbutton_get(UiString *s) { + UiLinkButton *link = s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = linkbutton_get_value(link); + s->value.free = free; + return s->value.ptr; +} + +void ui_linkbutton_set(UiString *s, const char *str) { + linkbutton_apply_value(s->obj, str); + if(s->value.free) { + s->value.free(s->value.ptr); + } +} + + +void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) { + char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_label(UiString *str, const char *label) { + char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_uri(UiString *str, const char *uri) { + char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) { + char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE); + ui_set(str, value); + free(value); +} diff -r 4ee6a23bdcbf -r 6105e9d9b7e1 ui/gtk/button.h --- a/ui/gtk/button.h Fri Aug 22 10:22:55 2025 +0200 +++ b/ui/gtk/button.h Fri Aug 22 18:56:37 2025 +0200 @@ -37,6 +37,16 @@ extern "C" { #endif +typedef struct UiLinkButton { + UiObject *obj; + GtkWidget *widget; + UiLinkType type; + UiBool nofollow; + char *link; + ui_callback onclick; + void *onclickdata; +} UiLinkButton; + void ui_button_set_icon_name(GtkWidget *button, const char *icon_name); typedef void (*ui_toggled_func)(void*, void*); @@ -89,6 +99,9 @@ int64_t ui_radiobutton_get(UiInteger *value); void ui_radiobutton_set(UiInteger *value, int64_t i); +char* ui_linkbutton_get(UiString *s); +void ui_linkbutton_set(UiString *s, const char *str); + #ifdef __cplusplus } #endif diff -r 4ee6a23bdcbf -r 6105e9d9b7e1 ui/ui/button.h --- a/ui/ui/button.h Fri Aug 22 10:22:55 2025 +0200 +++ b/ui/ui/button.h Fri Aug 22 18:56:37 2025 +0200 @@ -35,6 +35,12 @@ extern "C" { #endif +typedef enum UiLinkType UiLinkType; +enum UiLinkType { + UI_LINK_TEXT = 0, + UI_LINK_BUTTON +}; + typedef struct UiButtonArgs { UiBool fill; UiBool hexpand; @@ -81,22 +87,54 @@ const int* groups; } UiToggleArgs; + +typedef struct UiLinkButtonArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char *label; + const char *uri; + UiString *value; + const char *varname; + ui_callback onclick; + void *onclickdata; + UiBool nofollow; + UiAlignment align; + UiLinkType type; + + const int* groups; +} UiLinkButtonArgs; #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } ) #define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) #define ui_checkbox(obj, ...) ui_checkbox_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) #define ui_switch(obj, ...) ui_switch_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) #define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, &(UiToggleArgs){ __VA_ARGS__ } ) +#define ui_linkbutton(obj, ...) ui_linkbutton_create(obj, &(UiLinkButtonArgs){ __VA_ARGS__ }) -UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args); -UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args); -UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args); -UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args); -UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args); +UIEXPORT UIWIDGET ui_togglebutton_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_checkbox_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_switch_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args); +UIEXPORT UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args); UIEXPORT void ui_button_set_label(UIWIDGET button, const char *label); UIEXPORT void ui_button_set_icon(UIWIDGET button, const char *icon); +UIEXPORT void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri); +UIEXPORT void ui_linkbutton_value_set_label(UiString *str, const char *label); +UIEXPORT void ui_linkbutton_value_set_uri(UiString *str, const char *uri); +UIEXPORT void ui_linkbutton_value_set_visited(UiString *str, UiBool visited); + #ifdef __cplusplus } diff -r 4ee6a23bdcbf -r 6105e9d9b7e1 ui/ui/display.h --- a/ui/ui/display.h Fri Aug 22 10:22:55 2025 +0200 +++ b/ui/ui/display.h Fri Aug 22 18:56:37 2025 +0200 @@ -38,15 +38,7 @@ #ifdef __cplusplus extern "C" { #endif - -enum UiAlignment { - UI_ALIGN_DEFAULT = 0, - UI_ALIGN_LEFT, - UI_ALIGN_RIGHT, - UI_ALIGN_CENTER -}; - -typedef enum UiAlignment UiAlignment; + enum UiLabelStyle { UI_LABEL_STYLE_DEFAULT = 0, diff -r 4ee6a23bdcbf -r 6105e9d9b7e1 ui/ui/toolkit.h --- a/ui/ui/toolkit.h Fri Aug 22 10:22:55 2025 +0200 +++ b/ui/ui/toolkit.h Fri Aug 22 18:56:37 2025 +0200 @@ -237,7 +237,16 @@ UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM } UiDnDAction; - + +enum UiAlignment { + UI_ALIGN_DEFAULT = 0, + UI_ALIGN_LEFT, + UI_ALIGN_RIGHT, + UI_ALIGN_CENTER +}; + +typedef enum UiAlignment UiAlignment; + typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */ typedef void*(*ui_getvaluefunc)(void *elm, int col);