--- 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);