add path textfield (GTK) newapi

Sun, 09 Jun 2024 15:26:20 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 09 Jun 2024 15:26:20 +0200
branch
newapi
changeset 282
3a77b9048664
parent 281
2533cdebf6ef
child 283
bb0b8927f5c0

add path textfield (GTK)

application/main.c file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Sun Jun 09 10:24:49 2024 +0200
+++ b/application/main.c	Sun Jun 09 15:26:20 2024 +0200
@@ -36,6 +36,7 @@
 typedef struct {
     UiString *str1;
     UiString *str2;
+    UiString *path;
     UiDouble *progress;
     UiList *list;
     UiInteger *radio;
@@ -74,6 +75,7 @@
     UiContext *docctx = ui_document_context(doc);
     doc->str1 = ui_string_new(docctx, "str1");
     doc->str1 = ui_string_new(docctx, "str2");
+    doc->path = ui_string_new(docctx, "path");
     doc->progress = ui_double_new(docctx, "progress");
     doc->list = ui_list_new(docctx, "list");
     ui_list_append(doc->list, "test1");
@@ -110,7 +112,7 @@
     ui_togglebutton(obj, .label = "Toggle");
     ui_checkbox(obj, .label = "Checkbox");
     
-    ui_grid(obj, .fill = 1, .columnspacing = 5, .rowspacing = 5, .margin = 5) {
+    ui_grid(obj, .fill = 1, .columnspacing = 15, .rowspacing = 15, .margin = 15) {
         ui_button(obj, .label = "cell1", .hexpand = TRUE);
         ui_button(obj, .label = "cell2");
         ui_newline(obj);
@@ -131,6 +133,10 @@
         ui_textfield(obj, .value = doc->str1);
         ui_newline(obj);
         
+        ui_path_textfield(obj, .colspan = 2, .varname = "path");
+        ui_set(doc->path, "/test/path/123");
+        ui_newline(obj);
+        
         //UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", -1);
         //model->getvalue = list_getvalue;
         ui_combobox(obj, .hexpand = true, .vexpand = false, .colspan = 2, .varname = "list", .getvalue = list_getvalue);
--- a/ui/gtk/text.c	Sun Jun 09 10:24:49 2024 +0200
+++ b/ui/gtk/text.c	Sun Jun 09 15:26:20 2024 +0200
@@ -33,6 +33,10 @@
 #include "text.h"
 #include "container.h"
 
+#include <cx/printf.h>
+
+#include <gdk/gdkkeysyms.h>
+
 
 #include "../common/types.h"
 
@@ -640,3 +644,283 @@
         str->value.free = NULL;
     }
 }
+
+// ----------------------- path textfield -----------------------
+
+// TODO: move to common
+static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
+    cxstring *pathelms;
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+
+    if (nelm == 0) {
+        *ret_nelm = 0;
+        return NULL;
+    }
+
+    UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));
+    size_t n = nelm;
+    int j = 0;
+    for (int i = 0; i < nelm; i++) {
+        cxstring c = pathelms[i];
+        if (c.length == 0) {
+            if (i == 0) {
+                c.length = 1;
+            }
+            else {
+                n--;
+                continue;
+            }
+        }
+
+        cxmutstr m = cx_strdup(c);
+        elms[j].name = m.ptr;
+        elms[j].name_len = m.length;
+        
+        size_t elm_path_len = c.ptr + c.length - full_path;
+        cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));
+        elms[j].path = elm_path.ptr;
+        elms[j].path_len = elm_path.length;
+
+        j++;
+    }
+    *ret_nelm = n;
+
+    return elms;
+}
+
+static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) {
+    gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0);
+    gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+    
+    gtk_widget_show(pathtf->entry);
+    gtk_widget_grab_focus(pathtf->entry);    
+    
+    return TRUE;
+}
+
+static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
+    for(int i=0;i<nelm;i++) {
+        free(elms[i].name);
+        free(elms[i].path);
+    }
+    free(elms);
+}
+
+static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
+    free(pathtf->current_path);
+    g_object_unref(pathtf->entry);
+    free(pathtf);
+}
+
+static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
+    const gchar *text = gtk_entry_get_text(GTK_ENTRY(pathtf->entry));
+    if(strlen(text) == 0) {
+        return;
+    }
+    
+    UiObject *obj = pathtf->obj;
+    
+    if(ui_pathtextfield_update(pathtf, text)) {
+        return;
+    }
+    
+    if(pathtf->onactivate) {
+        UiEvent evt;
+        evt.obj = obj;
+        evt.window = obj->window;
+        evt.document = obj->ctx->document;
+        evt.eventdata = (char*)text;
+        evt.intval = -1;
+        pathtf->onactivate(&evt, pathtf->onactivatedata);
+    }
+}
+
+static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
+    if (event->keyval == GDK_KEY_Escape) {
+        // reset GtkEntry value
+        gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path);
+        const gchar *text = gtk_entry_get_text(GTK_ENTRY(self));
+        ui_pathtextfield_update(pathtf, text);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static GtkWidget* create_path_button_box() {
+    GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style
+    gtk_box_set_homogeneous(GTK_BOX(bb), FALSE);
+    gtk_box_set_spacing(GTK_BOX(bb), 0);
+    return bb;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+    memset(pathtf, 0, sizeof(UiPathTextField));
+    pathtf->obj = obj;
+    pathtf->getpathelm = args.getpathelm;
+    pathtf->getpathelmdata = args.getpathelmdata;
+    pathtf->onactivate = args.onactivate;
+    pathtf->onactivatedata = args.onactivatedata;
+    pathtf->ondragcomplete = args.ondragcomplete;
+    pathtf->ondragcompletedata = args.ondragcompletedata;
+    pathtf->ondragstart = args.ondragstart;
+    pathtf->ondragstartdata = args.ondragstartdata;
+    pathtf->ondrop = args.ondrop;
+    pathtf->ondropdata = args.ondropsdata;
+    
+    if(!pathtf->getpathelm) {
+        pathtf->getpathelm = default_pathelm_func;
+        pathtf->getpathelmdata = NULL;
+    }
+    
+    // top level container for the path textfield is a GtkEventBox
+    // the event box is needed to handle background button presses
+    GtkWidget *eventbox = gtk_event_box_new();
+    g_signal_connect(
+            eventbox,
+            "button-press-event",
+            G_CALLBACK(path_textfield_btn_pressed),
+            pathtf);
+    g_signal_connect(
+            eventbox,
+            "destroy",
+            G_CALLBACK(ui_path_textfield_destroy),
+            pathtf);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, eventbox, FALSE);
+    
+    // hbox as parent for the GtkEntry and GtkButtonBox
+    GtkWidget *hbox = ui_gtk_hbox_new(0);
+    pathtf->hbox = hbox;
+    gtk_container_add(GTK_CONTAINER(eventbox), hbox);
+    gtk_widget_set_name(hbox, "path-textfield-box");
+    
+    // create GtkEntry, that is also visible by default (with input yet)
+    pathtf->entry = gtk_entry_new();
+    g_object_ref(G_OBJECT(pathtf->entry));
+    gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0);
+    
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    g_signal_connect(
+            pathtf->entry,
+            "key-press-event",
+            G_CALLBACK(ui_path_textfield_key_press),
+            pathtf);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = pathtf;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    return hbox;
+}
+
+void ui_path_button_clicked(GtkWidget *widget, UiEventData *event) {
+    UiPathElm *elm = event->customdata;
+    cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = evt.obj->window;
+    evt.document = evt.obj->ctx->document;
+    evt.eventdata = elm->path;
+    evt.intval = event->value;
+    event->callback(&evt, event->userdata);
+    free(path.ptr);
+}
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) {
+    size_t full_path_len = strlen(full_path);
+    
+    size_t nelm = 0;
+    UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata);
+    if (!path_elm) {
+        return 1;
+    }
+    
+    free(pathtf->current_path);
+    pathtf->current_path = strdup(full_path);
+    
+    ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm);
+    pathtf->current_pathelms = path_elm;
+    pathtf->current_nelm = nelm;
+    
+    GtkWidget *buttonbox = create_path_button_box();
+    pathtf->buttonbox = buttonbox;
+    
+    // switch from entry to buttonbox
+    gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry);
+    gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
+    
+    for (int i=0;i<nelm;i++) {
+        UiPathElm *elm = &path_elm[i];
+        
+        cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
+        GtkWidget *button = gtk_button_new_with_label(name.ptr);
+        free(name.ptr);
+        
+        if(pathtf->onactivate) {
+            UiEventData *eventdata = malloc(sizeof(UiEventData));
+            eventdata->callback = pathtf->onactivate;
+            eventdata->userdata = pathtf->onactivatedata;
+            eventdata->obj = pathtf->obj;
+            eventdata->customdata = elm;
+            eventdata->value = i;
+            
+            g_signal_connect(
+                    button,
+                    "clicked",
+                    G_CALLBACK(ui_path_button_clicked),
+                    eventdata);
+            
+            g_signal_connect(
+                    button,
+                    "destroy",
+                    G_CALLBACK(ui_destroy_userdata),
+                    eventdata);
+        }
+        
+        gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
+    }
+    
+    gtk_widget_show_all(buttonbox);
+    
+    return 0;
+}
+
+char* ui_path_textfield_get(UiString *str) {
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+    }
+    UiPathTextField *tf = str->obj;
+    str->value.ptr = g_strdup(gtk_entry_get_text(GTK_ENTRY(tf->entry)));
+    str->value.free = (ui_freefunc)g_free;
+    return str->value.ptr;
+}
+
+void ui_path_textfield_set(UiString *str, const char *value) {
+    UiPathTextField *tf = str->obj;
+    gtk_entry_set_text(GTK_ENTRY(tf->entry), value);
+    ui_pathtextfield_update(tf, value);
+    if(str->value.ptr) {
+        str->value.free(str->value.ptr);
+        str->value.ptr = NULL;
+        str->value.free = NULL;
+    }
+}
--- a/ui/gtk/text.h	Sun Jun 09 10:24:49 2024 +0200
+++ b/ui/gtk/text.h	Sun Jun 09 15:26:20 2024 +0200
@@ -72,6 +72,31 @@
     // TODO: validatefunc
 } UiTextField;
 
