# HG changeset patch # User Olaf Wintermann # Date 1717939580 -7200 # Node ID 3a77b9048664715572049a4889166cc6ce515cd6 # Parent 2533cdebf6ef6607b00bb45a072c215cdaf6ac42 add path textfield (GTK) diff -r 2533cdebf6ef -r 3a77b9048664 application/main.c --- a/application/main.c Sun Jun 09 10:24:49 2024 +0200 +++ b/application/main.c Sun Jun 09 15:26:20 2024 +0200 @@ -36,6 +36,7 @@ typedef struct { UiString *str1; UiString *str2; + UiString *path; UiDouble *progress; UiList *list; UiInteger *radio; @@ -74,6 +75,7 @@ UiContext *docctx = ui_document_context(doc); doc->str1 = ui_string_new(docctx, "str1"); doc->str1 = ui_string_new(docctx, "str2"); + doc->path = ui_string_new(docctx, "path"); doc->progress = ui_double_new(docctx, "progress"); doc->list = ui_list_new(docctx, "list"); ui_list_append(doc->list, "test1"); @@ -110,7 +112,7 @@ ui_togglebutton(obj, .label = "Toggle"); ui_checkbox(obj, .label = "Checkbox"); - ui_grid(obj, .fill = 1, .columnspacing = 5, .rowspacing = 5, .margin = 5) { + ui_grid(obj, .fill = 1, .columnspacing = 15, .rowspacing = 15, .margin = 15) { ui_button(obj, .label = "cell1", .hexpand = TRUE); ui_button(obj, .label = "cell2"); ui_newline(obj); @@ -131,6 +133,10 @@ 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_newline(obj); + //UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", -1); //model->getvalue = list_getvalue; ui_combobox(obj, .hexpand = true, .vexpand = false, .colspan = 2, .varname = "list", .getvalue = list_getvalue); diff -r 2533cdebf6ef -r 3a77b9048664 ui/gtk/text.c --- a/ui/gtk/text.c Sun Jun 09 10:24:49 2024 +0200 +++ b/ui/gtk/text.c Sun Jun 09 15:26:20 2024 +0200 @@ -33,6 +33,10 @@ #include "text.h" #include "container.h" +#include + +#include + #include "../common/types.h" @@ -640,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;icurrent_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;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); + } + + 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; + } +} diff -r 2533cdebf6ef -r 3a77b9048664 ui/gtk/text.h --- a/ui/gtk/text.h Sun Jun 09 10:24:49 2024 +0200 +++ b/ui/gtk/text.h Sun Jun 09 15:26:20 2024 +0200 @@ -72,6 +72,31 @@ // TODO: validatefunc } UiTextField; +typedef struct UiPathTextField { + UiObject *obj; + + GtkWidget *hbox; + GtkWidget *entry; + GtkWidget *buttonbox; + + char *current_path; + UiPathElm *current_pathelms; + size_t current_nelm; + + ui_pathelm_func getpathelm; + void* getpathelmdata; + + ui_callback onactivate; + void* onactivatedata; + + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropdata; +} UiPathTextField; + UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var); void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea); @@ -110,6 +135,10 @@ char* ui_textfield_get(UiString *str); void ui_textfield_set(UiString *str, const char *value); +int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path); +char* ui_path_textfield_get(UiString *str); +void ui_path_textfield_set(UiString *str, const char *value); + #ifdef __cplusplus } #endif diff -r 2533cdebf6ef -r 3a77b9048664 ui/gtk/toolkit.c --- a/ui/gtk/toolkit.c Sun Jun 09 10:24:49 2024 +0200 +++ b/ui/gtk/toolkit.c Sun Jun 09 15:26:20 2024 +0200 @@ -72,6 +72,8 @@ gtk_init(&argc, &argv); application_name = appname; + ui_css_init(); + uic_docmgr_init(); uic_toolbar_init(); @@ -287,3 +289,28 @@ } +#if GTK_MAJOR_VERSION >= 3 + +static GtkCssProvider* ui_gtk_css_provider; + +static const char *ui_gtk_css = +"#path-textfield-box {" +" background-color: @theme_base_color;" +" border-radius: 5px;" +" padding: 0px;" +"}"; + +void ui_css_init(void) { + ui_gtk_css_provider = gtk_css_provider_new(); + gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL); + + GdkScreen *screen = gdk_screen_get_default(); + gtk_style_context_add_provider_for_screen( + screen, + GTK_STYLE_PROVIDER(ui_gtk_css_provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); +} + + + +#endif \ No newline at end of file diff -r 2533cdebf6ef -r 3a77b9048664 ui/gtk/toolkit.h --- a/ui/gtk/toolkit.h Sun Jun 09 10:24:49 2024 +0200 +++ b/ui/gtk/toolkit.h Sun Jun 09 15:26:20 2024 +0200 @@ -76,6 +76,10 @@ void ui_set_active_window(UiObject *obj); UiObject *ui_get_active_window(); +#if GTK_MAJOR_VERSION >= 3 +void ui_css_init(void); +#endif + #ifdef __cplusplus } #endif