add linkbutton (GTK)

Fri, 22 Aug 2025 18:56:37 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 22 Aug 2025 18:56:37 +0200
changeset 704
6105e9d9b7e1
parent 703
4ee6a23bdcbf
child 705
a537158b4269

add linkbutton (GTK)

application/main.c file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/button.h file | annotate | diff | comparison | revisions
ui/ui/button.h file | annotate | diff | comparison | revisions
ui/ui/display.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
--- 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);
             }
         }
     }
--- 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 <cx/allocator.h>
+#include <cx/buffer.h>
+#include <cx/json.h>
 #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);
+}
--- 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
--- 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
 }
--- 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,
--- 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);

mercurial