ui/gtk/text.c

changeset 32
e5f4d8af567e
parent 29
3fc287f06305
child 35
834d9c15a69f
--- a/ui/gtk/text.c	Mon Feb 12 21:13:23 2024 +0100
+++ b/ui/gtk/text.c	Sun Jun 09 15:43:08 2024 +0200
@@ -33,6 +33,10 @@
 #include "text.h"
 #include "container.h"
 
+#include <cx/printf.h>
+
+#include <gdk/gdkkeysyms.h>
+
 
 #include "../common/types.h"
 
@@ -534,9 +538,14 @@
 }
 
 
-static UIWIDGET create_textfield_var(UiObject *obj, int width, UiBool frameless, UiBool password, UiVar *var) {
+
+
+static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) {
     GtkWidget *textfield = gtk_entry_new();
     
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    
     UiTextField *uitext = malloc(sizeof(UiTextField));
     uitext->ctx = obj->ctx;
     uitext->var = var;
@@ -547,8 +556,8 @@
                 G_CALLBACK(ui_textfield_destroy),
                 uitext);
     
-    if(width > 0) {
-        gtk_entry_set_width_chars(GTK_ENTRY(textfield), width);
+    if(args.width > 0) {
+        gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
     }
     if(frameless) {
         // TODO: gtk2legacy workaroud
@@ -586,40 +595,25 @@
     return textfield;
 }
 
-static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
-    UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
-    if(var) {
-        return create_textfield_var(obj, width, frameless, password, var);
-    } else {
-        // TODO: error
-    }
-    return NULL;
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
+    return create_textfield(obj, FALSE, FALSE, args);
 }
 
-static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
-    UiVar *var = NULL;
-    if(value) {
-        var = malloc(sizeof(UiVar));
-        var->value = value;
-        var->type = UI_VAR_SPECIAL;
-        var->from = NULL;
-        var->from_ctx = NULL;
-    }
-    return create_textfield_var(obj, width, frameless, password, var);
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, TRUE, FALSE, args);
 }
 
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+    return create_textfield(obj, FALSE, TRUE, args);
+}
+
+
 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
-    if(textfield->var) {
-        UiText *text = textfield->var->value;
-        if(text->undomgr) {
-            ui_destroy_undomgr(text->undomgr);
-        }
-        ui_destroy_boundvar(textfield->ctx, textfield->var);
-    }
     free(textfield);
 }
 
 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
+    // changed event is only registered, if the textfield->var != NULL
     UiString *value = textfield->var->value;
     if(value->observers) {
         UiEvent e;
@@ -632,45 +626,6 @@
     }
 }
 
-UIWIDGET ui_textfield_deprecated(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, FALSE, value);
-}
-
-UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, FALSE, varname);
-}
-
-UIWIDGET ui_frameless_textfield_deprecated(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, TRUE, FALSE, value);
-}
-
-UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
-}
-
-UIWIDGET ui_passwordfield_deprecated(UiObject *obj, UiString *value) {
-    return create_textfield(obj, 0, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
-    return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
-}
-
-UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
-    return create_textfield(obj, width, FALSE, TRUE, value);
-}
-
-UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
-    return create_textfield_nv(obj, width, FALSE, TRUE, varname);
-}
 
 char* ui_textfield_get(UiString *str) {
     if(str->value.ptr) {
@@ -689,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;
+    }
+}

mercurial