add gtk4 pathbar newapi tip

Thu, 26 Sep 2024 22:43:13 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 26 Sep 2024 22:43:13 +0200
branch
newapi
changeset 310
4918f9132552
parent 309
02c95df91de1

add gtk4 pathbar

application/main.c file | annotate | diff | comparison | revisions
ui/gtk/headerbar.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/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/toolbar.h file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
--- a/application/main.c	Mon Sep 23 23:22:27 2024 +0200
+++ b/application/main.c	Thu Sep 26 22:43:13 2024 +0200
@@ -134,8 +134,9 @@
         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_button(obj, .label="Test");
+        ui_path_textfield(obj, .varname = "path");
+        ui_set(doc->path, "/test/path/longdirectoryname/123");
         ui_newline(obj);
         
         //UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", -1);
--- a/ui/gtk/headerbar.c	Mon Sep 23 23:22:27 2024 +0200
+++ b/ui/gtk/headerbar.c	Thu Sep 26 22:43:13 2024 +0200
@@ -43,11 +43,14 @@
 #define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h)
 #define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w)
 #define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w)
+#if GTK_MAJOR_VERSION >= 4
 #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w)
+#endif
 #endif
     
 void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) {
-    CxMap *items = uic_get_toolbar_items();
     CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
     CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
     CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
@@ -86,7 +89,7 @@
 #if GTK_MAJOR_VERSION >= 4
             gtk_box_append(GTK_BOX(box), item);
 #else
-            gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0)
+            gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0);
 #endif
             break;
         }
--- a/ui/gtk/text.c	Mon Sep 23 23:22:27 2024 +0200
+++ b/ui/gtk/text.c	Thu Sep 26 22:43:13 2024 +0200
@@ -691,27 +691,6 @@
     return elms;
 }
 
-
-#if GTK_MAJOR_VERSION >= 4
-
-UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
-    // TODO
-    return NULL;    
-}
-
-#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 void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
     for(int i=0;i<nelm;i++) {
         free(elms[i].name);
@@ -721,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;
     }
@@ -749,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
@@ -846,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
@@ -889,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);
     }
     
@@ -925,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);
@@ -945,5 +1096,3 @@
         str->value.free = NULL;
     }
 }
-
-#endif
--- a/ui/gtk/text.h	Mon Sep 23 23:22:27 2024 +0200
+++ b/ui/gtk/text.h	Thu Sep 26 22:43:13 2024 +0200
@@ -75,9 +75,13 @@
 typedef struct UiPathTextField {
     UiObject *obj;
     
+    GtkWidget *stack;
     GtkWidget *hbox;
+    GtkWidget *entry_box;
     GtkWidget *entry;
+#if GTK_MAJOR_VERSION == 3
     GtkWidget *buttonbox;
+#endif
     
     char *current_path;
     UiPathElm *current_pathelms;
@@ -136,6 +140,7 @@
 void ui_textfield_set(UiString *str, const char *value);
 
 int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path);
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf);
 char* ui_path_textfield_get(UiString *str);
 void ui_path_textfield_set(UiString *str, const char *value);
 
--- a/ui/gtk/toolbar.c	Mon Sep 23 23:22:27 2024 +0200
+++ b/ui/gtk/toolbar.c	Thu Sep 26 22:43:13 2024 +0200
@@ -361,9 +361,9 @@
     CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
     CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
     
-    ui_headerbar_add_items(obj, headerbar, items, left_defaults);
-    ui_headerbar_add_items(obj, headerbar, items, center_defaults);
-    ui_headerbar_add_items(obj, headerbar, items, right_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, left_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, center_defaults);
+    ui_toolbar_headerbar_add_items(obj, headerbar, items, right_defaults);
     
     return headerbar;
 }
@@ -388,7 +388,7 @@
 }
 
 
-void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) {
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) {
     // add pre-configured items
     CxIterator i = cxListIterator(defaults);
     cx_foreach(char*, def, i) {
--- a/ui/gtk/toolbar.h	Mon Sep 23 23:22:27 2024 +0200
+++ b/ui/gtk/toolbar.h	Thu Sep 26 22:43:13 2024 +0200
@@ -127,7 +127,7 @@
 
 GtkWidget* ui_create_headerbar(UiObject *obj);
 
-void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults);
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults);
 
 void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj);
 void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj);
--- a/ui/gtk/toolkit.c	Mon Sep 23 23:22:27 2024 +0200
+++ b/ui/gtk/toolkit.c	Thu Sep 26 22:43:13 2024 +0200
@@ -314,12 +314,44 @@
 
 static GtkCssProvider* ui_gtk_css_provider;
 
+#if GTK_MAJOR_VERSION == 4
+static const char *ui_gtk_css = 
+"#path-textfield-box {\n"
+"  background-color: alpha(currentColor, 0.1);"
+"  border-radius: 6px;"
+"  padding: 0px;"
+"}\n"
+".pathbar-extra-button {\n"
+"  border-top-right-radius: 6px;"
+"  border-bottom-right-radius: 6px;"
+"  border-top-left-radius: 0px;"
+"  border-bottom-left-radius: 0px;"
+"}\n"
+"#pathbar button {\n"
+"  margin: 3px;"
+"  border-radius: 4px;"
+"  padding-top: 0px;"
+"  padding-bottom: 0px;"
+"  padding-left: 8px;"
+"  padding-right: 8px;"
+"}\n"
+"#path-textfield-box entry {\n"
+"  background-color: #00000000;"
+"  border-top-left-radius: 6px;"
+"  border-bottom-left-radius: 6px;"
+"  border-top-right-radius: 0px;"
+"  border-bottom-right-radius: 0px;"
+"}"
+;
+
+#elif GTK_MAJOR_VERSION == 3
 static const char *ui_gtk_css = 
 "#path-textfield-box {"
 "  background-color: @theme_base_color;"
 "  border-radius: 5px;"
 "  padding: 0px;"
 "}";
+#endif
 
 void ui_css_init(void) {
     ui_gtk_css_provider = gtk_css_provider_new();
@@ -344,7 +376,7 @@
 #endif /* GTK_MINOR_VERSION < 12 */
     
     GdkDisplay *display = gdk_display_get_default();
-    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
+    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
     
 #endif /* UI_GTK4 */
 }

mercurial