+typedef struct UiPathTextField {
+    UiObject *obj;
+    
+    GtkWidget *hbox;
+    GtkWidget *entry;
+    GtkWidget *buttonbox;
+    
+    char *current_path;
+    UiPathElm *current_pathelms;
+    size_t current_nelm;
+    
+    ui_pathelm_func getpathelm;
+    void* getpathelmdata;
+
+    ui_callback onactivate;
+    void* onactivatedata;
+    
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropdata;
+} UiPathTextField;
+
 UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var);
 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea);
 
@@ -110,6 +135,10 @@
 char* ui_textfield_get(UiString *str);
 void ui_textfield_set(UiString *str, const char *value);
 
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path);
+char* ui_path_textfield_get(UiString *str);
+void ui_path_textfield_set(UiString *str, const char *value);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/gtk/toolkit.c	Sun Jun 09 10:24:49 2024 +0200
+++ b/ui/gtk/toolkit.c	Sun Jun 09 15:26:20 2024 +0200
@@ -72,6 +72,8 @@
     gtk_init(&argc, &argv);
     application_name = appname;
     
+    ui_css_init();
+    
     uic_docmgr_init();
     
     uic_toolbar_init();
@@ -287,3 +289,28 @@
 }
 
 
+#if GTK_MAJOR_VERSION >= 3
+
+static GtkCssProvider* ui_gtk_css_provider;
+
+static const char *ui_gtk_css = 
+"#path-textfield-box {"
+"  background-color: @theme_base_color;"
+"  border-radius: 5px;"
+"  padding: 0px;"
+"}";
+
+void ui_css_init(void) {
+    ui_gtk_css_provider = gtk_css_provider_new();
+    gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL);
+    
+    GdkScreen *screen = gdk_screen_get_default();
+    gtk_style_context_add_provider_for_screen(
+            screen,
+            GTK_STYLE_PROVIDER(ui_gtk_css_provider),
+            GTK_STYLE_PROVIDER_PRIORITY_USER);
+}
+
+
+
+#endif
\ No newline at end of file
--- a/ui/gtk/toolkit.h	Sun Jun 09 10:24:49 2024 +0200
+++ b/ui/gtk/toolkit.h	Sun Jun 09 15:26:20 2024 +0200
@@ -76,6 +76,10 @@
 void ui_set_active_window(UiObject *obj);
 UiObject *ui_get_active_window();
 
+#if GTK_MAJOR_VERSION >= 3
+void ui_css_init(void);
+#endif
+
 #ifdef	__cplusplus
 }
 #endif

mercurial