# HG changeset patch # User Olaf Wintermann # Date 1727383393 -7200 # Node ID 4918f9132552342e0949d469249df18321286b45 # Parent 02c95df91de1e5d6bd8f0961ac3d15f6724e9f9b add gtk4 pathbar diff -r 02c95df91de1 -r 4918f9132552 application/main.c --- 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); diff -r 02c95df91de1 -r 4918f9132552 ui/gtk/headerbar.c --- 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; } diff -r 02c95df91de1 -r 4918f9132552 ui/gtk/text.c --- 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;icurrent_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;icurrent_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;iname, 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;icurrent_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 diff -r 02c95df91de1 -r 4918f9132552 ui/gtk/text.h --- 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); diff -r 02c95df91de1 -r 4918f9132552 ui/gtk/toolbar.c --- 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) { diff -r 02c95df91de1 -r 4918f9132552 ui/gtk/toolbar.h --- 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); diff -r 02c95df91de1 -r 4918f9132552 ui/gtk/toolkit.c --- 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 */ }