ui/gtk/text.c

changeset 44
473954dc6b74
parent 38
acd8c4a9d3fe
child 45
ab71409644b0
--- a/ui/gtk/text.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/text.c	Sun Sep 29 13:32:51 2024 +0200
@@ -82,17 +82,17 @@
                 G_CALLBACK(ui_textarea_destroy),
                 uitext);
     
-    GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
     gtk_scrolled_window_set_policy(
             GTK_SCROLLED_WINDOW(scroll_area),
             GTK_POLICY_AUTOMATIC,
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
     
     // font and padding
     PangoFontDescription *font;
     font = pango_font_description_from_string("Monospace");
-    gtk_widget_modify_font(text_area, font);
+    //gtk_widget_modify_font(text_area, font); // TODO
     pango_font_description_free(font);
     
     gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
@@ -180,7 +180,7 @@
 }
 
 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
-    return gtk_bin_get_child(GTK_BIN(textarea));
+    return SCROLLEDWINDOW_GET_CHILD(textarea);
 }
 
 char* ui_textarea_get(UiText *text) {
@@ -557,7 +557,10 @@
                 uitext);
     
     if(args.width > 0) {
+        // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
         gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
+#endif
     }
     if(frameless) {
         // TODO: gtk2legacy workaroud
@@ -573,7 +576,7 @@
     if(var) {
         UiString *value = var->value;
         if(value->value.ptr) {
-            gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
+            ENTRY_SET_TEXT(textfield, value->value.ptr);
             value->value.free(value->value.ptr);
             value->value.ptr = NULL;
             value->value.free = NULL;
@@ -631,13 +634,13 @@
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
     }
-    str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
+    str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj));
     str->value.free = (ui_freefunc)g_free;
     return str->value.ptr;
 }
 
 void ui_textfield_set(UiString *str, const char *value) {
-    gtk_entry_set_text(str->obj, value);
+    ENTRY_SET_TEXT(str->obj, value);
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
         str->value.ptr = NULL;
@@ -688,17 +691,6 @@
     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);
-    pathtf->buttonbox = NULL;
-    
-    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);
@@ -708,13 +700,77 @@
 }
 
 static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
-    free(pathtf->current_path);
+    free(pathtf->hbox);
     g_object_unref(pathtf->entry);
     free(pathtf);
 }
 
+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);
+    if(full_path_len == 0) {
+        return 1;
+    }
+    
+    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;
+    
+    return ui_pathtextfield_update_widget(pathtf);
+}
+
+static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int 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);
+    }
+    
+    return button;
+}
+
 static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
-    const gchar *text = gtk_entry_get_text(GTK_ENTRY(pathtf->entry));
+    const gchar *text = ENTRY_GET_TEXT(pathtf->entry);
     if(strlen(text) == 0) {
         return;
     }
@@ -736,6 +792,168 @@
     }
 }
 
+#if GTK_MAJOR_VERSION >= 4
+
+static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) {
+    if(pathtf->current_path) {
+        gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+        ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path);
+    }
+}
+
+static gboolean ui_path_textfield_key_controller(
+        GtkEventControllerKey* self,
+        guint keyval,
+        guint keycode,
+        GdkModifierType state,
+        UiPathTextField *pathtf)
+{
+    if(keyval == GDK_KEY_Escape) {
+        pathbar_show_hbox(NULL, pathtf);
+    }
+    return FALSE;
+}
+
+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;
+    }
+    
+    pathtf->stack = gtk_stack_new();
+    gtk_widget_set_name(pathtf->stack, "path-textfield-box");
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, pathtf->stack, FALSE);
+    
+    pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    pathtf->entry = gtk_entry_new();
+    gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry);
+    gtk_widget_set_hexpand(pathtf->entry, TRUE);
+    
+    GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic");
+    gtk_widget_add_css_class(cancel_button, "flat");
+    gtk_widget_add_css_class(cancel_button, "pathbar-extra-button");
+    gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button);
+    g_signal_connect(
+                cancel_button,
+                "clicked",
+                G_CALLBACK(pathbar_show_hbox),
+                pathtf);
+    
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    g_object_ref(pathtf->entry); // for compatibility with older pathbar version
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    
+    GtkEventController *entry_cancel = gtk_event_controller_key_new();
+    gtk_widget_add_controller(pathtf->entry, entry_cancel);
+    g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf);
+    
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    
+    
+    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 pathtf->stack;    
+}
+
+static void pathbar_pressed(
+        GtkGestureClick* self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        UiPathTextField *pathtf)
+{
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    gtk_widget_grab_focus(pathtf->entry);
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+    // recreate button hbox
+    if(pathtf->hbox) {
+        gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox);
+    }
+    pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE);
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    gtk_widget_set_name(pathtf->hbox, "pathbar");
+    
+    // add buttons for path elements
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+        gtk_widget_add_css_class(button, "flat");
+        
+        gtk_box_append(GTK_BOX(pathtf->hbox), button);
+        
+        if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) {
+            gtk_box_append(GTK_BOX(pathtf->hbox), gtk_label_new("/"));
+        }
+    }
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    
+    // create a widget for receiving button press events
+    GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    GtkGesture *handler = gtk_gesture_click_new();
+    gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler));
+    g_signal_connect(
+                handler,
+                "pressed",
+                G_CALLBACK(pathbar_pressed),
+                pathtf);
+    gtk_widget_set_hexpand(event_area, TRUE);
+    gtk_widget_set_vexpand(event_area, TRUE);
+    gtk_box_append(GTK_BOX(pathtf->hbox), event_area);
+    
+    return 0;
+}
+
+#else
+
+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);
+    pathtf->buttonbox = NULL;
+    
+    gtk_widget_show(pathtf->entry);
+    gtk_widget_grab_focus(pathtf->entry);    
+    
+    return TRUE;
+}
+
 static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
     if (event->keyval == GDK_KEY_Escape) {
         // reset GtkEntry value
@@ -833,38 +1051,7 @@
     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);
-    if(full_path_len == 0) {
-        return 1;
-    }
-    
-    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;
-    
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
     GtkWidget *buttonbox = create_path_button_box();
     
     // switch from entry to buttonbox or remove current buttonbox
@@ -876,34 +1063,9 @@
     gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
     pathtf->buttonbox = buttonbox;
     
-    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);
-        }
-        
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
     }
     
@@ -912,19 +1074,21 @@
     return 0;
 }
 
+#endif
+
 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.ptr = g_strdup(ENTRY_GET_TEXT(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);
+    ENTRY_SET_TEXT(tf->entry, value);
     ui_pathtextfield_update(tf, value);
     if(str->value.ptr) {
         str->value.free(str->value.ptr);

mercurial