# HG changeset patch # User Olaf Wintermann # Date 1765637938 -3600 # Node ID e57ca27477825496d7a7ba2aa859b5380c85582f # Parent 3da24640513a3a39e0febf91a07942432e2591d7 fix build with newest toolkit version diff -r 3da24640513a -r e57ca2747782 application/application.c --- a/application/application.c Sun Dec 07 20:00:33 2025 +0100 +++ b/application/application.c Sat Dec 13 15:58:58 2025 +0100 @@ -87,15 +87,15 @@ ui_menu("File") { ui_menuitem(.label = "New Window", .onclick = action_window_new); ui_menuseparator(); - ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "New File", .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "Delete", .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "New File", .onclick = action_newfile, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Delete", .onclick = action_delete, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuseparator(); - ui_menuitem(.label = "Upload File", .onclick = action_upload_file, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "Upload Folder", .onclick = action_upload_dir, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem(.label = "Upload File", .onclick = action_upload_file, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Upload Folder", .onclick = action_upload_dir, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Download", .onclick = action_download, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem("Open Properties", .onclick = action_open_properties, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuseparator(); ui_menuitem(.label = "Close", .onclick = action_window_close); } @@ -105,9 +105,9 @@ } ui_menu("Go") { - ui_menuitem(.label = "Parent", .onclick = action_go_parent, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "Back", .onclick = action_go_back, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "Forward", .onclick = action_go_forward, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Parent", .onclick = action_go_parent, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Back", .onclick = action_go_back, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Forward", .onclick = action_go_forward, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuseparator(); ui_menu("Repositories") { ui_menu_itemlist(.varname = "repolist", .getvalue = davrepo_getname, .onselect = action_repo_selected); @@ -121,28 +121,28 @@ // toolbar ui_toolbar_item("Home", .icon = UI_ICON_HOME); ui_toolbar_item("NewWindow", .icon = UI_ICON_NEW_WINDOW, .onclick = action_window_new); - ui_toolbar_item("Refresh", .icon = UI_ICON_REFRESH, .onclick = action_refresh, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_toolbar_item("Refresh", .icon = UI_ICON_REFRESH, .onclick = action_refresh, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_toolbar_menu("Repo", .label = "Repository") { ui_menu_itemlist(.varname = "repolist", .getvalue = davrepo_getname, .onselect = action_repo_selected); } - ui_toolbar_item("NewFolder", .icon = UI_ICON_NEW_FOLDER, .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_toolbar_item("NewFile", .icon = UI_ICON_ADD, .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_toolbar_item("Upload", .label = "Upload", .icon = UI_ICON_UPLOAD, .onclick = action_upload_file, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_toolbar_item("Download", .icon = UI_ICON_SAVE_LOCAL, .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_toolbar_item("Remove", .icon = UI_ICON_DELETE, .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_toolbar_item("NewFolder", .icon = UI_ICON_NEW_FOLDER, .onclick = action_mkcol, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_toolbar_item("NewFile", .icon = UI_ICON_ADD, .onclick = action_newfile, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_toolbar_item("Upload", .label = "Upload", .icon = UI_ICON_UPLOAD, .onclick = action_upload_file, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_toolbar_item("Download", .icon = UI_ICON_SAVE_LOCAL, .onclick = action_download, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_toolbar_item("Remove", .icon = UI_ICON_DELETE, .onclick = action_delete, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_toolbar_toggleitem("LocalBrowser", .icon = UI_ICON_DOCK_LEFT, .label = "Local Browser"); ui_toolbar_toggleitem("PreviewPane", .icon = UI_ICON_DOCK_RIGHT); ui_toolbar_appmenu() { ui_menuitem("New Window", .onclick = action_window_new); ui_menuseparator(); - ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "New File", .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem("Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem("Upload Files", .onclick = action_upload_file, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem("Upload Directory", .onclick = action_upload_dir, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "New File", .onclick = action_newfile, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem("Download", .onclick = action_download, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem("Upload Files", .onclick = action_upload_file, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem("Upload Directory", .onclick = action_upload_dir, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem("Open Properties", .onclick = action_open_properties, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuseparator(); ui_menu("Downloads/Uploads") { ui_menu_itemlist(.varname = "transferlist", .getvalue = transfers_getlabel, .onselect = action_transfer_selected); diff -r 3da24640513a -r e57ca2747782 application/davcontroller.c --- a/application/davcontroller.c Sun Dec 07 20:00:33 2025 +0100 +++ b/application/davcontroller.c Sat Dec 13 15:58:58 2025 +0100 @@ -78,7 +78,7 @@ ui_list_update(browser->resources); - ui_set_group(ui->ctx, APP_STATE_BROWSER_SESSION); + ui_set_state(ui->ctx, APP_STATE_BROWSER_SESSION); } // ------------------------------ davbrowser_connect2repo ------------------------------ @@ -465,7 +465,7 @@ } cxmutstr wtitle = cx_asprintf("Upload to: %s", ui_get(browser->path)); - UiObject *dialog = ui_simple_window(wtitle.ptr, NULL); + UiObject *dialog = ui_simple_window(wtitle.ptr); free(wtitle.ptr); DavFileUpload *upload = dav_upload_create(browser, dialog, files); @@ -476,7 +476,7 @@ void davbrowser_download(UiObject *ui, DavBrowser *browser, DavResource *reslist, const char *local_path) { cxmutstr wtitle = cx_asprintf("Download to: %s", local_path); - UiObject *dialog = ui_simple_window(wtitle.ptr, NULL); + UiObject *dialog = ui_simple_window(wtitle.ptr); free(wtitle.ptr); DavFileDownload *download = dav_download_create(browser, dialog, reslist, local_path); @@ -1114,7 +1114,7 @@ cxmutstr errormsg = cx_asprintf("Upload failed: %d", upload->sn->error); // TODO: add full error message ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK"); free(errormsg.ptr); - ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_set_state(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); } free(upload->text.ptr); @@ -1134,7 +1134,7 @@ cxmutstr errormsg = cx_asprintf("Proppatch failed: %d", res->sn->error); // TODO: add full error message ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK"); free(errormsg.ptr); - ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_set_state(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; } else { CxList *properties = res->properties->data; @@ -1177,7 +1177,7 @@ ui_threadpool_job(res->dav_queue, ui, jobthr_store_properties, res, uithr_store_properties_finished, res); } - ui_unset_group(ui->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_unset_state(ui->ctx, RESOURCEVIEWER_STATE_MODIFIED); } void dav_resourceviewer_destroy(DavResourceViewer *res) { @@ -1187,7 +1187,7 @@ void dav_resourceviewer_property_remove(DavResourceViewer *res, DavPropertyList *prop) { if(!prop->isnew) { dav_remove_property_ns(res->current, prop->ns, prop->name); - ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_set_state(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; } @@ -1210,7 +1210,7 @@ prop->value_simplified = NULL; prop->update = TRUE; - ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_set_state(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; ui_list_update(res->properties); } @@ -1230,7 +1230,7 @@ prop->update = TRUE; ui_list_append(res->properties, prop); - ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_set_state(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); res->properties_modified = TRUE; ui_list_update(res->properties); } diff -r 3da24640513a -r e57ca2747782 application/settings.c --- a/application/settings.c Sun Dec 07 20:00:33 2025 +0100 +++ b/application/settings.c Sat Dec 13 15:58:58 2025 +0100 @@ -54,10 +54,10 @@ UiListSelection *selection = event->eventdata; SettingsWindow *settings = event->window; if(selection->count > 0) { - ui_set_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); + ui_set_state(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); settings->selected_repo = selection->rows[0]; } else { - ui_unset_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); + ui_unset_state(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); } } @@ -87,7 +87,7 @@ dav_repository_remove_and_free(settings->config, repo); settings_update_repolist(settings); settings->selected_repo = -1; - ui_unset_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); + ui_unset_state(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); } static void editrepo_go_back(UiEvent *event, void *userdata) { @@ -341,10 +341,10 @@ UiListSelection *sel = event->eventdata; if(sel->count > 0) { settings->credentials_location_selected_index = sel->rows[0]; - ui_set_group(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED); + ui_set_state(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED); } else { settings->credentials_location_selected_index = -1; - ui_unset_group(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED); + ui_unset_state(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED); } } @@ -516,13 +516,13 @@ ui_newline(dialog); ui_llabel(dialog, .label = "Type"); - ui_combobox(dialog, .list = wdata->type, .groups = UI_GROUPS(ADDKEY_DIALOG_STATE_NO_IMPORT)); + ui_dropdown(dialog, .list = wdata->type, .states = UI_GROUPS(ADDKEY_DIALOG_STATE_NO_IMPORT)); ui_newline(dialog); ui_llabel(dialog, .value = wdata->message, .colspan = 2); } - ui_set_group(dialog->ctx, ADDKEY_DIALOG_STATE_NO_IMPORT); + ui_set_state(dialog->ctx, ADDKEY_DIALOG_STATE_NO_IMPORT); ui_list_setselection(wdata->type, 0); ui_show(dialog); @@ -547,12 +547,12 @@ DavCfgKey *key = ui_list_get(settings->keys_list, sel->rows[0]); if(key) { settings_edit_key(settings, key); - ui_set_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED); + ui_set_state(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED); } } else { settings->keys_selected_index = -1; settings_clear_key(settings); - ui_unset_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED); + ui_unset_state(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED); } } @@ -626,7 +626,7 @@ PwdStore *pwdstore = get_pwdstore(); pwdstore = pwdstore ? pwdstore_clone(pwdstore) : pwdstore_new(); - UiObject *obj = ui_simple_window("Settings", NULL); + UiObject *obj = ui_simple_window("Settings"); ui_context_closefunc(obj->ctx, settings_close, NULL); SettingsWindow *wdata = ui_malloc(obj->ctx, sizeof(SettingsWindow)); memset(wdata, 0, sizeof(SettingsWindow)); @@ -650,8 +650,8 @@ ui_grid(obj, .margin = 16, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) { ui_hbox(obj, .spacing = 4) { ui_button(obj, .label = "Add", .onclick = repolist_add); - ui_button(obj, .label = "Edit", .onclick = repolist_edit, .groups = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED)); - ui_button(obj, .label = "Remove", .onclick = repolist_remove, .groups = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED)); + ui_button(obj, .label = "Edit", .onclick = repolist_edit, .states = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED)); + ui_button(obj, .label = "Remove", .onclick = repolist_remove, .states = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED)); } ui_newline(obj); @@ -687,7 +687,7 @@ ui_llabel(obj, .label = "Credentials", .style = UI_LABEL_STYLE_TITLE, .colspan = 2); ui_newline(obj); ui_hbox(obj, .spacing = 4, .colspan = 2) { - ui_combobox(obj, .list = wdata->repo_credentials); + ui_dropdown(obj, .list = wdata->repo_credentials); ui_button(obj, .label = "New Credentials", .onclick = credentials_new); } ui_newline(obj); @@ -707,12 +707,12 @@ ui_llabel(obj, .label = "Encryption", .style = UI_LABEL_STYLE_TITLE, .colspan = 2); ui_newline(obj); - ui_checkbox(obj, .label = "Enable client-side encryption", .value = wdata->repo_encryption, .colspan = 2, .enable_group = SETTINGS_STATE_REPO_ENCRYPTION); + ui_checkbox(obj, .label = "Enable client-side encryption", .value = wdata->repo_encryption, .colspan = 2, .enable_state = SETTINGS_STATE_REPO_ENCRYPTION); ui_newline(obj); ui_llabel(obj, .label = "Default key"); ui_hbox(obj, .spacing = 4) { - ui_combobox(obj, .list = wdata->repo_keys, .groups = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION)); - ui_button(obj, .label = "Generate Key", .onclick = keys_add, .onclickdata = "repo", .groups = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION)); + ui_dropdown(obj, .list = wdata->repo_keys, .states = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION)); + ui_button(obj, .label = "Generate Key", .onclick = keys_add, .onclickdata = "repo", .states = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION)); } ui_newline(obj); @@ -727,7 +727,7 @@ ui_llabel(obj, .label = "TLS Version"); ui_hbox0(obj) { - ui_combobox(obj, .list = wdata->repo_tls_versions); + ui_dropdown(obj, .list = wdata->repo_tls_versions); } ui_newline(obj); ui_checkbox(obj, .label = "Disable TLS verification", .value = wdata->repo_disable_verification, .colspan = 2); @@ -751,22 +751,22 @@ ui_vbox(obj, .spacing = 4) { ui_hbox(obj, .spacing = 4) { ui_button(obj, .label = "Add", .onclick = credentials_add); - ui_button(obj, .label = "Remove", .onclick = credentials_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); + ui_button(obj, .label = "Remove", .onclick = credentials_remove, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); } ui_listview(obj, .list = wdata->credentials_users, .fill = TRUE, .onselection = credentials_onselect); } ui_grid(obj, .columnspacing = 30, .rowspacing = 10) { ui_llabel(obj, .label = "Identifier"); - ui_textfield(obj, .value = wdata->credentials_id, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); + ui_textfield(obj, .value = wdata->credentials_id, .hexpand = TRUE, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); ui_newline(obj); ui_llabel(obj, .label = "User"); - ui_textfield(obj, .value = wdata->credentials_user, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); + ui_textfield(obj, .value = wdata->credentials_user, .hexpand = TRUE, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); ui_newline(obj); ui_llabel(obj, .label = "Password"); - ui_passwordfield(obj, .value = wdata->credentials_password, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); + ui_passwordfield(obj, .value = wdata->credentials_password, .hexpand = TRUE, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); ui_newline(obj); @@ -784,13 +784,13 @@ #else ui_callback credentials_activate_callback = NULL; #endif - ui_listview(obj, .list = wdata->credentials_locations, .onactivate = credentials_activate_callback, .onselection = credentials_location_onselect, .colspan = 2, .fill = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); + ui_listview(obj, .list = wdata->credentials_locations, .onactivate = credentials_activate_callback, .onselection = credentials_location_onselect, .colspan = 2, .fill = TRUE, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); ui_vbox(obj, .spacing = 4) { - ui_button(obj, .label = "Add", .onclick = credentials_location_add, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); - ui_button(obj, .label = "Edit", .onclick = credentials_location_edit, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); - ui_button(obj, .label = "Remove", .onclick = credentials_location_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); - ui_button(obj, .label = "Move Up", .onclick = credentials_location_up, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); - ui_button(obj, .label = "Move Down", .onclick = credentials_location_down, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); + ui_button(obj, .label = "Add", .onclick = credentials_location_add, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); + ui_button(obj, .label = "Edit", .onclick = credentials_location_edit, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); + ui_button(obj, .label = "Remove", .onclick = credentials_location_remove, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); + ui_button(obj, .label = "Move Up", .onclick = credentials_location_up, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); + ui_button(obj, .label = "Move Down", .onclick = credentials_location_down, .states = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED)); } } } @@ -802,20 +802,20 @@ ui_vbox(obj, .spacing = 4) { ui_hbox(obj, .spacing = 4) { ui_button(obj, .label = "Add", .onclick = keys_add); - ui_button(obj, .label = "Remove", .onclick = keys_remove, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); + ui_button(obj, .label = "Remove", .onclick = keys_remove, .states = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); } ui_listview(obj, .list = wdata->keys_list, .fill = TRUE, .onselection = keys_onselect, .getvalue = keylist_getvalue); } ui_grid(obj, .columnspacing = 30, .rowspacing = 10) { ui_llabel(obj, .label = "Identifier"); - ui_textfield(obj, .value = wdata->key_name, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); + ui_textfield(obj, .value = wdata->key_name, .states = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); ui_newline(obj); ui_llabel(obj, .label = "Type"); - ui_textfield(obj, .value = wdata->key_type, .groups = UI_GROUPS(SETTINGS_STATE_DISABLED)); + ui_textfield(obj, .value = wdata->key_type, .states = UI_GROUPS(SETTINGS_STATE_DISABLED)); ui_newline(obj); ui_llabel(obj, .label = "File"); - ui_textfield(obj, .value = wdata->key_file, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); + ui_textfield(obj, .value = wdata->key_file, .states = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); } } } @@ -1273,7 +1273,7 @@ ui_list_update(settings->credentials_locations); - ui_set_group(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED); + ui_set_state(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED); } void settings_credentials_clear(SettingsWindow *settings) { @@ -1287,7 +1287,7 @@ ui_list_clear(settings->credentials_locations); ui_list_update(settings->credentials_locations); - ui_unset_group(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED); + ui_unset_state(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED); } int settings_credentials_save(SettingsWindow *settings) { diff -r 3da24640513a -r e57ca2747782 application/window.c --- a/application/window.c Sun Dec 07 20:00:33 2025 +0100 +++ b/application/window.c Sat Dec 13 15:58:58 2025 +0100 @@ -52,25 +52,25 @@ // initialize the browser context menu ui_contextmenu(&contextmenu) { - ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "New File", .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "New File", .onclick = action_newfile, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuseparator(); - //ui_menuitem(.label = "Cut", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - //ui_menuitem(.label = "Copy", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - //ui_menuitem(.label = "Paste", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem(.label = "Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem(.label = "Delete", .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); - ui_menuitem(.label = "Select All", .onclick = action_selectall, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + //ui_menuitem(.label = "Cut", .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + //ui_menuitem(.label = "Copy", .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + //ui_menuitem(.label = "Paste", .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem(.label = "Download", .onclick = action_download, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem(.label = "Delete", .onclick = action_delete, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); + ui_menuitem(.label = "Select All", .onclick = action_selectall, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION)); ui_menuseparator(); - ui_menuitem(.label = "Rename", .onclick = action_rename, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem(.label = "Rename", .onclick = action_rename, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); ui_menuseparator(); - ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); - ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem("Open Properties", .onclick = action_open_properties, .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); + ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .states = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION)); } } UiObject* window_create(void) { - UiObject* obj = ui_window("iDAV", NULL); + UiObject* obj = ui_window("iDAV"); ui_window_size(obj, 900, 700); MainWindow* wdata = ui_malloc(obj->ctx, sizeof (MainWindow)); @@ -186,12 +186,12 @@ void resourceviewer_new(DavBrowser *browser, const char *path, DavResourceViewType type) { const char *name = util_resource_name(path); - UiObject *win = ui_simple_window(name, NULL); + UiObject *win = ui_simple_window(name); ui_window_size(win, 600, 600); ui_headerbar(win, .showtitle = TRUE) { ui_headerbar_start(win) { - ui_button(win, .label = "Save", .onclick = action_resourceviewer_save, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_MODIFIED)); + ui_button(win, .label = "Save", .onclick = action_resourceviewer_save, .states = UI_GROUPS(RESOURCEVIEWER_STATE_MODIFIED)); } } @@ -264,8 +264,8 @@ .onactivate = action_resourceviewer_property_activate); ui_hbox(win, .margin = 4, .spacing = 4) { ui_button(win, .label = "Add", .onclick = action_resourceviewer_property_add); - ui_button(win, .label = "Edit", .onclick = action_resourceviewer_property_edit, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED)); - ui_button(win, .label = "Remove", .onclick = action_resourceviewer_property_remove, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED)); + ui_button(win, .label = "Edit", .onclick = action_resourceviewer_property_edit, .states = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED)); + ui_button(win, .label = "Remove", .onclick = action_resourceviewer_property_remove, .states = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED)); } } } @@ -485,9 +485,9 @@ void action_list_selection(UiEvent *event, void *data) { UiListSelection *selection = event->eventdata; if (selection->count > 0) { - ui_set_group(event->obj->ctx, APP_STATE_BROWSER_SELECTION); + ui_set_state(event->obj->ctx, APP_STATE_BROWSER_SELECTION); } else { - ui_unset_group(event->obj->ctx, APP_STATE_BROWSER_SELECTION); + ui_unset_state(event->obj->ctx, APP_STATE_BROWSER_SELECTION); } } @@ -548,7 +548,7 @@ void action_resourceviewer_text_modified(UiEvent *event, void *data) { DavResourceViewer *doc = event->document; if(doc->loaded) { - ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); + ui_set_state(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED); } } @@ -561,10 +561,10 @@ DavResourceViewer *doc = event->document; UiListSelection *selection = event->eventdata; if(selection->count == 1) { - ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED); + ui_set_state(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED); doc->selected_property = ui_list_get(doc->properties, selection->rows[0]); } else { - ui_unset_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED); + ui_unset_state(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED); doc->selected_property = NULL; } } @@ -641,11 +641,11 @@ DavResourceViewer *res = data; switch(ui_get(res->property_type)) { case 0: { - ui_unset_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); + ui_unset_state(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); break; } case 1: { - ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); + ui_set_state(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); char *ns = ui_get(res->property_ns); char *nsdef = ui_get(res->property_nsdef); if(strlen(nsdef) == 0) { @@ -690,7 +690,7 @@ ui_newline(obj); ui_llabel(obj, .label = "Namespace Declarations"); - ui_textfield(obj, .hexpand = TRUE, .value = res->property_nsdef, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_XML)); + ui_textfield(obj, .hexpand = TRUE, .value = res->property_nsdef, .states = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_XML)); ui_newline(obj); ui_textarea(obj, .value = res->property_value, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2); @@ -706,7 +706,7 @@ ui_set(res->property_type, 0); ui_set(res->property_nsdef, ""); ui_set(res->property_value, prop->value_full); - ui_unset_group(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); + ui_unset_state(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); } else if(prop->xml) { ui_set(res->property_type, 1); cxmutstr xml; @@ -716,7 +716,7 @@ ui_set(res->property_value, xml.ptr); free(xml.ptr); free(nsdef.ptr); - ui_set_group(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); + ui_set_state(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML); } } else { ui_set(res->property_ns, ""); diff -r 3da24640513a -r e57ca2747782 ucx/allocator.c --- a/ucx/allocator.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/allocator.c Sat Dec 13 15:58:58 2025 +0100 @@ -31,6 +31,35 @@ #include #include +#ifdef _WIN32 +#include +#include +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ps = (unsigned long) sysinfo.dwPageSize; + } + return ps; +} +#else +#include +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + long sc = sysconf(_SC_PAGESIZE); + if (sc < 0) { + // fallback for systems which do not report a value here + ps = 4096; // LCOV_EXCL_LINE + } else { + ps = (unsigned long) sc; + } + } + return ps; +} +#endif + static void *cx_malloc_stdlib( cx_attr_unused void *d, size_t n @@ -79,6 +108,11 @@ void **mem, size_t n ) { + if (n == 0) { + free(*mem); + *mem = NULL; + return 0; + } void *nmem = realloc(*mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -93,6 +127,11 @@ size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + free(*mem); + *mem = NULL; + return 0; + } size_t n; if (cx_szmul(nmemb, size, &n)) { errno = EOVERFLOW; @@ -156,6 +195,11 @@ void **mem, size_t n ) { + if (n == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -171,6 +215,11 @@ size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = cxReallocArray(allocator, *mem, nmemb, size); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -194,3 +243,7 @@ ) { allocator->cl->free(allocator->data, mem); } + +void cxFreeDefault(void *mem) { + cxDefaultAllocator->cl->free(cxDefaultAllocator->data, mem); +} diff -r 3da24640513a -r e57ca2747782 ucx/array_list.c --- a/ucx/array_list.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/array_list.c Sat Dec 13 15:58:58 2025 +0100 @@ -42,10 +42,11 @@ cx_attr_unused CxArrayReallocator *alloc ) { size_t n; + // LCOV_EXCL_START if (cx_szmul(new_capacity, elem_size, &n)) { errno = EOVERFLOW; return NULL; - } + } // LCOV_EXCL_STOP return cxReallocDefault(array, n); } @@ -66,10 +67,11 @@ ) { // check for overflow size_t n; + // LCOV_EXCL_START if (cx_szmul(new_capacity, elem_size, &n)) { errno = EOVERFLOW; return NULL; - } + } // LCOV_EXCL_STOP // retrieve the pointer to the actual allocator const CxAllocator *al = alloc->allocator; @@ -108,13 +110,11 @@ * * @param current_capacity the current capacity of the array * @param needed_capacity the required capacity of the array - * @param maximum_capacity the maximum capacity (given by the data type) * @return the new capacity */ static size_t cx_array_grow_capacity( size_t current_capacity, - size_t needed_capacity, - size_t maximum_capacity + size_t needed_capacity ) { if (current_capacity >= needed_capacity) { return current_capacity; @@ -125,12 +125,7 @@ else if (cap < 1024) alignment = 64; else if (cap < 8192) alignment = 512; else alignment = 1024; - - if (cap - 1 > maximum_capacity - alignment) { - return maximum_capacity; - } else { - return cap - (cap % alignment) + alignment; - } + return cap - (cap % alignment) + alignment; } int cx_array_reserve( @@ -288,7 +283,7 @@ const size_t newsize = oldsize < minsize ? minsize : oldsize; // reallocate if necessary - const size_t newcap = cx_array_grow_capacity(oldcap, newsize, max_size); + const size_t newcap = cx_array_grow_capacity(oldcap, newsize); if (newcap > oldcap) { // check if we need to repair the src pointer uintptr_t targetaddr = (uintptr_t) *target; @@ -372,17 +367,18 @@ if (elem_count == 0) return 0; // overflow check + // LCOV_EXCL_START if (elem_count > SIZE_MAX - *size) { errno = EOVERFLOW; return 1; } + // LCOV_EXCL_STOP // store some counts const size_t old_size = *size; const size_t old_capacity = *capacity; // the necessary capacity is the worst case assumption, including duplicates - const size_t needed_capacity = cx_array_grow_capacity(old_capacity, - old_size + elem_count, SIZE_MAX); + const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + elem_count); // if we need more than we have, try a reallocation if (needed_capacity > old_capacity) { @@ -421,21 +417,23 @@ // while there are both source and buffered elements left, // copy them interleaving while (si < elem_count && bi < new_size) { - // determine how many source elements can be inserted + // determine how many source elements can be inserted. + // the first element that shall not be inserted is the smallest element + // that is strictly larger than the first buffered element + // (located at the index of the infimum plus one). + // the infimum is guaranteed to exist: + // - if all src elements are larger, + // there is no buffer, and this loop is skipped + // - if any src element is smaller or equal, the infimum exists + // - when all src elements that are smaller are copied, the second part + // of this loop body will copy the remaining buffer (emptying it) + // Therefore, the buffer can never contain an element that is smaller + // than any element in the source and the infimum exists. size_t copy_len, bytes_copied; - copy_len = cx_array_binary_search_sup( - src, - elem_count - si, - elem_size, - bptr, - cmp_func + copy_len = cx_array_binary_search_inf( + src, elem_count - si, elem_size, bptr, cmp_func ); - // binary search gives us the smallest index; - // we also want to include equal elements here - while (si + copy_len < elem_count - && cmp_func(bptr, src+copy_len*elem_size) == 0) { - copy_len++; - } + copy_len++; // copy the source elements if (copy_len > 0) { @@ -512,26 +510,17 @@ // duplicates allowed or nothing inserted yet: simply copy everything memcpy(dest, src, elem_size * (elem_count - si)); } else { - if (dest != *target) { - // skip all source elements that equal the last element - char *last = dest - elem_size; - while (si < elem_count) { - if (last != NULL && cmp_func(last, src) == 0) { - src += elem_size; - si++; - (*size)--; - } else { - break; - } - } - } - // we must check the elements in the chunk one by one + // we must check the remaining source elements one by one + // to skip the duplicates. + // Note that no source element can equal the last element in the + // destination, because that would have created an insertion point + // and a buffer, s.t. the above loop already handled the duplicates while (si < elem_count) { // find a chain of elements that can be copied size_t copy_len = 1, skip_len = 0; { const char *left_src = src; - while (si + copy_len < elem_count) { + while (si + copy_len + skip_len < elem_count) { const char *right_src = left_src + elem_size; int d = cmp_func(left_src, right_src); if (d < 0) { @@ -599,7 +588,8 @@ cmp_func, sorted_data, elem_size, elem_count, reallocator, false); } -size_t cx_array_binary_search_inf( +// implementation that finds ANY index +static size_t cx_array_binary_search_inf_impl( const void *arr, size_t size, size_t elem_size, @@ -644,13 +634,6 @@ result = cmp_func(elem, arr_elem); if (result == 0) { // found it! - // check previous elements; - // when they are equal, report the smallest index - arr_elem -= elem_size; - while (pivot_index > 0 && cmp_func(elem, arr_elem) == 0) { - pivot_index--; - arr_elem -= elem_size; - } return pivot_index; } else if (result < 0) { // element is smaller than pivot, continue search left @@ -665,6 +648,24 @@ return result < 0 ? (pivot_index - 1) : pivot_index; } +size_t cx_array_binary_search_inf( + const void *arr, + size_t size, + size_t elem_size, + const void *elem, + cx_compare_func cmp_func +) { + size_t index = cx_array_binary_search_inf_impl( + arr, size, elem_size, elem, cmp_func); + // in case of equality, report the largest index + const char *e = ((const char *) arr) + (index + 1) * elem_size; + while (index + 1 < size && cmp_func(e, elem) == 0) { + e += elem_size; + index++; + } + return index; +} + size_t cx_array_binary_search( const void *arr, size_t size, @@ -690,16 +691,25 @@ const void *elem, cx_compare_func cmp_func ) { - size_t inf = cx_array_binary_search_inf( + size_t index = cx_array_binary_search_inf_impl( arr, size, elem_size, elem, cmp_func ); - if (inf == size) { - // no infimum means, first element is supremum + const char *e = ((const char *) arr) + index * elem_size; + if (index == size) { + // no infimum means the first element is supremum return 0; - } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) { - return inf; + } else if (cmp_func(e, elem) == 0) { + // found an equal element, search the smallest index + e -= elem_size; // e now contains the element at index-1 + while (index > 0 && cmp_func(e, elem) == 0) { + e -= elem_size; + index--; + } + return index; } else { - return inf + 1; + // we already have the largest index of the infimum (by design) + // the next element is the supremum (or there is no supremum) + return index + 1; } } @@ -792,14 +802,13 @@ // guarantee enough capacity if (arl->capacity < list->collection.size + n) { - const size_t new_capacity = cx_array_grow_capacity(arl->capacity, - list->collection.size + n, SIZE_MAX); + const size_t new_capacity = cx_array_grow_capacity(arl->capacity,list->collection.size + n); if (cxReallocateArray( list->collection.allocator, &arl->data, new_capacity, list->collection.elem_size) ) { - return 0; + return 0; // LCOV_EXCL_LINE } arl->capacity = new_capacity; } @@ -843,7 +852,7 @@ &arl->reallocator )) { // array list implementation is "all or nothing" - return 0; + return 0; // LCOV_EXCL_LINE } else { return n; } @@ -868,7 +877,7 @@ &arl->reallocator )) { // array list implementation is "all or nothing" - return 0; + return 0; // LCOV_EXCL_LINE } else { return n; } @@ -895,7 +904,7 @@ if (iter->index < list->collection.size) { if (cx_arl_insert_element(list, iter->index + 1 - prepend, elem) == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } iter->elem_count++; if (prepend != 0) { @@ -905,7 +914,7 @@ return 0; } else { if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } iter->elem_count++; iter->index = list->collection.size; diff -r 3da24640513a -r e57ca2747782 ucx/buffer.c --- a/ucx/buffer.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/buffer.c Sat Dec 13 15:58:58 2025 +0100 @@ -32,28 +32,10 @@ #include #include -#ifdef _WIN32 -#include -#include -static unsigned long system_page_size() { - static unsigned long ps = 0; - if (ps == 0) { - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - ps = sysinfo.dwPageSize; - } - return ps; -} -#define SYSTEM_PAGE_SIZE system_page_size() -#else -#include -#define SYSTEM_PAGE_SIZE sysconf(_SC_PAGESIZE) -#endif - static int buffer_copy_on_write(CxBuffer* buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; void *newspace = cxMalloc(buffer->allocator, buffer->capacity); - if (NULL == newspace) return -1; + if (NULL == newspace) return -1; // LCOV_EXCL_LINE memcpy(newspace, buffer->space, buffer->size); buffer->space = newspace; buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; @@ -78,37 +60,24 @@ buffer->flags = flags; if (!space) { buffer->bytes = cxMalloc(allocator, capacity); - if (buffer->bytes == NULL) { - return -1; // LCOV_EXCL_LINE - } + if (buffer->bytes == NULL) return -1; // LCOV_EXCL_LINE buffer->flags |= CX_BUFFER_FREE_CONTENTS; } else { buffer->bytes = space; } buffer->capacity = capacity; + buffer->max_capacity = SIZE_MAX; buffer->size = 0; buffer->pos = 0; - buffer->flush = NULL; - return 0; } -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -) { - buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); - if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE - memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); - return 0; -} - void cxBufferDestroy(CxBuffer *buffer) { - if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { + if ((buffer->flags & (CX_BUFFER_FREE_CONTENTS | CX_BUFFER_DO_NOT_FREE)) + == CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - cxFreeDefault(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } @@ -122,7 +91,7 @@ allocator = cxDefaultAllocator; } CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer)); - if (buf == NULL) return NULL; + if (buf == NULL) return NULL; // LCOV_EXCL_LINE if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) { return buf; } else { @@ -183,6 +152,35 @@ } +size_t cxBufferPop(CxBuffer *buffer, size_t size, size_t nitems) { + size_t len; + if (cx_szmul(size, nitems, &len)) { + // LCOV_EXCL_START + errno = EOVERFLOW; + return 0; + // LCOV_EXCL_STOP + } + if (len == 0) return 0; + if (len > buffer->size) { + if (size == 1) { + // simple case: everything can be discarded + len = buffer->size; + } else { + // complicated case: misaligned bytes must stay + size_t misalignment = buffer->size % size; + len = buffer->size - misalignment; + } + } + buffer->size -= len; + + // adjust position, if required + if (buffer->pos > buffer->size) { + buffer->pos = buffer->size; + } + + return len / size; +} + void cxBufferClear(CxBuffer *buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) { memset(buffer->bytes, 0, buffer->size); @@ -200,36 +198,13 @@ return buffer->pos >= buffer->size; } -int cxBufferMinimumCapacity( - CxBuffer *buffer, - size_t newcap -) { - if (newcap <= buffer->capacity) { +int cxBufferReserve(CxBuffer *buffer, size_t newcap) { + if (newcap == buffer->capacity) { return 0; } - - unsigned long pagesize = SYSTEM_PAGE_SIZE; - // if page size is larger than 64 KB - for some reason - truncate to 64 KB - if (pagesize > 65536) pagesize = 65536; - if (newcap < pagesize) { - // when smaller as one page, map to the next power of two - newcap--; - newcap |= newcap >> 1; - newcap |= newcap >> 2; - newcap |= newcap >> 4; - // last operation only needed for pages larger 4096 bytes - // but if/else would be more expensive than just doing this - newcap |= newcap >> 8; - newcap++; - } else { - // otherwise, map to a multiple of the page size - newcap -= newcap % pagesize; - newcap += pagesize; - // note: if newcap is already page aligned, - // this gives a full additional page (which is good) + if (newcap > buffer->max_capacity) { + return -1; } - - const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; if (buffer->flags & force_copy_flags) { void *newspace = cxMalloc(buffer->allocator, newcap); @@ -242,13 +217,60 @@ return 0; } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { + buffer->flags |= CX_BUFFER_FREE_CONTENTS; buffer->capacity = newcap; + if (buffer->size > newcap) { + buffer->size = newcap; + } return 0; } else { return -1; // LCOV_EXCL_LINE } } +int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) { + if (capacity < buffer->capacity) { + return -1; + } + buffer->max_capacity = capacity; + return 0; +} + +int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { + if (newcap <= buffer->capacity) { + return 0; + } + if (newcap > buffer->max_capacity) { + return -1; + } + if (newcap < buffer->max_capacity) { + unsigned long pagesize = cx_system_page_size(); + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + if (newcap > buffer->max_capacity) { + newcap = buffer->max_capacity; + } + } + return cxBufferReserve(buffer, newcap); +} + void cxBufferShrink( CxBuffer *buffer, size_t reserve @@ -271,60 +293,15 @@ } } -static size_t cx_buffer_flush_helper( - const CxBuffer *buffer, - const unsigned char *src, - size_t size, - size_t nitems -) { - // flush data from an arbitrary source - // does not need to be the buffer's contents - size_t max_items = buffer->flush->blksize / size; - size_t fblocks = 0; - size_t flushed_total = 0; - while (nitems > 0 && fblocks < buffer->flush->blkmax) { - fblocks++; - size_t items = nitems > max_items ? max_items : nitems; - size_t flushed = buffer->flush->wfunc( - src, size, items, buffer->flush->target); - if (flushed > 0) { - flushed_total += flushed; - src += flushed * size; - nitems -= flushed; - } else { - // if no bytes can be flushed out anymore, we give up - break; - } - } - return flushed_total; -} - -static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { - // flush the current contents of the buffer - unsigned char *space = buffer->bytes; - size_t remaining = buffer->pos / size; - size_t flushed_total = cx_buffer_flush_helper( - buffer, space, size, remaining); - - // shift the buffer left after flushing - // IMPORTANT: up to this point, copy on write must have been - // performed already, because we can't do error handling here - cxBufferShiftLeft(buffer, flushed_total*size); - - return flushed_total; -} - -size_t cxBufferFlush(CxBuffer *buffer) { - if (buffer_copy_on_write(buffer)) return 0; - return cx_buffer_flush_impl(buffer, 1); -} - size_t cxBufferWrite( const void *ptr, size_t size, size_t nitems, CxBuffer *buffer ) { + // trivial case + if (size == 0 || nitems == 0) return 0; + // optimize for easy case if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { if (buffer_copy_on_write(buffer)) return 0; @@ -336,98 +313,52 @@ return nitems; } - size_t len, total_flushed = 0; -cx_buffer_write_retry: + size_t len; if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; - return total_flushed; + return 0; } if (buffer->pos > SIZE_MAX - len) { errno = EOVERFLOW; - return total_flushed; + return 0; } + const size_t required = buffer->pos + len; - size_t required = buffer->pos + len; - bool perform_flush = false; + // check if we need to auto-extend if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - if (buffer->flush != NULL && required > buffer->flush->threshold) { - perform_flush = true; - } else { - if (cxBufferMinimumCapacity(buffer, required)) { - return total_flushed; // LCOV_EXCL_LINE - } - } - } else { - if (buffer->flush != NULL) { - perform_flush = true; - } else { - // truncate data, if we can neither extend nor flush - len = buffer->capacity - buffer->pos; - if (size > 1) { - len -= len % size; - } - nitems = len / size; + size_t newcap = required < buffer->max_capacity + ? required : buffer->max_capacity; + if (cxBufferMinimumCapacity(buffer, newcap)) { + return 0; // LCOV_EXCL_LINE } } } + // check again and truncate data if capacity is still not enough + if (required > buffer->capacity) { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len % size; + } + nitems = len / size; + } + // check here and not above because of possible truncation if (len == 0) { - return total_flushed; + return 0; } // check if we need to copy if (buffer_copy_on_write(buffer)) return 0; // perform the operation - if (perform_flush) { - size_t items_flushed; - if (buffer->pos == 0) { - // if we don't have data in the buffer, but are instructed - // to flush, it means that we are supposed to relay the data - items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); - if (items_flushed == 0) { - // we needed to relay data, but could not flush anything - // i.e. we have to give up to avoid endless trying - return 0; - } - nitems -= items_flushed; - total_flushed += items_flushed; - if (nitems > 0) { - ptr = ((unsigned char*)ptr) + items_flushed * size; - goto cx_buffer_write_retry; - } - return total_flushed; - } else { - items_flushed = cx_buffer_flush_impl(buffer, size); - if (items_flushed == 0) { - // flush target is full, let's try to truncate - size_t remaining_space; - if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - remaining_space = buffer->flush->threshold > buffer->pos - ? buffer->flush->threshold - buffer->pos - : 0; - } else { - remaining_space = buffer->capacity > buffer->pos - ? buffer->capacity - buffer->pos - : 0; - } - nitems = remaining_space / size; - if (nitems == 0) { - return total_flushed; - } - } - goto cx_buffer_write_retry; - } - } else { - memcpy(buffer->bytes + buffer->pos, ptr, len); - buffer->pos += len; - if (buffer->pos > buffer->size) { - buffer->size = buffer->pos; - } - return total_flushed + nitems; + memcpy(buffer->bytes + buffer->pos, ptr, len); + buffer->pos += len; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; } + return nitems; } size_t cxBufferAppend( @@ -436,20 +367,13 @@ size_t nitems, CxBuffer *buffer ) { - size_t pos = buffer->pos; - size_t append_pos = buffer->size; - buffer->pos = append_pos; - size_t written = cxBufferWrite(ptr, size, nitems, buffer); - // the buffer might have been flushed - // we must compute a possible delta for the position - // expected: pos = append_pos + written - // -> if this is not the case, there is a delta - size_t delta = append_pos + written*size - buffer->pos; - if (delta > pos) { - buffer->pos = 0; - } else { - buffer->pos = pos - delta; - } + // trivial case + if (size == 0 || nitems == 0) return 0; + + const size_t pos = buffer->pos; + buffer->pos = buffer->size; + const size_t written = cxBufferWrite(ptr, size, nitems, buffer); + buffer->pos = pos; return written; } @@ -467,19 +391,35 @@ } int cxBufferTerminate(CxBuffer *buffer) { - if (0 == cxBufferPut(buffer, 0)) { - buffer->size = buffer->pos - 1; - return 0; + // try to extend / shrink the buffer + if (buffer->pos >= buffer->capacity) { + if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == 0) { + return -1; + } + if (cxBufferReserve(buffer, buffer->pos + 1)) { + return -1; // LCOV_EXCL_LINE + } } else { - return -1; + buffer->size = buffer->pos; + cxBufferShrink(buffer, 1); + // set the capacity explicitly, in case shrink was skipped due to CoW + buffer->capacity = buffer->size + 1; } + + // check if we are still on read-only memory + if (buffer_copy_on_write(buffer)) return -1; + + // write the terminator and exit + buffer->space[buffer->pos] = '\0'; + return 0; } -size_t cxBufferPutString( - CxBuffer *buffer, - const char *str -) { - return cxBufferWrite(str, 1, strlen(str), buffer); +size_t cx_buffer_put_string(CxBuffer *buffer, cxstring str) { + return cxBufferWrite(str.ptr, 1, str.length, buffer); +} + +size_t cx_buffer_append_string(CxBuffer *buffer, cxstring str) { + return cxBufferAppend(str.ptr, 1, str.length, buffer); } size_t cxBufferRead( diff -r 3da24640513a -r e57ca2747782 ucx/cx/allocator.h --- a/ucx/cx/allocator.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/allocator.h Sat Dec 13 15:58:58 2025 +0100 @@ -146,6 +146,17 @@ const CxAllocator *allocator, void *data); /** + * Returns the system's memory page size. + * + * If the page size cannot be retrieved from the system, + * a default of 4096 bytes is assumed. + * + * @return the system's memory page size in bytes + */ +cx_attr_nodiscard +CX_EXPORT unsigned long cx_system_page_size(void); + +/** * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * @@ -428,9 +439,9 @@ */ #define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__) /** - * Convenience macro that invokes cxFree() with the cxDefaultAllocator. + * Convenience function that invokes cxFree() with the cxDefaultAllocator. */ -#define cxFreeDefault(...) cxFree(cxDefaultAllocator, __VA_ARGS__) +CX_EXPORT void cxFreeDefault(void *mem); #ifdef __cplusplus } // extern "C" diff -r 3da24640513a -r e57ca2747782 ucx/cx/array_list.h --- a/ucx/cx/array_list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/array_list.h Sat Dec 13 15:58:58 2025 +0100 @@ -676,6 +676,9 @@ * in @p arr that is less or equal to @p elem with respect to @p cmp_func. * When no such element exists, @p size is returned. * + * When such an element exists more than once, the largest index of all those + * elements is returned. + * * If @p elem is contained in the array, this is identical to * #cx_array_binary_search(). * @@ -698,6 +701,9 @@ /** * Searches an item in a sorted array. * + * When such an element exists more than once, the largest index of all those + * elements is returned. + * * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * @@ -722,6 +728,9 @@ * in @p arr that is greater or equal to @p elem with respect to @p cmp_func. * When no such element exists, @p size is returned. * + * When such an element exists more than once, the smallest index of all those + * elements is returned. + * * If @p elem is contained in the array, this is identical to * #cx_array_binary_search(). * diff -r 3da24640513a -r e57ca2747782 ucx/cx/buffer.h --- a/ucx/cx/buffer.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/buffer.h Sat Dec 13 15:58:58 2025 +0100 @@ -48,6 +48,7 @@ #include "common.h" #include "allocator.h" +#include "string.h" #ifdef __cplusplus extern "C" { @@ -89,6 +90,13 @@ #define CX_BUFFER_COPY_ON_EXTEND 0x08 /** + * If this flag is enabled, the buffer will never free its contents regardless of #CX_BUFFER_FREE_CONTENTS. + * + * This is useful, for example, when you want to keep a pointer to the data after destroying the buffer. + */ +#define CX_BUFFER_DO_NOT_FREE 0x10 + +/** * Function pointer for cxBufferWrite that is compatible with cx_write_func. * @see cx_write_func */ @@ -99,59 +107,6 @@ */ #define cxBufferReadFunc ((cx_read_func) cxBufferRead) -/** - * Configuration for automatic flushing. - */ -struct cx_buffer_flush_config_s { - /** - * The buffer may not extend beyond this threshold before starting to flush. - * - * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. - * The threshold will be the maximum capacity the buffer is extended to - * before flushing. - */ - size_t threshold; - /** - * The block size for the elements to flush. - */ - size_t blksize; - /** - * The maximum number of blocks to flush in one cycle. - * - * @attention while it is guaranteed that cxBufferFlush() will not flush - * more blocks, this is not necessarily the case for cxBufferWrite(). - * After performing a flush cycle, cxBufferWrite() will retry the write - * operation and potentially trigger another flush cycle, until the - * flush target accepts no more data. - */ - size_t blkmax; - - /** - * The target for the write function. - */ - void *target; - - /** - * The write-function used for flushing. - * If NULL, the flushed content gets discarded. - */ - cx_write_func wfunc; -}; - -/** - * Type alias for the flush configuration struct. - * - * @code - * struct cx_buffer_flush_config_s { - * size_t threshold; - * size_t blksize; - * size_t blkmax; - * void *target; - * cx_write_func wfunc; - * }; - * @endcode - */ -typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; /** Structure for the UCX buffer data. */ struct cx_buffer_s { @@ -168,16 +123,12 @@ }; /** The allocator to use for automatic memory management. */ const CxAllocator *allocator; - /** - * Optional flush configuration - * - * @see cxBufferEnableFlushing() - */ - CxBufferFlushConfig *flush; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ size_t capacity; + /** Maximum capacity that this buffer may grow to. */ + size_t max_capacity; /** Current size of the buffer content. */ size_t size; /** @@ -231,23 +182,6 @@ const CxAllocator *allocator, int flags); /** - * Configures the buffer for flushing. - * - * Flushing can happen automatically when data is written - * to the buffer (see cxBufferWrite()) or manually when - * cxBufferFlush() is called. - * - * @param buffer the buffer - * @param config the flush configuration - * @retval zero success - * @retval non-zero failure - * @see cxBufferFlush() - * @see cxBufferWrite() - */ -cx_attr_nonnull -CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config); - -/** * Destroys the buffer contents. * * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled. @@ -388,6 +322,20 @@ CX_EXPORT int cxBufferSeek(CxBuffer *buffer, off_t offset, int whence); /** + * Discards items from the end of the buffer. + * + * When the current position points to a byte that gets discarded, + * the position is set to the buffer size. + * + * @param buffer the buffer + * @param size the size of one item + * @param nitems the number of items to discard + * @return the actual number of discarded items + */ +cx_attr_nonnull +CX_EXPORT size_t cxBufferPop(CxBuffer *buffer, size_t size, size_t nitems); + +/** * Clears the buffer by resetting the position and deleting the data. * * The data is deleted by zeroing it with a call to memset(). @@ -425,11 +373,51 @@ cx_attr_nonnull cx_attr_nodiscard CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); +/** + * Ensures that the buffer has the required capacity. + * + * If the current capacity is not sufficient, the buffer will be extended. + * If the current capacity is larger, the buffer is shrunk and superfluous + * content is discarded. + * + * This function will reserve no more bytes than requested, in contrast to + * cxBufferMinimumCapacity(), which may reserve more bytes to improve the + * number of future necessary reallocations. + * + * @param buffer the buffer + * @param capacity the required capacity for this buffer + * @retval zero on success + * @retval non-zero on allocation failure + * @see cxBufferShrink() + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity); + +/** + * Limits the buffer's capacity. + * + * If the current capacity is already larger, this function fails and returns + * non-zero. + * + * The capacity limit will affect auto-extension features, as well as future + * calls to cxBufferMinimumCapacity() and cxBufferReserve(). + * + * @param buffer the buffer + * @param capacity the maximum allowed capacity for this buffer + * @retval zero the limit is applied + * @retval non-zero the new limit is smaller than the current capacity + * @see cxBufferReserve() + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +CX_EXPORT int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity); /** * Ensures that the buffer has a minimum capacity. * - * If the current capacity is not sufficient, the buffer will be extended. + * If the current capacity is not sufficient, the buffer will be generously + * extended. * * The new capacity will be a power of two until the system's page size is reached. * Then, the new capacity will be a multiple of the page size. @@ -438,6 +426,8 @@ * @param capacity the minimum required capacity for this buffer * @retval zero the capacity was already sufficient or successfully increased * @retval non-zero on allocation failure + * @see cxBufferMaximumCapacity() + * @see cxBufferReserve() * @see cxBufferShrink() */ cx_attr_nonnull @@ -457,6 +447,7 @@ * * @param buffer the buffer * @param reserve the number of bytes that shall remain reserved + * @see cxBufferReserve() * @see cxBufferMinimumCapacity() */ cx_attr_nonnull @@ -465,33 +456,13 @@ /** * Writes data to a CxBuffer. * - * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position, and the position of the buffer is increased - * by the number of bytes written. - * - * If flushing is enabled and the buffer needs to flush, the data is flushed to - * the target until the target signals that it cannot take more data by - * returning zero via the respective write function. In that case, the remaining - * data in this buffer is shifted to the beginning of this buffer so that the - * newly available space can be used to append as much data as possible. - * - * This function only stops writing more elements when the flush target and this - * buffer are both incapable of taking more data or all data has been written. + * If auto-extension is enabled, the buffer's capacity is automatically + * increased when it is not large enough to hold all data. + * By default, the capacity grows indefinitely, unless limited with + * cxBufferMaximumCapacity(). + * When auto-extension fails, this function writes no data and returns zero. * - * If, after flushing, the number of items that shall be written still exceeds - * the capacity or flush threshold, this function tries to write all items directly - * to the flush target, if possible. - * - * The number returned by this function is the number of elements from - * @c ptr that could be written to either the flush target or the buffer. - * That means it does @em not include the number of items that were already in - * the buffer and were also flushed during the process. - * - * @attention - * When @p size is larger than one and the contents of the buffer are not aligned - * with @p size, flushing stops after all complete items have been flushed, leaving - * the misaligned part in the buffer. - * Afterward, this function only writes as many items as possible to the buffer. + * The position of the buffer is moved alongside the written data. * * @note The signature is compatible with the fwrite() family of functions. * @@ -531,62 +502,6 @@ size_t nitems, CxBuffer *buffer); /** - * Performs a single flush-run on the specified buffer. - * - * Does nothing when the position in the buffer is zero. - * Otherwise, the data until the current position minus - * one is considered for flushing. - * Note carefully that flushing will never exceed the - * current @em position, even when the size of the - * buffer is larger than the current position. - * - * One flush run will try to flush @c blkmax many - * blocks of size @c blksize until either the @p buffer - * has no more data to flush or the write function - * used for flushing returns zero. - * - * The buffer is shifted left for that many bytes - * the flush operation has successfully flushed. - * - * @par Example 1 - * Assume you have a buffer with size 340 and you are - * at position 200. The flush configuration is - * @c blkmax=4 and @c blksize=64 . - * Assume that the entire flush operation is successful. - * All 200 bytes on the left-hand-side from the current - * position are written. - * That means the size of the buffer is now 140 and the - * position is zero. - * - * @par Example 2 - * Same as Example 1, but now the @c blkmax is 1. - * The size of the buffer is now 276, and the position is 136. - * - * @par Example 3 - * Same as Example 1, but now assume the flush target - * only accepts 100 bytes before returning zero. - * That means the flush operation manages to flush - * one complete block and one partial block, ending - * up with a buffer with size 240 and position 100. - * - * @remark Just returns zero when flushing was not enabled with - * cxBufferEnableFlushing(). - * - * @remark When the buffer uses copy-on-write, the memory - * is copied first, before attempting any flush. - * This is, however, considered an erroneous use of the - * buffer because it makes little sense to put - * readonly data into an UCX buffer for flushing instead - * of writing it directly to the target. - * - * @param buffer the buffer - * @return the number of successfully flushed bytes - * @see cxBufferEnableFlushing() - */ -cx_attr_nonnull -CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer); - -/** * Reads data from a CxBuffer. * * The position of the buffer is increased by the number of bytes read. @@ -610,8 +525,9 @@ * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature - * is disabled or the buffer extension fails, @c EOF is returned. + * the buffer capacity is extended, unless a limit set by + * cxBufferMaximumCapacity() is reached. + * If the feature is disabled or the buffer extension fails, @c EOF is returned. * * On successful writing, the position of the buffer is increased. * @@ -620,8 +536,8 @@ * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has been written or @c EOF when the end of the stream is - * reached, and automatic extension is not enabled or not possible + * @return the byte that has been written or @c EOF when the end of the + * stream is reached, and automatic extension is not enabled or not possible * @see cxBufferTerminate() */ cx_attr_nonnull @@ -630,28 +546,61 @@ /** * Writes a terminating zero to a buffer at the current position. * - * If successful, sets the size to the current position and advances the position by one. + * If successful, also sets the size to the current position and shrinks the buffer. * * The purpose of this function is to have the written data ready to be used as * a C string with the buffer's size being the length of that string. * * @param buffer the buffer to write to * @return zero, if the terminator could be written, non-zero otherwise + * @see cxBufferShrink() */ cx_attr_nonnull CX_EXPORT int cxBufferTerminate(CxBuffer *buffer); /** - * Writes a string to a buffer. - * - * This is a convenience function for cxBufferWrite(str, 1, strlen(str), buffer). + * Internal function - do not use. * * @param buffer the buffer - * @param str the zero-terminated string + * @param str the string * @return the number of bytes written + * @see cxBufferPutString() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_buffer_put_string(CxBuffer *buffer, cxstring str); + +/** + * Writes a string to a buffer with cxBufferWrite(). + * + * @param buffer (@c CxBuffer*) the buffer + * @param str (any string) the zero-terminated string + * @return (@c size_t) the number of bytes written + * @see cxBufferWrite() + * @see cx_strcast() */ -cx_attr_nonnull cx_attr_cstr_arg(2) -CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str); +#define cxBufferPutString(buffer, str) cx_buffer_put_string(buffer, cx_strcast(str)) + +/** + * Internal function - do not use. + * + * @param buffer the buffer + * @param str the string + * @return the number of bytes written + * @see cxBufferPutString() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_buffer_append_string(CxBuffer *buffer, cxstring str); + +/** + * Appends a string to a buffer with cxBufferAppend(). + * + * @param buffer (@c CxBuffer*) the buffer + * @param str (any string) the zero-terminated string + * @return (@c size_t) the number of bytes written + * @see cxBufferAppend() + * @see cx_strcast() + */ +#define cxBufferAppendString(buffer, str) cx_buffer_append_string(buffer, cx_strcast(str)) /** * Gets a character from a buffer. diff -r 3da24640513a -r e57ca2747782 ucx/cx/common.h --- a/ucx/cx/common.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/common.h Sat Dec 13 15:58:58 2025 +0100 @@ -80,10 +80,10 @@ #define UCX_COMMON_H /** Major UCX version as integer constant. */ -#define UCX_VERSION_MAJOR 3 +#define UCX_VERSION_MAJOR 4 /** Minor UCX version as integer constant. */ -#define UCX_VERSION_MINOR 1 +#define UCX_VERSION_MINOR 0 /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) @@ -284,6 +284,9 @@ */ #define CX_INLINE __attribute__((always_inline)) static inline #else +/** + * Declares a function to be inlined. + */ #define CX_INLINE static inline #endif /** diff -r 3da24640513a -r e57ca2747782 ucx/cx/json.h --- a/ucx/cx/json.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/json.h Sat Dec 13 15:58:58 2025 +0100 @@ -41,6 +41,7 @@ #include "string.h" #include "buffer.h" #include "array_list.h" +#include "map.h" #include @@ -188,9 +189,10 @@ */ typedef struct cx_json_array_s CxJsonArray; /** - * Type alias for the JSON object struct. + * Type alias for the map representing a JSON object. + * The map contains pointers of type @c CxJsonValue. */ -typedef struct cx_json_object_s CxJsonObject; +typedef CxMap* CxJsonObject; /** * Type alias for a JSON string. */ @@ -209,46 +211,13 @@ typedef enum cx_json_literal CxJsonLiteral; /** - * Type alias for a key/value pair in a JSON object. - */ -typedef struct cx_json_obj_value_s CxJsonObjValue; - -/** * JSON array structure. */ struct cx_json_array_s { /** * The array data. */ - CX_ARRAY_DECLARE(CxJsonValue*, array); -}; - -/** - * JSON object structure. - */ -struct cx_json_object_s { - /** - * The key/value entries. - */ - CX_ARRAY_DECLARE(CxJsonObjValue, values); - /** - * The original indices to reconstruct the order in which the members were added. - */ - size_t *indices; -}; - -/** - * Structure for a key/value entry in a JSON object. - */ -struct cx_json_obj_value_s { - /** - * The key (or name in JSON terminology) of the value. - */ - cxmutstr name; - /** - * The value. - */ - CxJsonValue *value; + CX_ARRAY_DECLARE(CxJsonValue*, data); }; /** @@ -295,7 +264,7 @@ * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; - } value; + }; }; /** @@ -349,11 +318,11 @@ CxJsonValue *parsed; /** - * A pointer to an intermediate state of a currently parsed object member. + * The name of a not yet completely parsed object member. * * Never access this value manually. */ - CxJsonObjValue uncompleted_member; + cxmutstr uncompleted_member_name; /** * State stack. @@ -439,10 +408,6 @@ */ bool pretty; /** - * Set false to output the members in the order in which they were added. - */ - bool sort_members; - /** * The maximum number of fractional digits in a number value. * The default value is 6 and values larger than 15 are reduced to 15. * Note that the actual number of digits may be lower, depending on the concrete number. @@ -508,6 +473,33 @@ CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value, cx_write_func wfunc, const CxJsonWriter* settings); + +/** + * Produces a compact string representation of the specified JSON value. + * + * @param value the JSON value + * @param allocator the allocator for the string + * @return the produced string + * @see cxJsonWrite() + * @see cxJsonWriterCompact() + * @see cxJsonToPrettyString() + */ +cx_attr_nonnull_arg(1) +CX_EXPORT cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator); + +/** + * Produces a pretty string representation of the specified JSON value. + * + * @param value the JSON value + * @param allocator the allocator for the string + * @return the produced string + * @see cxJsonWrite() + * @see cxJsonWriterPretty() + * @see cxJsonToString() + */ +cx_attr_nonnull_arg(1) +CX_EXPORT cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator); + /** * Initializes the JSON interface. * @@ -530,8 +522,8 @@ /** * Destroys and re-initializes the JSON interface. * - * You might want to use this to reset the parser after - * encountering a syntax error. + * You must use this to reset the parser after encountering a syntax error + * if you want to continue using it. * * @param json the JSON interface */ @@ -556,7 +548,7 @@ * @retval non-zero internal allocation error * @see cxJsonFill() */ -cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_nonnull_arg(1) cx_attr_access_r(2, 3) CX_EXPORT int cxJsonFilln(CxJson *json, const char *buf, size_t len); @@ -592,6 +584,36 @@ */ #define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) + +/** + * Internal function - use cxJsonFromString() instead. + * + * @param allocator the allocator for the JSON value + * @param str the string to parse + * @param value a pointer where the JSON value shall be stored to + * @return status code + */ +cx_attr_nonnull_arg(3) +CX_EXPORT CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value); + +/** + * Parses a string into a JSON value. + * + * @param allocator (@c CxAllocator*) the allocator for the JSON value + * @param str (any string) the string to parse + * @param value (@c CxJsonValue**) a pointer where the JSON value shall be stored to + * @retval CX_JSON_NO_ERROR success + * @retval CX_JSON_NO_DATA the string was empty or blank + * @retval CX_JSON_INCOMPLETE_DATA the string unexpectedly ended + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for the CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +#define cxJsonFromString(allocator, str, value) \ + cx_json_from_string(allocator, cx_strcast(str), value) + /** * Creates a new (empty) JSON object. * @@ -641,28 +663,27 @@ /** * Creates a new JSON string. * + * Internal function - use cxJsonCreateString() instead. + * * @param allocator the allocator to use * @param str the string data * @return the new JSON value or @c NULL if allocation fails - * @see cxJsonCreateString() * @see cxJsonObjPutString() - * @see cxJsonArrAddStrings() + * @see cxJsonArrAddCxStrings() */ -cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2) -CX_EXPORT CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); +cx_attr_nodiscard +CX_EXPORT CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str); /** * Creates a new JSON string. * - * @param allocator the allocator to use - * @param str the string data - * @return the new JSON value or @c NULL if allocation fails - * @see cxJsonCreateCxString() - * @see cxJsonObjPutCxString() + * @param allocator (@c CxAllocator*) the allocator to use + * @param str the string + * @return (@c CxJsonValue*) the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutString() * @see cxJsonArrAddCxStrings() */ -cx_attr_nodiscard -CX_EXPORT CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); +#define cxJsonCreateString(allocator, str) cx_json_create_string(allocator, cx_strcast(str)) /** * Creates a new JSON literal. @@ -760,10 +781,7 @@ /** * Adds or replaces a value within a JSON object. * - * The value will be directly added and not copied. - * - * @note If a value with the specified @p name already exists, - * it will be (recursively) freed with its own allocator. + * Internal function - use cxJsonObjPut(). * * @param obj the JSON object * @param name the name of the value @@ -772,11 +790,29 @@ * @retval non-zero allocation failure */ cx_attr_nonnull -CX_EXPORT int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); +CX_EXPORT int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child); + +/** + * Adds or replaces a value within a JSON object. + * + * The value will be directly added and not copied. + * + * @note If a value with the specified @p name already exists, + * it will be (recursively) freed with its own allocator. + * + * @param obj (@c CxJsonValue*) the JSON object + * @param name (any string) the name of the value + * @param child (@c CxJsonValue*) the value + * @retval zero success + * @retval non-zero allocation failure + */ +#define cxJsonObjPut(obj, name, child) cx_json_obj_put(obj, cx_strcast(name), child) /** * Creates a new JSON object and adds it to an existing object. * + * Internal function - use cxJsonObjPutObj(). + * * @param obj the target JSON object * @param name the name of the new value * @return the new value or @c NULL if allocation fails @@ -784,11 +820,24 @@ * @see cxJsonCreateObj() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON object and adds it to an existing object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateObj() + */ +#define cxJsonObjPutObj(obj, name) cx_json_obj_put_obj(obj, cx_strcast(name)) /** * Creates a new JSON array and adds it to an object. * + * Internal function - use cxJsonObjPutArr(). + * * @param obj the target JSON object * @param name the name of the new value * @return the new value or @c NULL if allocation fails @@ -796,11 +845,24 @@ * @see cxJsonCreateArr() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON array and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateArr() + */ +#define cxJsonObjPutArr(obj, name) cx_json_obj_put_arr(obj, cx_strcast(name)) /** * Creates a new JSON number and adds it to an object. * + * Internal function - use cxJsonObjPutNumber(). + * * @param obj the target JSON object * @param name the name of the new value * @param num the numeric value @@ -809,11 +871,25 @@ * @see cxJsonCreateNumber() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); +CX_EXPORT CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num); + +/** + * Creates a new JSON number and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param num (@c double) the numeric value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateNumber() + */ +#define cxJsonObjPutNumber(obj, name, num) cx_json_obj_put_number(obj, cx_strcast(name), num) /** * Creates a new JSON number, based on an integer, and adds it to an object. * + * Internal function - use cxJsonObjPutInteger(). + * * @param obj the target JSON object * @param name the name of the new value * @param num the numeric value @@ -822,12 +898,24 @@ * @see cxJsonCreateInteger() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); +CX_EXPORT CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num); + +/** + * Creates a new JSON number, based on an integer, and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param num (@c int64_t) the numeric value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateInteger() + */ +#define cxJsonObjPutInteger(obj, name, num) cx_json_obj_put_integer(obj, cx_strcast(name), num) /** * Creates a new JSON string and adds it to an object. * - * The string data is copied. + * Internal function - use cxJsonObjPutString() * * @param obj the target JSON object * @param name the name of the new value @@ -836,27 +924,28 @@ * @see cxJsonObjPut() * @see cxJsonCreateString() */ -cx_attr_nonnull cx_attr_cstr_arg(3) -CX_EXPORT CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); +cx_attr_nonnull +CX_EXPORT CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str); /** * Creates a new JSON string and adds it to an object. * * The string data is copied. * - * @param obj the target JSON object - * @param name the name of the new value - * @param str the string data - * @return the new value or @c NULL if allocation fails + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param str (any string) the string data + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails * @see cxJsonObjPut() - * @see cxJsonCreateCxString() + * @see cxJsonCreateString() */ -cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); +#define cxJsonObjPutString(obj, name, str) cx_json_obj_put_string(obj, cx_strcast(name), cx_strcast(str)) /** * Creates a new JSON literal and adds it to an object. * + * Internal function - use cxJsonObjPutLiteral(). + * * @param obj the target JSON object * @param name the name of the new value * @param lit the type of literal @@ -865,7 +954,19 @@ * @see cxJsonCreateLiteral() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); +CX_EXPORT CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); + +/** + * Creates a new JSON literal and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param lit (@c CxJsonLiteral) the type of literal + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateLiteral() + */ +#define cxJsonObjPutLiteral(obj, name, lit) cx_json_obj_put_literal(obj, cx_strcast(name), lit) /** * Recursively deallocates the memory of a JSON value. @@ -998,7 +1099,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal != CX_JSON_NULL; } /** @@ -1015,7 +1116,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_TRUE; } /** @@ -1032,7 +1133,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_FALSE; } /** @@ -1045,7 +1146,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_NULL; } /** @@ -1123,7 +1224,7 @@ */ cx_attr_nonnull CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { - return value->value.literal == CX_JSON_TRUE; + return value->literal == CX_JSON_TRUE; } /** @@ -1137,7 +1238,7 @@ */ cx_attr_nonnull CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) { - return value->value.array.array_size; + return value->array.data_size; } /** @@ -1188,10 +1289,24 @@ CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value); /** - * Returns an iterator over the JSON object members. + * Returns the size of a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. * - * The iterator yields values of type @c CxJsonObjValue* which - * contain the name and value of the member. + * @param value the JSON value + * @return the size of the object, i.e., the number of key/value pairs + * @see cxJsonIsObject() + */ +cx_attr_nonnull +CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { + return cxCollectionSize(value->object); +} + +/** + * Returns a map iterator over the JSON object members. + * + * The iterator yields values of type @c CxMapEntry* which + * contain the name and the @c CxJsonObjValue* of the member. * * If the @p value is not a JSON object, the behavior is undefined. * @@ -1200,7 +1315,7 @@ * @see cxJsonIsObject() */ cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value); +CX_EXPORT CxMapIterator cxJsonObjIter(const CxJsonValue *value); /** * Internal function, do not use. diff -r 3da24640513a -r e57ca2747782 ucx/cx/linked_list.h --- a/ucx/cx/linked_list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/linked_list.h Sat Dec 13 15:58:58 2025 +0100 @@ -62,7 +62,14 @@ */ off_t loc_data; /** + * Location of extra data (optional). + * Negative when no extra data is requested. + * @see cx_linked_list_extra_data() + */ + off_t loc_extra; + /** * Additional bytes to allocate @em behind the payload (e.g. for metadata). + * @see cx_linked_list_extra_data() */ size_t extra_data_len; /** @@ -112,6 +119,23 @@ cxLinkedListCreate(NULL, NULL, elem_size) /** + * Instructs the linked list to reserve extra data in each node. + * + * The extra data will be aligned and placed behind the element data. + * The exact location in the node is stored in the @c loc_extra field + * of the linked list. + * + * You should usually not use this function except when you are creating an + * own linked-list implementation that is based on the UCX linked list and + * needs to store extra data in each node. + * + * @param list the list (must be a linked list) + * @param len the length of the extra data + */ +cx_attr_nonnull +CX_EXPORT void cx_linked_list_extra_data(cx_linked_list *list, size_t len); + +/** * Finds the node at a certain index. * * This function can be used to start at an arbitrary position within the list. diff -r 3da24640513a -r e57ca2747782 ucx/cx/properties.h --- a/ucx/cx/properties.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/properties.h Sat Dec 13 15:58:58 2025 +0100 @@ -41,9 +41,6 @@ #include "map.h" #include "buffer.h" -#include -#include - #ifdef __cplusplus extern "C" { #endif @@ -80,9 +77,6 @@ * The character, when appearing at the end of a line, continues that line. * This is '\' by default. */ - /** - * Reserved for future use. - */ char continuation; }; @@ -141,23 +135,16 @@ */ CX_PROPERTIES_BUFFER_ALLOC_FAILED, /** - * Initializing the properties source failed. - * - * @see cx_properties_read_init_func + * A file operation failed. + * Only for cxPropertiesLoad(). + * It is system-specific if errno is set. */ - CX_PROPERTIES_READ_INIT_FAILED, + CX_PROPERTIES_FILE_ERROR, /** - * Reading from a properties source failed. - * - * @see cx_properties_read_func + * A map operation failed. + * Only for cxPropertiesLoad(). */ - CX_PROPERTIES_READ_FAILED, - /** - * Sinking a k/v-pair failed. - * - * @see cx_properties_sink_func - */ - CX_PROPERTIES_SINK_FAILED, + CX_PROPERTIES_MAP_ERROR, }; /** @@ -190,134 +177,6 @@ */ typedef struct cx_properties_s CxProperties; - -/** - * Typedef for a properties sink. - */ -typedef struct cx_properties_sink_s CxPropertiesSink; - -/** - * A function that consumes a k/v-pair in a sink. - * - * The sink could be a map, and the sink function would be calling - * a map function to store the k/v-pair. - * - * @param prop the properties interface that wants to sink a k/v-pair - * @param sink the sink - * @param key the key - * @param value the value - * @retval zero success - * @retval non-zero sinking the k/v-pair failed - */ -typedef int(*cx_properties_sink_func)( - CxProperties *prop, - CxPropertiesSink *sink, - cxstring key, - cxstring value -); - -/** - * Defines a sink for k/v-pairs. - */ -struct cx_properties_sink_s { - /** - * The sink object. - */ - void *sink; - /** - * Optional custom data. - */ - void *data; - /** - * A function for consuming k/v-pairs into the sink. - */ - cx_properties_sink_func sink_func; -}; - - -/** - * Typedef for a properties source. - */ -typedef struct cx_properties_source_s CxPropertiesSource; - -/** - * A function that reads data from a source. - * - * When the source is depleted, implementations SHALL provide an empty - * string in the @p target and return zero. - * A non-zero return value is only permitted in case of an error. - * - * The meaning of the optional parameters is implementation-dependent. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - * @param target a string buffer where the read data shall be stored - * @retval zero success - * @retval non-zero reading the data failed - */ -typedef int(*cx_properties_read_func)( - CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -); - -/** - * A function that may initialize additional memory for the source. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - * @retval zero initialization was successful - * @retval non-zero otherwise - */ -typedef int(*cx_properties_read_init_func)( - CxProperties *prop, - CxPropertiesSource *src -); - -/** - * A function that cleans memory initialized by the read_init_func. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - */ -typedef void(*cx_properties_read_clean_func)( - CxProperties *prop, - CxPropertiesSource *src -); - -/** - * Defines a properties source. - */ -struct cx_properties_source_s { - /** - * The source object. - * - * For example, a file stream or a string. - */ - void *src; - /** - * Optional additional data pointer. - */ - void *data_ptr; - /** - * Optional size information. - */ - size_t data_size; - /** - * A function that reads data from the source. - */ - cx_properties_read_func read_func; - /** - * Optional function that may prepare the source for reading data. - */ - cx_properties_read_init_func read_init_func; - /** - * Optional function that cleans additional memory allocated by the - * read_init_func. - */ - cx_properties_read_clean_func read_clean_func; -}; - /** * Initialize a properties interface. * @@ -465,100 +324,83 @@ CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** - * Creates a properties sink for an UCX map. - * - * The values stored in the map will be pointers to freshly allocated, - * zero-terminated C strings (@c char*), which means the @p map should have been - * created with #CX_STORE_POINTERS. - * - * The cxDefaultAllocator will be used unless you specify a custom - * allocator in the optional @c data field of the returned sink. - * - * @param map the map that shall consume the k/v-pairs. - * @return the sink - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will reserve with `cxPropertiesUseStack()`. */ -cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map); +CX_EXPORT extern const unsigned cx_properties_load_buf_size; /** - * Creates a properties source based on an UCX string. - * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will use to read contents from the file. */ -cx_attr_nodiscard -CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str); - -/** - * Creates a properties source based on C string with the specified length. - * - * @param str the string - * @param len the length - * @return the properties source - * @see cxPropertiesLoad() - */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) -CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); +CX_EXPORT extern const unsigned cx_properties_load_fill_size; /** - * Creates a properties source based on a C string. - * - * The length will be determined with strlen(), so the string MUST be - * zero-terminated. + * Internal function - use cxPropertiesLoad() instead. * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() + * @param config the parser config + * @param allocator the allocator for the values + * @param filename the file name + * @param target the target map + * @return status code */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) -CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str); +cx_attr_nonnull_arg(4) +CX_EXPORT CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, + const CxAllocator *allocator, cxstring filename, CxMap *target); /** - * Creates a properties source based on an FILE. - * - * @param file the file - * @param chunk_size how many bytes may be read in one operation + * Loads properties from a file and inserts them into a map. * - * @return the properties source - * @see cxPropertiesLoad() - */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) -CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); - - -/** - * Loads properties data from a source and transfers it to a sink. + * Entries are added to the map, possibly overwriting existing entries. + * + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. * - * This function tries to read as much data from the source as possible. - * When the source was completely consumed and at least on k/v-pair was found, - * the return value will be #CX_PROPERTIES_NO_ERROR. - * When the source was consumed but no k/v-pairs were found, the return value - * will be #CX_PROPERTIES_NO_DATA. - * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA - * is returned. In that case you should call this function again with the same - * sink and either an updated source or the same source if the source is able to - * yield the missing data. + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. * - * The other result codes apply, according to their description. - * - * @param prop the properties interface - * @param sink the sink - * @param source the source - * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found - * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed - * @retval CX_PROPERTIES_READ_FAILED reading from the source failed - * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed - * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs - * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data + * @param config the parser config + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoadDefault() */ -cx_attr_nonnull -CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop, - CxPropertiesSink sink, CxPropertiesSource source); +#define cxPropertiesLoad(config, allocator, filename, target) \ + cx_properties_load(config, allocator, cx_strcast(filename), target) + +/** + * Loads properties from a file and inserts them into a map with a default config. + * + * Entries are added to the map, possibly overwriting existing entries. + * + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. + * + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. + * + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoad() + */ +#define cxPropertiesLoadDefault(allocator, filename, target) \ + cx_properties_load(cx_properties_config_default, allocator, cx_strcast(filename), target) + #ifdef __cplusplus } // extern "C" diff -r 3da24640513a -r e57ca2747782 ucx/cx/string.h --- a/ucx/cx/string.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/string.h Sat Dec 13 15:58:58 2025 +0100 @@ -256,7 +256,7 @@ } cx_attr_nodiscard CX_CPPDECL cxstring cx_strcast(const unsigned char *str) { - return cx_str(static_cast(str)); + return cx_str(reinterpret_cast(str)); } extern "C" { #else diff -r 3da24640513a -r e57ca2747782 ucx/cx/tree.h --- a/ucx/cx/tree.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/cx/tree.h Sat Dec 13 15:58:58 2025 +0100 @@ -88,6 +88,7 @@ ptrdiff_t loc_next; /** * The total number of distinct nodes that have been passed so far. + * This includes the current node. */ size_t counter; /** @@ -185,6 +186,7 @@ ptrdiff_t loc_next; /** * The total number of distinct nodes that have been passed so far. + * This includes the currently visited node. */ size_t counter; /** @@ -641,17 +643,13 @@ * Structure for holding the base data of a tree. */ struct cx_tree_s { + CX_COLLECTION_BASE; /** * The tree class definition. */ const cx_tree_class *cl; /** - * Allocator to allocate new nodes. - */ - const CxAllocator *allocator; - - /** * A pointer to the root node. * * Will be @c NULL when @c size is 0. @@ -669,21 +667,6 @@ cx_tree_node_create_func node_create; /** - * An optional simple destructor for the tree nodes. - */ - cx_destructor_func simple_destructor; - - /** - * An optional advanced destructor for the tree nodes. - */ - cx_destructor_func2 advanced_destructor; - - /** - * The pointer to additional data that is passed to the advanced destructor. - */ - void *destructor_data; - - /** * A function to compare two nodes. */ cx_tree_search_func search; @@ -694,11 +677,6 @@ cx_tree_search_data_func search_data; /** - * The number of currently stored elements. - */ - size_t size; - - /** * Offset in the node struct for the parent pointer. */ ptrdiff_t loc_parent; @@ -1094,7 +1072,7 @@ * @see cxTreeIterate() */ cx_attr_nonnull cx_attr_nodiscard -CxTreeVisitor cxTreeVisit(CxTree *tree); +CX_EXPORT CxTreeVisitor cxTreeVisit(CxTree *tree); /** * Sets the (new) parent of the specified child. diff -r 3da24640513a -r e57ca2747782 ucx/hash_map.c --- a/ucx/hash_map.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/hash_map.c Sat Dec 13 15:58:58 2025 +0100 @@ -117,7 +117,7 @@ allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) return NULL; + if (e == NULL) return NULL; // LCOV_EXCL_LINE // write the value if (value == NULL) { @@ -130,10 +130,10 @@ // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) { + if (kd == NULL) { // LCOV_EXCL_START cxFree(allocator, e); return NULL; - } + } // LCOV_EXCL_STOP memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -447,15 +447,16 @@ size_t new_bucket_count = (map->collection.size * 5) >> 1; if (new_bucket_count < hash_map->bucket_count) { + // LCOV_EXCL_START errno = EOVERFLOW; return 1; - } + } // LCOV_EXCL_STOP struct cx_hash_map_element_s **new_buckets = cxCalloc( map->collection.allocator, new_bucket_count, sizeof(struct cx_hash_map_element_s *) ); - if (new_buckets == NULL) return 1; + if (new_buckets == NULL) return 1; // LCOV_EXCL_LINE // iterate through the elements and assign them to their new slots for (size_t slot = 0; slot < hash_map->bucket_count; slot++) { diff -r 3da24640513a -r e57ca2747782 ucx/json.c --- a/ucx/json.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/json.c Sat Dec 13 15:58:58 2025 +0100 @@ -27,6 +27,7 @@ */ #include "cx/json.h" +#include "cx/kv_list.h" #include #include @@ -41,90 +42,10 @@ static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; -static int json_cmp_objvalue(const void *l, const void *r) { - const CxJsonObjValue *left = l; - const CxJsonObjValue *right = r; - return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); -} - -static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { - assert(obj->type == CX_JSON_OBJECT); - CxJsonObjValue kv_dummy; - kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - return cx_array_binary_search( - obj->value.object.values, - obj->value.object.values_size, - sizeof(CxJsonObjValue), - &kv_dummy, - json_cmp_objvalue - ); -} - -static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { - assert(objv->type == CX_JSON_OBJECT); - const CxAllocator * const al = objv->allocator; - CxJsonObject *obj = &(objv->value.object); - - // determine the index where we need to insert the new member - size_t index = cx_array_binary_search_sup( - obj->values, - obj->values_size, - sizeof(CxJsonObjValue), - &member, json_cmp_objvalue - ); - - // is the name already present? - if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { - // free the original value - cx_strfree_a(al, &obj->values[index].name); - cxJsonValueFree(obj->values[index].value); - // replace the item - obj->values[index] = member; - - // nothing more to do - return 0; - } - - // determine the old capacity and reserve for one more element - CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); - size_t oldcap = obj->values_capacity; - if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; - - // check the new capacity, if we need to realloc the index array - size_t newcap = obj->values_capacity; - if (newcap > oldcap) { - if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { - return 1; - } - } - - // check if append or insert - if (index < obj->values_size) { - // move the other elements - memmove( - &obj->values[index+1], - &obj->values[index], - (obj->values_size - index) * sizeof(CxJsonObjValue) - ); - // increase indices for the moved elements - for (size_t i = 0; i < obj->values_size ; i++) { - if (obj->indices[i] >= index) { - obj->indices[i]++; - } - } - } - - // insert the element and set the index - obj->values[index] = member; - obj->indices[obj->values_size] = index; - obj->values_size++; - - return 0; -} - static void token_destroy(CxJsonToken *token) { if (token->allocated) { cx_strfree(&token->content); + token->allocated = false; } } @@ -307,7 +228,9 @@ } } - if (ttype != CX_JSON_NO_TOKEN) { + if (ttype == CX_JSON_NO_TOKEN) { + return CX_JSON_NO_DATA; + } else { // uncompleted token size_t uncompleted_len = json->buffer.size - token_part_start; if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { @@ -334,9 +257,8 @@ } // advance the buffer position - we saved the stuff in the uncompleted token json->buffer.pos += uncompleted_len; + return CX_JSON_INCOMPLETE_DATA; } - - return CX_JSON_INCOMPLETE_DATA; } // converts a Unicode codepoint to utf8 @@ -437,7 +359,7 @@ } else if (c == 'u') { char utf8buf[4]; unsigned utf8len = unescape_unicode_string( - cx_strn(str.ptr + i - 1, str.length + 1 - i), + cx_strn(str.ptr + i - 1, str.length - i), utf8buf ); if(utf8len > 0) { @@ -456,7 +378,7 @@ } else { // TODO: discuss the behavior for unrecognized escape sequences // most parsers throw an error here - we just ignore it - result.ptr[result.length++] = '\\'; + result.ptr[result.length++] = '\\'; // LCOV_EXCL_LINE } result.ptr[result.length++] = c; @@ -473,7 +395,7 @@ return result; } -static cxmutstr escape_string(cxmutstr str, bool escape_slash) { +static cxmutstr escape_string(cxstring str, bool escape_slash) { // note: this function produces the string without enclosing quotes // the reason is that we don't want to allocate memory just for that CxBuffer buf = {0}; @@ -519,11 +441,27 @@ cxBufferPut(&buf, c); } } - if (!all_printable) { - str = cx_mutstrn(buf.space, buf.size); + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); } cxBufferDestroy(&buf); - return str; + return ret; +} + +static CxJsonObject json_create_object_map(const CxAllocator *allocator) { + // TODO: we might want to add a comparator that is sorting the elements by their key + CxMap *map = cxKvListCreateAsMap(allocator, NULL, CX_STORE_POINTERS); + if (map == NULL) return NULL; // LCOV_EXCL_LINE + cxDefineDestructor(map, cxJsonValueFree); + return map; +} + +static void json_free_object_map(CxJsonObject obj) { + cxMapFree(obj); } static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { @@ -534,14 +472,11 @@ v->type = type; v->allocator = json->allocator; if (type == CX_JSON_ARRAY) { - cx_array_initialize_a(json->allocator, v->value.array.array, 16); - if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE + cx_array_initialize_a(json->allocator, v->array.data, 16); + if (v->array.data == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE } else if (type == CX_JSON_OBJECT) { - cx_array_initialize_a(json->allocator, v->value.object.values, 16); - v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); - if (v->value.object.values == NULL || - v->value.object.indices == NULL) - goto create_json_value_exit_error; // LCOV_EXCL_LINE + v->object = json_create_object_map(json->allocator); + if (v->object == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE } // add the new value to a possible parent @@ -550,17 +485,17 @@ assert(parent != NULL); if (parent->type == CX_JSON_ARRAY) { CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); - if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { + if (cx_array_simple_add_a(&value_realloc, parent->array.data, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } } else if (parent->type == CX_JSON_OBJECT) { // the member was already created after parsing the name - assert(json->uncompleted_member.name.ptr != NULL); - json->uncompleted_member.value = v; - if (json_add_objvalue(parent, json->uncompleted_member)) { + // store the pointer of the uncompleted value in the map + assert(json->uncompleted_member_name.ptr != NULL); + if (cxMapPut(parent->object, json->uncompleted_member_name, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } - json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } else { assert(false); // LCOV_EXCL_LINE } @@ -624,10 +559,8 @@ } cxJsonValueFree(json->parsed); json->parsed = NULL; - if (json->uncompleted_member.name.ptr != NULL) { - cx_strfree_a(json->allocator, &json->uncompleted_member.name); - json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; - } + token_destroy(&json->uncompleted); + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } void cxJsonReset(CxJson *json) { @@ -640,6 +573,7 @@ if (cxBufferEof(&json->buffer)) { // reinitialize the buffer cxBufferDestroy(&json->buffer); + if (buf == NULL) buf = ""; // buffer must not be initialized with NULL cxBufferInit(&json->buffer, (char*) buf, size, NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); json->buffer.size = size; @@ -719,7 +653,7 @@ if (str.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - vbuf->value.string = str; + vbuf->string = str; return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_INTEGER: @@ -729,12 +663,13 @@ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (type == CX_JSON_INTEGER) { - if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + if (cx_strtoi64(token.content, &vbuf->integer, 10)) { return_rec(CX_JSON_FORMAT_ERROR_NUMBER); } } else { - if (cx_strtod(token.content, &vbuf->value.number)) { - return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + if (cx_strtod(token.content, &vbuf->number)) { + // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod() + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); // LCOV_EXCL_LINE } } return_rec(CX_JSON_NO_ERROR); @@ -744,11 +679,11 @@ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { - vbuf->value.literal = CX_JSON_TRUE; + vbuf->literal = CX_JSON_TRUE; } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { - vbuf->value.literal = CX_JSON_FALSE; + vbuf->literal = CX_JSON_FALSE; } else { - vbuf->value.literal = CX_JSON_NULL; + vbuf->literal = CX_JSON_NULL; } return_rec(CX_JSON_NO_ERROR); } @@ -784,8 +719,8 @@ if (name.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - assert(json->uncompleted_member.name.ptr == NULL); - json->uncompleted_member.name = name; + assert(json->uncompleted_member_name.ptr == NULL); + json->uncompleted_member_name = name; assert(json->vbuf_size > 0); // next state @@ -815,19 +750,19 @@ } else { // should be unreachable assert(false); - return_rec(-1); + return_rec(-1); // LCOV_EXCL_LINE } } CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { - // check if buffer has been filled + // initialize output value + *value = &cx_json_value_nothing; + + // check if the buffer has been filled if (json->buffer.space == NULL) { return CX_JSON_NULL_DATA; } - // initialize output value - *value = &cx_json_value_nothing; - // parse data CxJsonStatus result; do { @@ -858,29 +793,54 @@ return result; } +CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value) { + *value = &cx_json_value_nothing; + CxJson parser; + cxJsonInit(&parser, allocator); + if (cxJsonFill(&parser, str)) { + // LCOV_EXCL_START + cxJsonDestroy(&parser); + return CX_JSON_BUFFER_ALLOC_FAILED; + // LCOV_EXCL_STOP + } + CxJsonStatus status = cxJsonNext(&parser, value); + // check if we consume the total string + CxJsonValue *chk_value = NULL; + CxJsonStatus chk_status = CX_JSON_NO_DATA; + if (status == CX_JSON_NO_ERROR) { + chk_status = cxJsonNext(&parser, &chk_value); + } + cxJsonDestroy(&parser); + if (chk_status == CX_JSON_NO_DATA) { + return status; + } else { + cxJsonValueFree(*value); + // if chk_value is nothing, the free is harmless + cxJsonValueFree(chk_value); + *value = &cx_json_value_nothing; + return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN; + } + +} + void cxJsonValueFree(CxJsonValue *value) { if (value == NULL || value->type == CX_JSON_NOTHING) return; switch (value->type) { case CX_JSON_OBJECT: { - CxJsonObject obj = value->value.object; - for (size_t i = 0; i < obj.values_size; i++) { - cxJsonValueFree(obj.values[i].value); - cx_strfree_a(value->allocator, &obj.values[i].name); - } - cxFree(value->allocator, obj.values); - cxFree(value->allocator, obj.indices); + json_free_object_map(value->object); break; } case CX_JSON_ARRAY: { - CxJsonArray array = value->value.array; - for (size_t i = 0; i < array.array_size; i++) { - cxJsonValueFree(array.array[i]); + CxJsonArray array = value->array; + for (size_t i = 0; i < array.data_size; i++) { + cxJsonValueFree(array.data[i]); } - cxFree(value->allocator, array.array); + cxFree(value->allocator, array.data); break; } case CX_JSON_STRING: { - cxFree(value->allocator, value->value.string.ptr); + cxFree(value->allocator, value->string.ptr); break; } default: { @@ -896,15 +856,8 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_OBJECT; - cx_array_initialize_a(allocator, v->value.object.values, 16); - if (v->value.object.values == NULL) { // LCOV_EXCL_START - cxFree(allocator, v); - return NULL; - // LCOV_EXCL_STOP - } - v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); - if (v->value.object.indices == NULL) { // LCOV_EXCL_START - cxFree(allocator, v->value.object.values); + v->object = json_create_object_map(allocator); + if (v->object == NULL) { // LCOV_EXCL_START cxFree(allocator, v); return NULL; // LCOV_EXCL_STOP @@ -918,8 +871,8 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_ARRAY; - cx_array_initialize_a(allocator, v->value.array.array, 16); - if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } + cx_array_initialize_a(allocator, v->array.data, 16); + if (v->array.data == NULL) { cxFree(allocator, v); return NULL; } return v; } @@ -929,7 +882,7 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_NUMBER; - v->value.number = num; + v->number = num; return v; } @@ -939,15 +892,11 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_INTEGER; - v->value.integer = num; + v->integer = num; return v; } -CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) { - return cxJsonCreateCxString(allocator, cx_str(str)); -} - -CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { +CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str) { if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; @@ -955,7 +904,7 @@ v->type = CX_JSON_STRING; cxmutstr s = cx_strdup_a(allocator, str); if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } - v->value.string = s; + v->string = s; return v; } @@ -965,7 +914,7 @@ if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_LITERAL; - v->value.literal = lit; + v->literal = lit; return v; } @@ -1020,7 +969,7 @@ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { - values[i] = cxJsonCreateCxString(arr->allocator, str[i]); + values[i] = cxJsonCreateString(arr->allocator, str[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); @@ -1044,67 +993,52 @@ CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); assert(arr->type == CX_JSON_ARRAY); return cx_array_simple_copy_a(&value_realloc, - arr->value.array.array, - arr->value.array.array_size, + arr->array.data, + arr->array.data_size, val, count ); } -int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) { - cxmutstr k = cx_strdup_a(obj->allocator, name); - if (k.ptr == NULL) return -1; - CxJsonObjValue kv = {k, child}; - if (json_add_objvalue(obj, kv)) { - cx_strfree_a(obj->allocator, &k); - return 1; - } else { - return 0; - } +int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) { + return cxMapPut(obj->object, name, child); } -CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) { +CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { CxJsonValue* v = cxJsonCreateObj(obj->allocator); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) { +CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) { CxJsonValue* v = cxJsonCreateArr(obj->allocator); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) { +CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num) { CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) { +CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num) { CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) { +CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str) { CxJsonValue* v = cxJsonCreateString(obj->allocator, str); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) { - CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str); - if (v == NULL) return NULL; - if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } - return v; -} - -CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { +CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} @@ -1112,98 +1046,84 @@ } CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.data_size) { return &cx_json_value_nothing; } - return value->value.array.array[index]; + return value->array.data[index]; } CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.data_size) { return NULL; } - CxJsonValue *ret = value->value.array.array[index]; + CxJsonValue *ret = value->array.data[index]; // TODO: replace with a low level cx_array_remove() - size_t count = value->value.array.array_size - index - 1; + size_t count = value->array.data_size - index - 1; if (count > 0) { - memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); + memmove(value->array.data + index, value->array.data + index + 1, count * sizeof(CxJsonValue*)); } - value->value.array.array_size--; + value->array.data_size--; return ret; } char *cxJsonAsString(const CxJsonValue *value) { - return value->value.string.ptr; + return value->string.ptr; } cxstring cxJsonAsCxString(const CxJsonValue *value) { - return cx_strcast(value->value.string); + return cx_strcast(value->string); } cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { - return value->value.string; + return value->string; } double cxJsonAsDouble(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return (double) value->value.integer; + return (double) value->integer; } else { - return value->value.number; + return value->number; } } int64_t cxJsonAsInteger(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return value->value.integer; + return value->integer; } else { - return (int64_t) value->value.number; + return (int64_t) value->number; } } CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( - value->value.array.array, - value->value.array.array_size, + value->array.data, + value->array.data_size, true // arrays need to keep order ); } -CxIterator cxJsonObjIter(const CxJsonValue *value) { - return cxIterator( - value->value.object.values, - sizeof(CxJsonObjValue), - value->value.object.values_size, - true // TODO: objects do not always need to keep order - ); +CxMapIterator cxJsonObjIter(const CxJsonValue *value) { + return cxMapIterator(value->object); } CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { + CxJsonValue *v = cxMapGet(value->object, name); + if (v == NULL) { return &cx_json_value_nothing; } else { - return value->value.object.values[index].value; + return v; } } CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { - return NULL; - } else { - CxJsonObjValue kv = value->value.object.values[index]; - cx_strfree_a(value->allocator, &kv.name); - // TODO: replace with cx_array_remove() / cx_array_remove_fast() - value->value.object.values_size--; - memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); - return kv.value; - } + CxJsonValue *v = NULL; + cxMapRemoveAndGet(value->object, name, &v); + return v; } CxJsonWriter cxJsonWriterCompact(void) { return (CxJsonWriter) { false, - true, 6, false, 4, @@ -1214,7 +1134,6 @@ CxJsonWriter cxJsonWriterPretty(bool use_spaces) { return (CxJsonWriter) { true, - true, 6, use_spaces, 4, @@ -1279,17 +1198,8 @@ expected++; } depth++; - size_t elem_count = value->value.object.values_size; - for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { - // get the member either via index array or directly - size_t elem_idx = settings->sort_members - ? look_idx - : value->value.object.indices[look_idx]; - CxJsonObjValue *member = &value->value.object.values[elem_idx]; - if (settings->sort_members) { - depth++;depth--; - } - + CxMapIterator member_iter = cxJsonObjIter(value); + cx_foreach(const CxMapEntry *, member, member_iter) { // possible indentation if (settings->pretty) { if (cx_json_writer_indent(target, wfunc, settings, depth)) { @@ -1299,26 +1209,27 @@ // the name actual += wfunc("\"", 1, 1, target); - cxmutstr name = escape_string(member->name, settings->escape_slash); + cxstring key = cx_strn(member->key->data, member->key->len); + cxmutstr name = escape_string(key, settings->escape_slash); actual += wfunc(name.ptr, 1, name.length, target); - if (name.ptr != member->name.ptr) { - cx_strfree(&name); - } actual += wfunc("\"", 1, 1, target); const char *obj_name_sep = ": "; if (settings->pretty) { actual += wfunc(obj_name_sep, 1, 2, target); - expected += 4 + member->name.length; + expected += 4 + name.length; } else { actual += wfunc(obj_name_sep, 1, 1, target); - expected += 3 + member->name.length; + expected += 3 + name.length; + } + if (name.ptr != key.ptr) { + cx_strfree(&name); } // the value if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; // end of object-value - if (look_idx < elem_count - 1) { + if (member_iter.index < member_iter.elem_count - 1) { const char *obj_value_sep = ",\n"; if (settings->pretty) { actual += wfunc(obj_value_sep, 1, 2, target); @@ -1350,7 +1261,9 @@ if (cx_json_write_rec( target, element, wfunc, settings, depth) - ) return 1; + ) { + return 1; // LCOV_EXCL_LINE + } if (iter.index < iter.elem_count - 1) { const char *arr_value_sep = ", "; @@ -1369,13 +1282,14 @@ } case CX_JSON_STRING: { actual += wfunc("\"", 1, 1, target); - cxmutstr str = escape_string(value->value.string, settings->escape_slash); + cxmutstr str = escape_string(cx_strcast(value->string), + settings->escape_slash); actual += wfunc(str.ptr, 1, str.length, target); - if (str.ptr != value->value.string.ptr) { + actual += wfunc("\"", 1, 1, target); + expected += 2 + str.length; + if (str.ptr != value->string.ptr) { cx_strfree(&str); } - actual += wfunc("\"", 1, 1, target); - expected += 2 + value->value.string.length; break; } case CX_JSON_NUMBER: { @@ -1383,7 +1297,7 @@ // because of the way how %g is defined, we need to // double the precision and truncate ourselves precision = 1 + (precision > 15 ? 30 : 2 * precision); - snprintf(numbuf, 40, "%.*g", precision, value->value.number); + snprintf(numbuf, 40, "%.*g", precision, value->number); char *dot, *exp; unsigned char max_digits; // find the decimal separator and hope that it's one of . or , @@ -1447,17 +1361,17 @@ break; } case CX_JSON_INTEGER: { - snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + snprintf(numbuf, 32, "%" PRIi64, value->integer); size_t len = strlen(numbuf); actual += wfunc(numbuf, 1, len, target); expected += len; break; } case CX_JSON_LITERAL: { - if (value->value.literal == CX_JSON_TRUE) { + if (value->literal == CX_JSON_TRUE) { actual += wfunc("true", 1, 4, target); expected += 4; - } else if (value->value.literal == CX_JSON_FALSE) { + } else if (value->literal == CX_JSON_FALSE) { actual += wfunc("false", 1, 5, target); expected += 5; } else { @@ -1495,3 +1409,35 @@ } return cx_json_write_rec(target, value, wfunc, settings, 0); } + +static cxmutstr cx_json_to_string(CxJsonValue *value, const CxAllocator *allocator, CxJsonWriter *writer) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxBuffer buffer; + if (cxBufferInit(&buffer, NULL, 128, allocator, + CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) { + return (cxmutstr){NULL, 0}; + } + if (cx_json_write_rec(&buffer, value, cxBufferWriteFunc, writer, 0) + || cxBufferTerminate(&buffer)) { + // LCOV_EXCL_START + buffer.flags &= ~CX_BUFFER_DO_NOT_FREE; + cxBufferDestroy(&buffer); + return (cxmutstr){NULL, 0}; + // LCOV_EXCL_STOP + } else { + cxmutstr str = cx_mutstrn(buffer.space, buffer.size); + cxBufferDestroy(&buffer); + return str; + } + +} + +cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator) { + CxJsonWriter writer = cxJsonWriterCompact(); + return cx_json_to_string(value, allocator, &writer); +} + +cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator) { + CxJsonWriter writer = cxJsonWriterPretty(true); + return cx_json_to_string(value, allocator, &writer); +} diff -r 3da24640513a -r e57ca2747782 ucx/kv_list.c --- a/ucx/kv_list.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/kv_list.c Sat Dec 13 15:58:58 2025 +0100 @@ -98,7 +98,7 @@ } static CxHashKey *cx_kv_list_loc_key(cx_kv_list *list, void *node_data) { - return (CxHashKey*)((char*)node_data + list->list.base.collection.elem_size); + return (CxHashKey*)((char*)node_data - list->list.loc_data + list->list.loc_extra); } static void cx_kvl_deallocate(struct cx_list_s *list) { @@ -285,6 +285,7 @@ static void cx_kvl_map_deallocate(struct cx_map_s *map) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); kv_list->map_methods->deallocate(map); kv_list->list_methods->deallocate(&kv_list->list.base); } @@ -296,41 +297,7 @@ kv_list->map_methods->clear(map); } -static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { - cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; - // if the hash has not yet been computed, do it now - if (key.hash == 0) { - cx_hash_murmur(&key); - } - - // reserve memory in the map first - void **map_data = kv_list->map_methods->put(map, key, NULL); - if (map_data == NULL) return NULL; // LCOV_EXCL_LINE - - // insert the data into the list (which most likely destroys the sorted property) - kv_list->list.base.collection.sorted = false; - void *node_data = kv_list->list_methods->insert_element( - &kv_list->list.base, kv_list->list.base.collection.size, - kv_list->list.base.collection.store_pointer ? &value : value); - if (node_data == NULL) { // LCOV_EXCL_START - // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); - return NULL; - } // LCOV_EXCL_STOP - - // write the node pointer to the map entry - *map_data = node_data; - - // copy the key to the node data - CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); - *key_ptr = key; - - // we must return node_data here and not map_data, - // because the node_data is the actual element of this collection - return node_data; -} - -void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { +static void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data = kv_list->map_methods->get(map, key); if (node_data == NULL) return NULL; // LCOV_EXCL_LINE @@ -338,7 +305,7 @@ return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; } -int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { +static int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data; @@ -381,6 +348,43 @@ return 0; } +static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // remove any existing element first + cx_kvl_map_remove(map, key, NULL); + + // now reserve new memory in the map + void **map_data = kv_list->map_methods->put(map, key, NULL); + if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return NULL; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *map_data = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = key; + + // we must return node_data here and not map_data, + // because the node_data is the actual element of this collection + return node_data; +} + static void *cx_kvl_iter_current_entry(const void *it) { const CxMapIterator *iter = it; return (void*)&iter->entry; @@ -455,7 +459,7 @@ return iter->elem != NULL; } -CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { +static CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { CxMapIterator iter = {0}; iter.type = type; @@ -559,7 +563,7 @@ CxList *list = cxLinkedListCreate(allocator, comparator, elem_size); if (list == NULL) return NULL; // LCOV_EXCL_LINE cx_linked_list *ll = (cx_linked_list*)list; - ll->extra_data_len = sizeof(CxHashKey); + cx_linked_list_extra_data(ll, sizeof(CxHashKey)); CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0); if (map == NULL) { // LCOV_EXCL_START cxListFree(list); diff -r 3da24640513a -r e57ca2747782 ucx/linked_list.c --- a/ucx/linked_list.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/linked_list.c Sat Dec 13 15:58:58 2025 +0100 @@ -31,6 +31,15 @@ #include #include +#if __STDC_VERSION__ < 202311L +// we cannot simply include stdalign.h +// because Solaris is not entirely C11 complaint +#ifndef __alignof_is_defined +#define alignof _Alignof +#define __alignof_is_defined 1 +#endif +#endif + // LOW LEVEL LINKED LIST FUNCTIONS #define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) @@ -516,7 +525,7 @@ void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE]; void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ? cxMallocDefault(sizeof(void *) * length) : sbo; - if (sorted == NULL) abort(); + if (sorted == NULL) abort(); // LCOV_EXCL_LINE void *rc, *lc; lc = ls; @@ -692,8 +701,13 @@ } static void *cx_ll_malloc_node(const cx_linked_list *list) { - return cxZalloc(list->base.collection.allocator, - list->loc_data + list->base.collection.elem_size + list->extra_data_len); + size_t n; + if (list->extra_data_len == 0) { + n = list->loc_data + list->base.collection.elem_size; + } else { + n = list->loc_extra + list->extra_data_len; + } + return cxZalloc(list->base.collection.allocator, n); } static int cx_ll_insert_at( @@ -1215,7 +1229,7 @@ return result; } else { if (cx_ll_insert_element(list, list->collection.size, elem) == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } iter->elem_count++; iter->index = list->collection.size; @@ -1267,12 +1281,22 @@ cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); if (list == NULL) return NULL; - list->extra_data_len = 0; list->loc_prev = 0; list->loc_next = sizeof(void*); list->loc_data = sizeof(void*)*2; + list->loc_extra = -1; + list->extra_data_len = 0; cx_list_init((CxList*)list, &cx_linked_list_class, allocator, comparator, elem_size); return (CxList *) list; } + +void cx_linked_list_extra_data(cx_linked_list *list, size_t len) { + list->extra_data_len = len; + + off_t loc_extra = list->loc_data + list->base.collection.elem_size; + size_t alignment = alignof(void*); + size_t padding = alignment - (loc_extra % alignment); + list->loc_extra = loc_extra + padding; +} diff -r 3da24640513a -r e57ca2747782 ucx/list.c --- a/ucx/list.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/list.c Sat Dec 13 15:58:58 2025 +0100 @@ -186,7 +186,11 @@ } static int cx_pl_change_capacity(struct cx_list_s *list, size_t cap) { - return list->climpl->change_capacity(list, cap); + if (list->climpl->change_capacity == NULL) { + return 0; + } else { + return list->climpl->change_capacity(list, cap); + } } static struct cx_iterator_s cx_pl_iterator( @@ -1026,7 +1030,7 @@ CxIterator src_iter = cxListIterator(src); CxIterator other_iter = cxListIterator(other); while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) { - void *src_elem, *other_elem; + void *src_elem = NULL, *other_elem = NULL; int d; if (!cxIteratorValid(src_iter)) { other_elem = cxIteratorCurrent(other_iter); @@ -1124,4 +1128,4 @@ return 0; } return list->cl->change_capacity(list, cxCollectionSize(list)); -} \ No newline at end of file +} diff -r 3da24640513a -r e57ca2747782 ucx/printf.c --- a/ucx/printf.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/printf.c Sat Dec 13 15:58:58 2025 +0100 @@ -61,8 +61,10 @@ va_copy(ap2, ap); int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap); if (ret < 0) { + // LCOV_EXCL_START va_end(ap2); return ret; + // LCOV_EXCL_STOP } else if (ret < CX_PRINTF_SBO_SIZE) { va_end(ap2); return (int) wfc(buf, 1, ret, stream); @@ -121,8 +123,10 @@ if (s.ptr) { ret = vsnprintf(s.ptr, len, fmt, ap2); if (ret < 0) { + // LCOV_EXCL_START cxFree(a, s.ptr); s.ptr = NULL; + // LCOV_EXCL_STOP } else { s.length = (size_t) ret; } @@ -162,7 +166,7 @@ if (ptr) { int newret = vsnprintf(ptr, newlen, fmt, ap2); if (newret < 0) { - cxFree(alloc, ptr); + cxFree(alloc, ptr); // LCOV_EXCL_LINE } else { *len = newlen; *str = ptr; @@ -207,7 +211,7 @@ if (ptr) { int newret = vsnprintf(ptr, newlen, fmt, ap2); if (newret < 0) { - cxFree(alloc, ptr); + cxFree(alloc, ptr); // LCOV_EXCL_LINE } else { *len = newlen; *str = ptr; diff -r 3da24640513a -r e57ca2747782 ucx/properties.c --- a/ucx/properties.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/properties.c Sat Dec 13 15:58:58 2025 +0100 @@ -29,12 +29,15 @@ #include "cx/properties.h" #include +#include +#include +#include const CxPropertiesConfig cx_properties_config_default = { - '=', - '#', - '\0', - '\0', + '=', + '#', + '\0', + '\0', '\\', }; @@ -94,13 +97,30 @@ // a pointer to the buffer we want to read from CxBuffer *current_buffer = &prop->input; - + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.delimiter; + char continuation = prop->config.continuation; + // check if we have rescued data if (!cxBufferEof(&prop->buffer)) { // check if we can now get a complete line cxstring input = cx_strn(prop->input.space + prop->input.pos, prop->input.size - prop->input.pos); cxstring nl = cx_strchr(input, '\n'); + while (nl.length > 0) { + // check for line continuation + char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1]; + if (previous == continuation) { + // this nl is a line continuation, check the next newline + nl = cx_strchr(cx_strsubs(nl, 1), '\n'); + } else { + break; + } + } + if (nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line @@ -120,19 +140,14 @@ // still not enough data, copy input buffer to internal buffer if (cxBufferAppend(input.ptr, 1, input.length, &prop->buffer) < input.length) { - return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE } // reset the input buffer (make way for a re-fill) cxBufferReset(&prop->input); return CX_PROPERTIES_INCOMPLETE_DATA; } } - - char comment1 = prop->config.comment1; - char comment2 = prop->config.comment2; - char comment3 = prop->config.comment3; - char delimiter = prop->config.delimiter; - + // get one line and parse it while (!cxBufferEof(current_buffer)) { const char *buf = current_buffer->space + current_buffer->pos; @@ -145,6 +160,7 @@ size_t delimiter_index = 0; size_t comment_index = 0; bool has_comment = false; + bool has_continuation = false; size_t i = 0; char c = 0; @@ -159,6 +175,9 @@ if (delimiter_index == 0 && !has_comment) { delimiter_index = i; } + } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') { + has_continuation = true; + i++; } else if (c == '\n') { break; } @@ -223,10 +242,53 @@ k = cx_strtrim(k); val = cx_strtrim(val); if (k.length > 0) { + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size); + + if (has_continuation) { + char *ptr = (char*)val.ptr; + if (current_buffer != &prop->buffer) { + // move value to the rescue buffer + if (prop->buffer.space == NULL) { + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } + prop->buffer.size = 0; + prop->buffer.pos = 0; + if (cxBufferWrite(val.ptr, 1, val.length, &prop->buffer) != val.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + val.ptr = prop->buffer.space; + ptr = prop->buffer.space; + } + // value.ptr is now inside the rescue buffer and we can + // remove the continuation character from the value + bool trim = false; + size_t x = 0; + for(size_t j=0;j x) { + if (trim) { + if (isspace((unsigned char)c)) { + continue; + } + trim = false; + } + ptr[x] = c; + } + x++; + } + val.length = x; + } *key = k; *value = val; - current_buffer->pos += i + 1; - assert(current_buffer->pos <= current_buffer->size); + return CX_PROPERTIES_NO_ERROR; } else { return CX_PROPERTIES_INVALID_EMPTY_KEY; @@ -241,180 +303,96 @@ return CX_PROPERTIES_NO_DATA; } -static int cx_properties_sink_map( - cx_attr_unused CxProperties *prop, - CxPropertiesSink *sink, - cxstring key, - cxstring value -) { - CxMap *map = sink->sink; - CxAllocator *alloc = sink->data; - cxmutstr v = cx_strdup_a(alloc, value); - int r = cxMapPut(map, key, v.ptr); - if (r != 0) cx_strfree_a(alloc, &v); - return r; -} - -CxPropertiesSink cxPropertiesMapSink(CxMap *map) { - CxPropertiesSink sink; - sink.sink = map; - sink.data = (void*) cxDefaultAllocator; - sink.sink_func = cx_properties_sink_map; - return sink; -} +#ifndef CX_PROPERTIES_LOAD_FILL_SIZE +#define CX_PROPERTIES_LOAD_FILL_SIZE 1024 +#endif +const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE; +#ifndef CX_PROPERTIES_LOAD_BUF_SIZE +#define CX_PROPERTIES_LOAD_BUF_SIZE 256 +#endif +const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE; -static int cx_properties_read_string( - CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - if (prop->input.space == src->src) { - // when the input buffer already contains the string - // we have nothing more to provide - target->length = 0; - } else { - target->ptr = src->src; - target->length = src->data_size; +CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, + const CxAllocator *allocator, cxstring filename, CxMap *target) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; } - return 0; -} - -static int cx_properties_read_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - target->ptr = src->data_ptr; - target->length = fread(src->data_ptr, 1, src->data_size, src->src); - return ferror((FILE*)src->src); -} - -static int cx_properties_read_init_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - src->data_ptr = cxMallocDefault(src->data_size); - if (src->data_ptr == NULL) return 1; - return 0; -} -static void cx_properties_read_clean_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - cxFreeDefault(src->data_ptr); -} - -CxPropertiesSource cxPropertiesStringSource(cxstring str) { - CxPropertiesSource src; - src.src = (void*) str.ptr; - src.data_size = str.length; - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { - CxPropertiesSource src; - src.src = (void*) str; - src.data_size = len; - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} + // sanity check for the map + const bool use_cstring = cxCollectionStoresPointers(target); + if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) { + return CX_PROPERTIES_MAP_ERROR; + } -CxPropertiesSource cxPropertiesCstrSource(const char *str) { - CxPropertiesSource src; - src.src = (void*) str; - src.data_size = strlen(str); - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} + // create a duplicate to guarantee zero-termination + cxmutstr fname = cx_strdup(filename); + if (fname.ptr == NULL) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } -CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { - CxPropertiesSource src; - src.src = file; - src.data_size = chunk_size; - src.data_ptr = NULL; - src.read_func = cx_properties_read_file; - src.read_init_func = cx_properties_read_init_file; - src.read_clean_func = cx_properties_read_clean_file; - return src; -} - -CxPropertiesStatus cxPropertiesLoad( - CxProperties *prop, - CxPropertiesSink sink, - CxPropertiesSource source -) { - assert(source.read_func != NULL); - assert(sink.sink_func != NULL); - - // initialize reader - if (source.read_init_func != NULL) { - if (source.read_init_func(prop, &source)) { - return CX_PROPERTIES_READ_INIT_FAILED; - } + // open the file + FILE *f = fopen(fname.ptr, "r"); + if (f == NULL) { + cx_strfree(&fname); + return CX_PROPERTIES_FILE_ERROR; } - // transfer the data from the source to the sink + // initialize the parser + char linebuf[cx_properties_load_buf_size]; + char fillbuf[cx_properties_load_fill_size]; CxPropertiesStatus status; - CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; - bool found = false; + CxProperties parser; + cxPropertiesInit(&parser, config); + cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size); + + // read/fill/parse loop + status = CX_PROPERTIES_NO_DATA; + size_t keys_found = 0; while (true) { - // read input - cxstring input; - if (source.read_func(prop, &source, &input)) { - status = CX_PROPERTIES_READ_FAILED; + size_t r = fread(fillbuf, 1, cx_properties_load_fill_size, f); + if (ferror(f)) { + status = CX_PROPERTIES_FILE_ERROR; + break; + } + if (r == 0) { break; } - - // no more data - break - if (input.length == 0) { - if (found) { - // something was found, check the last kv_status - if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { - status = CX_PROPERTIES_INCOMPLETE_DATA; - } else { - status = CX_PROPERTIES_NO_ERROR; - } - } else { - // nothing found - status = CX_PROPERTIES_NO_DATA; - } + if (cxPropertiesFilln(&parser, fillbuf, r)) { + status = CX_PROPERTIES_BUFFER_ALLOC_FAILED; break; } - - // set the input buffer and read the k/v-pairs - cxPropertiesFill(prop, input); - - do { - cxstring key, value; - kv_status = cxPropertiesNext(prop, &key, &value); - if (kv_status == CX_PROPERTIES_NO_ERROR) { - found = true; - if (sink.sink_func(prop, &sink, key, value)) { - kv_status = CX_PROPERTIES_SINK_FAILED; + cxstring key, value; + while (true) { + status = cxPropertiesNext(&parser, &key, &value); + if (status != CX_PROPERTIES_NO_ERROR) { + break; + } else { + cxmutstr v = cx_strdup_a(allocator, value); + if (v.ptr == NULL) { + status = CX_PROPERTIES_MAP_ERROR; + break; } + void *mv = use_cstring ? (void*)v.ptr : &v; + if (cxMapPut(target, key, mv)) { + cx_strfree(&v); + status = CX_PROPERTIES_MAP_ERROR; + break; + } + keys_found++; } - } while (kv_status == CX_PROPERTIES_NO_ERROR); - - if (kv_status > CX_PROPERTIES_OK) { - status = kv_status; + } + if (status > CX_PROPERTIES_OK) { break; } } - if (source.read_clean_func != NULL) { - source.read_clean_func(prop, &source); + // cleanup and exit + fclose(f); + cxPropertiesDestroy(&parser); + cx_strfree(&fname); + if (status == CX_PROPERTIES_NO_DATA && keys_found > 0) { + return CX_PROPERTIES_NO_ERROR; + } else { + return status; } - - return status; } diff -r 3da24640513a -r e57ca2747782 ucx/string.c --- a/ucx/string.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/string.c Sat Dec 13 15:58:58 2025 +0100 @@ -91,7 +91,7 @@ cxstring src ) { if (cxReallocate(alloc, &dest->ptr, src.length + 1)) { - return 1; + return 1; // LCOV_EXCL_LINE } memcpy(dest->ptr, src.ptr, src.length); @@ -137,7 +137,7 @@ size_t slen = str.length; for (size_t i = 0; i < count; i++) { cxstring s = va_arg(ap, cxstring); - if (slen > SIZE_MAX - str.length) overflow = true; + if (slen > SIZE_MAX - s.length) overflow = true; slen += s.length; } va_end(ap); @@ -156,10 +156,10 @@ } else { newstr = cxRealloc(alloc, str.ptr, slen + 1); } - if (newstr == NULL) { + if (newstr == NULL) { // LCOV_EXCL_START va_end(ap2); return (cxmutstr) {NULL, 0}; - } + } // LCOV_EXCL_STOP str.ptr = newstr; // concatenate strings @@ -521,10 +521,12 @@ cxMalloc(allocator, string.length + 1), string.length }; + // LCOV_EXCL_START if (result.ptr == NULL) { result.length = 0; return result; } + // LCOV_EXCL_STOP memcpy(result.ptr, string.ptr, string.length); result.ptr[string.length] = '\0'; return result; diff -r 3da24640513a -r e57ca2747782 ucx/tree.c --- a/ucx/tree.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ucx/tree.c Sat Dec 13 15:58:58 2025 +0100 @@ -566,7 +566,7 @@ ptrdiff_t loc_next ) { *cnode = cfunc(src, cdata); - if (*cnode == NULL) return 1; + if (*cnode == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations); void *match = NULL; @@ -627,7 +627,7 @@ // create the new node void *new_node = cfunc(elem, cdata); - if (new_node == NULL) return processed; + if (new_node == NULL) return processed; // LCOV_EXCL_LINE cx_tree_zero_pointers(new_node, cx_tree_ptr_locations); // start searching from current node @@ -731,18 +731,18 @@ void *node; if (tree->root == NULL) { node = tree->node_create(data, tree); - if (node == NULL) return 1; + if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); tree->root = node; - tree->size = 1; + tree->collection.size = 1; return 0; } int result = cx_tree_add(data, tree->search, tree->node_create, tree, &node, tree->root, cx_tree_node_layout(tree)); if (0 == result) { - tree->size++; + tree->collection.size++; } else { - cxFree(tree->allocator, node); + cxFree(tree->collection.allocator, node); } return result; } @@ -758,7 +758,7 @@ // use the first element from the iter to create the root node void **eptr = iter->current(iter); void *node = tree->node_create(*eptr, tree); - if (node == NULL) return 0; + if (node == NULL) return 0; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); tree->root = node; ins = 1; @@ -767,9 +767,9 @@ void *failed; ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create, tree, &failed, tree->root, cx_tree_node_layout(tree)); - tree->size += ins; + tree->collection.size += ins; if (ins < n) { - cxFree(tree->allocator, failed); + cxFree(tree->collection.allocator, failed); } return ins; } @@ -780,7 +780,7 @@ const void *data, size_t depth ) { - if (tree->root == NULL) return NULL; + if (tree->root == NULL) return NULL; // LCOV_EXCL_LINE void *found; if (0 == cx_tree_search_data( @@ -818,24 +818,21 @@ assert(search_func != NULL); assert(search_data_func != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); - if (tree == NULL) return NULL; + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); + if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; - tree->allocator = allocator; + tree->collection.allocator = allocator; tree->node_create = create_func; tree->search = search_func; tree->search_data = search_data_func; - tree->simple_destructor = NULL; - tree->advanced_destructor = (cx_destructor_func2) cxFree; - tree->destructor_data = (void *) allocator; + tree->collection.advanced_destructor = (cx_destructor_func2) cxFree; + tree->collection.destructor_data = (void *) allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; - tree->root = NULL; - tree->size = 0; return tree; } @@ -845,7 +842,7 @@ if (tree->root != NULL) { cxTreeClear(tree); } - cxFree(tree->allocator, tree); + cxFree(tree->collection.allocator, tree); } CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, @@ -856,47 +853,41 @@ } assert(root != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); - if (tree == NULL) return NULL; + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); + if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; // set the allocator anyway, just in case... - tree->allocator = allocator; - tree->node_create = NULL; - tree->search = NULL; - tree->search_data = NULL; - tree->simple_destructor = NULL; - tree->advanced_destructor = NULL; - tree->destructor_data = NULL; + tree->collection.allocator = allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; tree->root = root; - tree->size = cxTreeSubtreeSize(tree, root); + tree->collection.size = cxTreeSubtreeSize(tree, root); return tree; } void cxTreeSetParent(CxTree *tree, void *parent, void *child) { size_t loc_parent = tree->loc_parent; if (tree_parent(child) == NULL) { - tree->size++; + tree->collection.size++; } cx_tree_link(parent, child, cx_tree_node_layout(tree)); } void cxTreeAddChildNode(CxTree *tree, void *parent, void *child) { cx_tree_link(parent, child, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; } int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { void *node = tree->node_create(data, tree); - if (node == NULL) return 1; + if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); cx_tree_link(parent, node, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; return 0; } @@ -948,7 +939,7 @@ } size_t cxTreeSize(CxTree *tree) { - return tree->size; + return tree->collection.size; } size_t cxTreeDepth(CxTree *tree) { @@ -1002,7 +993,7 @@ if (loc_last_child >= 0) tree_last_child(node) = NULL; // the tree now has one member less - tree->size--; + tree->collection.size--; return 0; } @@ -1010,12 +1001,12 @@ void cxTreeRemoveSubtree(CxTree *tree, void *node) { if (node == tree->root) { tree->root = NULL; - tree->size = 0; + tree->collection.size = 0; return; } size_t subtree_size = cxTreeSubtreeSize(tree, node); cx_tree_unlink(node, cx_tree_node_layout(tree)); - tree->size -= subtree_size; + tree->collection.size -= subtree_size; } int cxTreeDestroyNode( @@ -1025,12 +1016,7 @@ ) { int result = cxTreeRemoveNode(tree, node, relink_func); if (result == 0) { - if (tree->simple_destructor) { - tree->simple_destructor(node); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, node); - } + cx_invoke_destructor(tree, node); return 0; } else { return result; @@ -1045,15 +1031,10 @@ ); cx_foreach(void *, child, iter) { if (iter.exiting) { - if (tree->simple_destructor) { - tree->simple_destructor(child); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, child); - } + cx_invoke_destructor(tree, child); } } - tree->size -= iter.counter; + tree->collection.size -= iter.counter; if (node == tree->root) { tree->root = NULL; } @@ -1071,6 +1052,7 @@ cxFreeDefault(q); q = next; } + visitor->queue_next = visitor->queue_last = NULL; } CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit) { diff -r 3da24640513a -r e57ca2747782 ui/cocoa/ListDataSource.h --- a/ui/cocoa/ListDataSource.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/cocoa/ListDataSource.h Sat Dec 13 15:58:58 2025 +0100 @@ -27,7 +27,7 @@ */ #import "toolkit.h" -#import "../ui/tree.h" +#import "../ui/list.h" @interface ListDataSource : NSObject diff -r 3da24640513a -r e57ca2747782 ui/cocoa/list.h --- a/ui/cocoa/list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/cocoa/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -28,7 +28,7 @@ #import "toolkit.h" #import "Container.h" -#import "../ui/tree.h" +#import "../ui/list.h" #import "ListDataSource.h" diff -r 3da24640513a -r e57ca2747782 ui/cocoa/list.m --- a/ui/cocoa/list.m Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/cocoa/list.m Sat Dec 13 15:58:58 2025 +0100 @@ -179,7 +179,7 @@ ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata]; if(model) { - dataSource.model = ui_model_copy(obj->ctx, model); + dataSource.model = model; } tableview.dataSource = dataSource; @@ -263,7 +263,7 @@ @end -UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) { NSComboBox *dropdown = [[NSComboBox alloc] init]; dropdown.editable = NO; diff -r 3da24640513a -r e57ca2747782 ui/cocoa/toolkit.m --- a/ui/cocoa/toolkit.m Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/cocoa/toolkit.m Sat Dec 13 15:58:58 2025 +0100 @@ -33,6 +33,7 @@ #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/threadpool.h" +#include "../common/app.h" #import "image.h" #import "menu.h" @@ -46,13 +47,6 @@ static int app_argc; static const char **app_argv; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static UiBool exit_on_shutdown; /* ------------------- App Init / Event Loop functions ------------------- */ @@ -85,21 +79,6 @@ return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } @@ -111,9 +90,7 @@ e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(startup_func) { - startup_func(&e, startup_data); - } + uic_application_startup(&e); } void ui_cocoa_onopen(const char *file) { @@ -123,9 +100,7 @@ e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(open_func) { - open_func(&e, open_data); - } + uic_application_open(&e); } void ui_cocoa_onexit(void) { @@ -135,9 +110,7 @@ e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(exit_func) { - exit_func(&e, exit_data); - } + uic_application_exit(&e); } void ui_main(void) { diff -r 3da24640513a -r e57ca2747782 ui/cocoa/window.m --- a/ui/cocoa/window.m Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/cocoa/window.m Sat Dec 13 15:58:58 2025 +0100 @@ -67,21 +67,18 @@ return obj; } -UiObject* ui_window(const char *title, void *window_data) { +UiObject* ui_window(const char *title) { UiObject *obj = create_window(title, FALSE, FALSE, FALSE); - obj->window = window_data; return obj; } -UiObject* ui_simple_window(const char *title, void *window_data) { +UiObject* ui_simple_window(const char *title) { UiObject *obj = create_window(title, TRUE, FALSE, FALSE); - obj->window = window_data; return obj; } -UiObject* ui_sidebar_window(const char *title, void *window_data) { +UiObject* ui_sidebar_window(const char *title) { UiObject *obj = create_window(title, FALSE, TRUE, FALSE); - obj->window = window_data; return obj; } diff -r 3da24640513a -r e57ca2747782 ui/common/app.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/app.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,71 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "app.h" + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +void *open_data; +static ui_callback exit_func; +void *exit_data; + + +void ui_onstartup(ui_callback f, void *userdata) { + startup_func = f; + startup_data = userdata; +} + +void ui_onopen(ui_callback f, void *userdata) { + open_func = f; + open_data = userdata; +} + +void ui_onexit(ui_callback f, void *userdata) { + exit_func = f; + exit_data = userdata; +} + + +void uic_application_startup(UiEvent *event) { + if(startup_func) { + startup_func(event, startup_data); + } +} + +void uic_application_open(UiEvent *event) { + if(open_func) { + open_func(event, open_data); + } +} + +void uic_application_exit(UiEvent *event) { + if(exit_func) { + exit_func(event, exit_data); + } +} diff -r 3da24640513a -r e57ca2747782 ui/common/app.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/app.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_APP_H +#define UIC_APP_H + +#include "../ui/toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void uic_application_startup(UiEvent *event); +void uic_application_open(UiEvent *event); +void uic_application_exit(UiEvent *event); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_APP_H */ + diff -r 3da24640513a -r e57ca2747782 ui/common/args.c --- a/ui/common/args.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/args.c Sat Dec 13 15:58:58 2025 +0100 @@ -175,10 +175,10 @@ free((void*)args->lbutton2); free((void*)args->rbutton3); free((void*)args->rbutton4); - free((void*)args->lbutton1_groups); - free((void*)args->lbutton2_groups); - free((void*)args->rbutton3_groups); - free((void*)args->rbutton4_groups); + free((void*)args->lbutton1_states); + free((void*)args->lbutton2_states); + free((void*)args->rbutton3_states); + free((void*)args->rbutton4_states); free(args); } @@ -310,16 +310,16 @@ args->onclickdata = onclickdata; } -void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toolbar_item_args_set_states(UiToolbarItemArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toolbar_item_args_free(UiToolbarItemArgs *args) { free((void*)args->label); free((void*)args->icon); free((void*)args->tooltip); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -355,10 +355,10 @@ args->onchangedata = onchangedata; } -void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toolbar_toggleitem_args_set_states(UiToolbarToggleItemArgs *args,int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { @@ -366,7 +366,7 @@ free((void*)args->icon); free((void*)args->tooltip); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1383,10 +1383,10 @@ args->onclickdata = onclickdata; } -void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_button_args_set_states(UiButtonArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_button_args_free(UiButtonArgs *args) { @@ -1395,7 +1395,7 @@ free((void*)args->label); free((void*)args->icon); free((void*)args->tooltip); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1502,14 +1502,14 @@ args->value = value; } -void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group) { - args->enable_group = group; -} - -void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toggle_args_set_enablestate(UiToggleArgs *args, int state) { + args->enable_state = state; +} + +void ui_toggle_args_set_states(UiToggleArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toggle_args_free(UiToggleArgs *args) { @@ -1519,7 +1519,7 @@ free((void*)args->icon); free((void*)args->tooltip); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1633,10 +1633,10 @@ args->value = value; } -void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_linkbutton_args_set_states(UiLinkButtonArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_linkbutton_args_free(UiLinkButtonArgs *args) { @@ -1645,7 +1645,7 @@ free((void*)args->label); free((void*)args->uri); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1815,10 +1815,10 @@ args->contextmenu = menubuilder; } -void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_list_args_set_states(UiListArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_list_args_free(UiListArgs *args) { @@ -1831,7 +1831,7 @@ } free(args->static_elements); } - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1971,7 +1971,7 @@ free((void*)args->style_class); free((void*)args->varname); free((void*)args->sublists); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2071,17 +2071,17 @@ args->value = value; } -void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_textarea_args_set_states(UiTextAreaArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_textarea_args_free(UiTextAreaArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2191,17 +2191,17 @@ args->value = value; } -void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_textfield_args_set_states(UiTextFieldArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_textfield_args_free(UiTextFieldArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2314,17 +2314,17 @@ args->rangevalue = value; } -void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_spinbox_args_set_states(UiSpinBoxArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_spinbox_args_free(UiSpinBoxArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2414,17 +2414,17 @@ args->value = value; } -void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_webview_args_set_states(UiWebviewArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_webview_args_free(UiWebviewArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } diff -r 3da24640513a -r e57ca2747782 ui/common/args.h --- a/ui/common/args.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/args.h Sat Dec 13 15:58:58 2025 +0100 @@ -36,7 +36,7 @@ #include "../ui/entry.h" #include "../ui/menu.h" #include "../ui/toolbar.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../ui/text.h" #include "../ui/webview.h" #include "../ui/widget.h" @@ -107,7 +107,7 @@ UIEXPORT void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip); UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata); -UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_item_args_set_states(UiToolbarItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_item_args_free(UiToolbarItemArgs *args); UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void); @@ -117,7 +117,7 @@ UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname); UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata); -UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_toggleitem_args_set_states(UiToolbarToggleItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args); UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void); @@ -137,7 +137,7 @@ UIEXPORT void ui_container_args_set_margin_left(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_top(UiContainerArgs *args, int value); -UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_bottom(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_colspan(UiContainerArgs *args, int colspan); UIEXPORT void ui_container_args_set_rowspan(UiContainerArgs *args, int rowspan); UIEXPORT void ui_container_args_set_def_hexpand(UiContainerArgs *args, UiBool value); @@ -346,7 +346,7 @@ UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype); UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback); UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata); -UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_button_args_set_states(UiButtonArgs *args, int *states, int numstates); UIEXPORT void ui_button_args_free(UiButtonArgs *args); UIEXPORT UiToggleArgs* ui_toggle_args_new(void); @@ -373,8 +373,8 @@ UIEXPORT void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata); UIEXPORT void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname); UIEXPORT void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value); -UIEXPORT void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group); -UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates); +UIEXPORT void ui_toggle_args_set_enablestate(UiToggleArgs *args, int state); +UIEXPORT void ui_toggle_args_set_states(UiToggleArgs *args, int *states, int numstates); UIEXPORT void ui_toggle_args_free(UiToggleArgs *args); UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void); @@ -401,7 +401,7 @@ UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata); UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value); UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type); -UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_linkbutton_args_set_states(UiLinkButtonArgs *args, int *states, int numstates); UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args); UIEXPORT UiListArgs* ui_list_args_new(void); @@ -443,7 +443,7 @@ UIEXPORT void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection); UIEXPORT void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder); -UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates); +UIEXPORT void ui_list_args_set_states(UiListArgs *args, int *states, int numstates); UIEXPORT void ui_list_args_free(UiListArgs *args); UIEXPORT UiSourceListArgs* ui_sourcelist_args_new(void); @@ -495,7 +495,7 @@ UIEXPORT void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata); UIEXPORT void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname); UIEXPORT void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value); -UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates); +UIEXPORT void ui_textarea_args_set_states(UiTextAreaArgs *args, int *states, int numstates); UIEXPORT void ui_textarea_args_free(UiTextAreaArgs *args); UIEXPORT UiTextFieldArgs* ui_textfield_args_new(void); @@ -520,7 +520,7 @@ UIEXPORT void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata); UIEXPORT void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname); UIEXPORT void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value); -UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates); +UIEXPORT void ui_textfield_args_set_states(UiTextFieldArgs *args, int *states, int numstates); UIEXPORT void ui_textfield_args_free(UiTextFieldArgs *args); UIEXPORT UiSpinBoxArgs* ui_spinbox_args_new(void); @@ -549,7 +549,7 @@ UIEXPORT void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value); UIEXPORT void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value); UIEXPORT void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value); -UIEXPORT void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates); +UIEXPORT void ui_spinbox_args_set_states(UiSpinBoxArgs *args, int *states, int numstates); UIEXPORT void ui_spinbox_args_free(UiSpinBoxArgs *args); UIEXPORT UiWebviewArgs* ui_webview_args_new(void); @@ -570,7 +570,7 @@ UIEXPORT void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname); UIEXPORT void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname); UIEXPORT void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value); -UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates); +UIEXPORT void ui_webview_args_set_states(UiWebviewArgs *args, int *states, int numstates); UIEXPORT void ui_webview_args_free(UiWebviewArgs *args); #ifdef __cplusplus diff -r 3da24640513a -r e57ca2747782 ui/common/context.c --- a/ui/common/context.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/context.c Sat Dec 13 15:58:58 2025 +0100 @@ -64,8 +64,8 @@ ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, CX_STORE_POINTERS); - ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget)); - ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); + ctx->state_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiStateWidget)); + ctx->states = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); ctx->attach_document = uic_context_attach_document; ctx->detach_document2 = uic_context_detach_document; @@ -92,11 +92,17 @@ } void uic_context_prepare_close(UiContext *ctx) { - cxListClear(ctx->groups); - cxListClear(ctx->group_widgets); + cxListClear(ctx->states); + cxListClear(ctx->state_widgets); } void uic_context_attach_document(UiContext *ctx, void *document) { + if(ctx->single_document_mode) { + if(ctx->document) { + uic_context_detach_document(ctx, ctx->document); + } + } + cxListAdd(ctx->documents, document); ctx->document = document; @@ -114,7 +120,7 @@ UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); if(docvar) { // bind var to document var - uic_copy_binding(var, docvar, TRUE); + uic_copy_var_binding(var, docvar, TRUE); cxIteratorFlagRemoval(i); } } @@ -124,16 +130,19 @@ } static void uic_context_unbind_vars(UiContext *ctx) { + ui_onchange_events_enable(FALSE); CxMapIterator mi = cxMapIterator(ctx->vars); cx_foreach(CxMapEntry*, entry, mi) { + printf("detach %.*s\n", (int)entry->key->len, (char*)entry->key->data); UiVar *var = entry->value; // var->from && var->from_ctx && var->from_ctx != ctx uic_save_var(var); if(var->from) { - uic_copy_binding(var, var->from, FALSE); + uic_copy_var_binding(var, var->from, FALSE); cxMapPut(var->from->from_ctx->vars, *entry->key, var->from); var->from = NULL; } + uic_unbind_var(var); } if(ctx->documents) { @@ -143,6 +152,8 @@ uic_context_unbind_vars(subctx); } } + + ui_onchange_events_enable(TRUE); } void uic_context_detach_document(UiContext *ctx, void *document) { @@ -197,6 +208,14 @@ return ctx_getvar(ctx, key); } +UiVar* uic_get_var_t(UiContext *ctx,const char *name, UiVarType type) { + UiVar *var = uic_get_var(ctx, name); + if(var && var->type == type) { + return var; + } + return NULL; +} + UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { UiVar *var = uic_get_var(ctx, name); if(var) { @@ -213,8 +232,9 @@ var->original_value = NULL; var->from = NULL; var->from_ctx = ctx; - - cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); + var->bound = FALSE; + + cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); // TODO: use another destructor that cleans the value (UiString free, UiText destroy, ...) cxMapPut(ctx->vars, name, var); @@ -266,6 +286,40 @@ return val; } +// destroys a value, that was created by uic_create_value +void uic_destroy_value(UiContext *ctx, UiVarType type, void *value) { + switch(type) { + default: { + ui_free(ctx, value); + break; + } + case UI_VAR_SPECIAL: break; + case UI_VAR_STRING: { + UiString *s = value; + if(s->value.free) { + s->value.free(s->value.ptr); + } + ui_free(ctx, value); + } + case UI_VAR_TEXT: { + UiText *t = value; + if(t->value.free) { + t->value.free(t->value.ptr); + t->value.free = NULL; + t->value.ptr = NULL; + } + if(t->destroy) { + t->destroy(t); + } + ui_free(ctx, value); + } + case UI_VAR_LIST: { + ui_list_free(ctx, value); + break; + } + } +} + UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type) { if (value) { @@ -278,7 +332,7 @@ } -void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { +void uic_copy_var_binding(UiVar *from, UiVar *to, UiBool copytodoc) { // check type if(from->type != to->type) { fprintf(stderr, "UI Error: var has incompatible type.\n"); @@ -301,32 +355,36 @@ } } - ui_setop_enable(TRUE); + uic_copy_value_binding(from->type, fromvalue, tovalue); +} + +void uic_copy_value_binding(UiVarType type, void *from, void *to) { + ui_setop_enable(TRUE); // copy binding - // we don't copy the observer, because the from var has never one - switch(from->type) { + // we don't copy the observer, because the from value never has oberservers + switch(type) { default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break; case UI_VAR_SPECIAL: break; case UI_VAR_INTEGER: { - UiInteger *f = fromvalue; - UiInteger *t = tovalue; + UiInteger *f = from; + UiInteger *t = to; if(!f->obj) break; uic_int_copy(f, t); t->set(t, t->value); break; } case UI_VAR_DOUBLE: { - UiDouble *f = fromvalue; - UiDouble *t = tovalue; + UiDouble *f = from; + UiDouble *t = to; if(!f->obj) break; uic_double_copy(f, t); t->set(t, t->value); break; } case UI_VAR_STRING: { - UiString *f = fromvalue; - UiString *t = tovalue; + UiString *f = from; + UiString *t = to; if(!f->obj) break; uic_string_copy(f, t); char *tvalue = t->value.ptr ? t->value.ptr : ""; @@ -335,23 +393,23 @@ break; } case UI_VAR_TEXT: { - UiText *f = fromvalue; - UiText *t = tovalue; + UiText *f = from; + UiText *t = to; if(!f->obj) break; uic_text_copy(f, t); t->restore(t); break; } case UI_VAR_LIST: { - UiList *f = fromvalue; - UiList *t = tovalue; + UiList *f = from; + UiList *t = to; uic_list_copy(f, t); ui_list_update(t); break; } case UI_VAR_RANGE: { - UiRange *f = fromvalue; - UiRange *t = tovalue; + UiRange *f = from; + UiRange *t = to; if(!f->obj) break; uic_range_copy(f, t); t->setextent(t, t->extent); @@ -360,8 +418,8 @@ break; } case UI_VAR_GENERIC: { - UiGeneric *f = fromvalue; - UiGeneric *t = tovalue; + UiGeneric *f = from; + UiGeneric *t = to; if(!f->obj) break; uic_generic_copy(f, t); t->set(t, t->value, t->type); @@ -398,59 +456,56 @@ } } +const char *uic_type2str(UiVarType type) { + switch(type) { + default: return ""; + case UI_VAR_INTEGER: return "int"; + case UI_VAR_DOUBLE: return "double"; + case UI_VAR_STRING: return "string"; + case UI_VAR_TEXT: return "text"; + case UI_VAR_LIST: return "list"; + case UI_VAR_RANGE: return "range"; + case UI_VAR_GENERIC: return "generic"; + } +} + void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value) { - // TODO: do we need/want this? Why adding vars to a context after - // widgets reference these? Workarounds: - // 1. add vars to ctx before creating ui - // 2. create ui, create new document with vars, attach doc - // also it would be possible to create a function, that scans unbound vars - // and connects them to available vars - /* - UiContext *rootctx = uic_root_context(ctx); - UiVar *b = NULL; - if(rootctx->bound) { - // some widgets are already bound to some vars - b = ucx_map_cstr_get(rootctx->bound, name); - if(b) { - // a widget is bound to a var with this name - // if ctx is the root context we can remove the var from bound - // because set_doc or detach can't fuck things up - if(ctx == rootctx) { - ucx_map_cstr_remove(ctx->bound, name); - // TODO: free stuff - } + UiVar *var = cxMapGet(ctx->vars, name); + if(!var) { + // create new var and add it to the context var map + var = ui_malloc(ctx, sizeof(UiVar)); + cxMapPut(ctx->vars, name, var); + } else { + // override var with new value + if(var->type != type) { + fprintf(stderr, "Error: var %s type mismatch: %s - %s\n", name, uic_type2str(var->type), uic_type2str(type)); + return; } + if(var->bound) { + fprintf(stderr, "Error: var %s already bound\n", name); + return; + } + UiInteger *prev_value = var->value; + uic_copy_value_binding(type, prev_value, value); + uic_destroy_value(var->from_ctx, var->type, var->value); } - */ - // create new var and add it to doc's vars - UiVar *var = ui_malloc(ctx, sizeof(UiVar)); var->type = type; var->value = value; var->from = NULL; var->from_ctx = ctx; - size_t oldcount = cxMapSize(ctx->vars); - cxMapPut(ctx->vars, name, var); - if(cxMapSize(ctx->vars) != oldcount + 1) { - fprintf(stderr, "UiError: var '%s' already exists\n", name); - } - - // TODO: remove? - // a widget is already bound to a var with this name - // copy the binding (like uic_context_set_document) - /* - if(b) { - uic_copy_binding(b, var, TRUE); - } - */ + var->bound = TRUE; } -void uic_remove_bound_var(UiContext *ctx, UiVar *var) { - // TODO +// public API + +void* ui_context_get_document(UiContext *ctx) { + return ctx->document; } - -// public API +void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable) { + ctx->single_document_mode = enable; +} void ui_attach_document(UiContext *ctx, void *document) { uic_context_attach_document(ctx, document); @@ -478,48 +533,48 @@ } -void ui_set_group(UiContext *ctx, int group) { - if(!cxListIndexValid(ctx->groups, cxListFind(ctx->groups, &group))) { - cxListAdd(ctx->groups, &group); +void ui_set_state(UiContext *ctx, int state) { + if(!cxListIndexValid(ctx->states, cxListFind(ctx->states, &state))) { + cxListAdd(ctx->states, &state); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -void ui_unset_group(UiContext *ctx, int group) { - int i = cxListFind(ctx->groups, &group); +void ui_unset_state(UiContext *ctx, int state) { + int i = cxListFind(ctx->states, &state); if(i != -1) { - cxListRemove(ctx->groups, i); + cxListRemove(ctx->states, i); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -int* ui_active_groups(UiContext *ctx, int *ngroups) { - *ngroups = cxListSize(ctx->groups); - return cxListAt(ctx->groups, 0); +int* ui_active_states(UiContext *ctx, int *nstates) { + *nstates = cxListSize(ctx->states); + return cxListAt(ctx->states, 0); } -void uic_check_group_widgets(UiContext *ctx) { +void uic_check_state_widgets(UiContext *ctx) { int ngroups = 0; - int *groups = ui_active_groups(ctx, &ngroups); + int *groups = ui_active_states(ctx, &ngroups); - CxIterator i = cxListIterator(ctx->group_widgets); - cx_foreach(UiGroupWidget *, gw, i) { - char *check = calloc(1, gw->numgroups); + CxIterator i = cxListIterator(ctx->state_widgets); + cx_foreach(UiStateWidget *, gw, i) { + char *check = calloc(1, gw->numstates); for(int i=0;inumgroups;k++) { - if(groups[i] == gw->groups[k]) { + for(int k=0;knumstates;k++) { + if(groups[i] == gw->states[k]) { check[k] = 1; } } } int enable = 1; - for(int i=0;inumgroups;i++) { + for(int i=0;inumstates;i++) { if(check[i] == 0) { enable = 0; break; @@ -530,70 +585,70 @@ } } -void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { +void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - // get groups - CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); + // get states + CxList *states = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); va_list ap; va_start(ap, enable); - int group; - while((group = va_arg(ap, int)) != -1) { - cxListAdd(groups, &group); + int state; + while((state = va_arg(ap, int)) != -1) { + cxListAdd(states, &state); } va_end(ap); - uic_add_group_widget(ctx, widget, enable, groups); + uic_add_state_widget(ctx, widget, enable, states); - cxListFree(groups); + cxListFree(states); } -void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups) { +void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups); - for(int i=0;i= 0;i++) { } + for(i=0;states[i] >= 0;i++) { } return i; } -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) { - uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups)); +void uic_add_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states) { + uic_add_state_widget_i(ctx, widget, enable, cxListAt(states, 0), cxListSize(states)); } -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) { +void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates) { const CxAllocator *a = ctx->allocator; - UiGroupWidget gw; + UiStateWidget gw; gw.widget = widget; gw.enable = enable; - gw.numgroups = numgroups; - gw.groups = cxCalloc(a, numgroups, sizeof(int)); + gw.numstates = numstates; + gw.states = cxCalloc(a, numstates, sizeof(int)); - // copy groups - if(groups) { - memcpy(gw.groups, groups, gw.numgroups * sizeof(int)); + // copy states + if(states) { + memcpy(gw.states, states, gw.numstates * sizeof(int)); } - cxListAdd(ctx->group_widgets, &gw); + cxListAdd(ctx->state_widgets, &gw); } -void uic_remove_group_widget(UiContext *ctx, void *widget) { - (void)cxListFindRemove(ctx->group_widgets, widget); +void uic_remove_state_widget(UiContext *ctx, void *widget) { + (void)cxListFindRemove(ctx->state_widgets, widget); } UIEXPORT void *ui_allocator(UiContext *ctx) { @@ -638,3 +693,33 @@ void ui_set_destructor(void *mem, ui_destructor_func destr) { cxMempoolSetDestructor(mem, (cx_destructor_func)destr); } + +UiInteger* ui_get_int_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_INTEGER); + return var ? var->value : NULL; +} + +UiDouble* ui_get_double_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_DOUBLE); + return var ? var->value : NULL; +} + +UiString* ui_get_string_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_STRING); + return var ? var->value : NULL; +} + +UiText* ui_get_text_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_TEXT); + return var ? var->value : NULL; +} + +UiRange* ui_get_range_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_RANGE); + return var ? var->value : NULL; +} + +UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_GENERIC); + return var ? var->value : NULL; +} diff -r 3da24640513a -r e57ca2747782 ui/common/context.h --- a/ui/common/context.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/context.h Sat Dec 13 15:58:58 2025 +0100 @@ -43,7 +43,7 @@ typedef struct UiVar UiVar; typedef struct UiListPtr UiListPtr; typedef struct UiListVar UiListVar; -typedef struct UiGroupWidget UiGroupWidget; +typedef struct UiStateWidget UiStateWidget; typedef struct UiDestroyHandler UiDestroyHandler; typedef enum UiVarType { @@ -69,8 +69,8 @@ CxMap *vars; - CxList *groups; // int list - CxList *group_widgets; // UiGroupWidget list + CxList *states; // int list + CxList *state_widgets; // UiGroupWidget list void (*attach_document)(UiContext *ctx, void *document); void (*detach_document2)(UiContext *ctx, void *document); @@ -84,8 +84,10 @@ GtkAccelGroup *accel_group; #endif #endif - - + + // allow only one document to be attached + // attaching a document will automatically detach the current document + UiBool single_document_mode; ui_callback close_callback; void *close_data; @@ -97,13 +99,14 @@ UiVarType type; UiVar *from; UiContext *from_ctx; + UiBool bound; }; -struct UiGroupWidget { +struct UiStateWidget { void *widget; ui_enablefunc enable; - int *groups; - int numgroups; + int *states; + int numstates; }; struct UiDestroyHandler { @@ -127,25 +130,27 @@ void uic_context_detach_all(UiContext *ctx); UiVar* uic_get_var(UiContext *ctx, const char *name); +UiVar* uic_get_var_t(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_value_var(UiContext *ctx, void *value); void* uic_create_value(UiContext *ctx, UiVarType type); +void uic_destroy_value(UiContext *ctx, UiVarType type, void *value); UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type); -void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc); +void uic_copy_var_binding(UiVar *from, UiVar *to, UiBool copytodoc); +void uic_copy_value_binding(UiVarType type, void *from, void *to); void uic_save_var(UiVar *var); void uic_unbind_var(UiVar *var); +const char *uic_type2str(UiVarType type); void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value); -void uic_remove_bound_var(UiContext *ctx, UiVar *var); - -size_t uic_group_array_size(const int *groups); -void uic_check_group_widgets(UiContext *ctx); -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups); -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups); -void uic_remove_group_widget(UiContext *ctx, void *widget); +size_t uic_state_array_size(const int *states); +void uic_check_state_widgets(UiContext *ctx); +void uic_add_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states); +void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates); +void uic_remove_state_widget(UiContext *ctx, void *widget); #ifdef __cplusplus } diff -r 3da24640513a -r e57ca2747782 ui/common/menu.c --- a/ui/common/menu.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/menu.c Sat Dec 13 15:58:58 2025 +0100 @@ -89,20 +89,20 @@ return s ? strdup(s) : NULL; } -int* uic_copy_groups(const int* groups, size_t *ngroups) { - *ngroups = 0; - if (!groups) { +int* uic_copy_states(const int* states, size_t *nstates) { + *nstates = 0; + if (!states) { return NULL; } size_t n; - for (n = 0; groups[n] > -1; n++) { } + for (n = 0; states[n] > -1; n++) { } - if (ngroups > 0) { + if (nstates > 0) { int* newarray = calloc(n+1, sizeof(int)); - memcpy(newarray, groups, n * sizeof(int)); + memcpy(newarray, states, n * sizeof(int)); newarray[n] = -1; - *ngroups = n; + *nstates = n; return newarray; } return NULL; @@ -152,7 +152,7 @@ item->icon = nl_strdup(args->icon); item->userdata = args->onclickdata; item->callback = args->onclick; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->states, &item->nstates); add_item((UiMenuItemI*)item); } @@ -179,7 +179,7 @@ item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; item->callback = args->onchange; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->nstates, &item->nstates); add_item((UiMenuItemI*)item); } @@ -196,7 +196,7 @@ item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; item->callback = args->onchange; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->nstates, &item->nstates); add_item((UiMenuItemI*)item); } @@ -296,14 +296,14 @@ } case UI_MENU_ITEM: { UiMenuItem *i = (UiMenuItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); break; } case UI_MENU_CHECK_ITEM: { UiMenuCheckItem *i = (UiMenuCheckItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); free(i->varname); @@ -311,7 +311,7 @@ } case UI_MENU_RADIO_ITEM: { UiMenuRadioItem *i = (UiMenuRadioItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); free(i->varname); diff -r 3da24640513a -r e57ca2747782 ui/common/menu.h --- a/ui/common/menu.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/menu.h Sat Dec 13 15:58:58 2025 +0100 @@ -79,8 +79,8 @@ char *label; char *icon; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuCheckItem { @@ -90,8 +90,8 @@ char *varname; ui_callback callback; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuRadioItem { @@ -101,8 +101,8 @@ char *varname; ui_callback callback; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuItemList { @@ -129,7 +129,7 @@ void uic_add_menu_to_stack(UiMenu* menu); -int* uic_copy_groups(const int* groups, size_t *ngroups); +int* uic_copy_states(const int* states, size_t *nstates); void uic_set_tmp_eventdata(void *eventdata, int type); void* uic_get_tmp_eventdata(void); diff -r 3da24640513a -r e57ca2747782 ui/common/message.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/message.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,214 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _WIN32 + +#include +#include +#include + +#include "message.h" + +int uic_message_send_(UiMessageHandler *handler, cxstring msg) { + return handler->send(handler, msg); +} + +UiMessageHandler* uic_simple_msg_handler(int in, int out, msg_received_callback callback) { + UiSimpleMessageHandler *handler = malloc(sizeof(UiSimpleMessageHandler)); + handler->handler.start = uic_simple_msg_handler_start; + handler->handler.stop = uic_simple_msg_handler_stop; + handler->handler.send = uic_simple_msg_handler_send; + handler->handler.callback = callback; + handler->in = in; + handler->out = out; + handler->outbuf = cxBufferCreate(NULL, 4096, NULL, CX_BUFFER_FREE_CONTENTS | CX_BUFFER_AUTO_EXTEND); + handler->stop = 0; + pthread_mutex_init(&handler->queue_lock, NULL); + pthread_mutex_init(&handler->avlbl_lock, NULL); + pthread_cond_init(&handler->available, NULL); + return (UiMessageHandler*)handler; +} + +int uic_simple_msg_handler_start(UiMessageHandler *handler) { + UiSimpleMessageHandler *sh = (UiSimpleMessageHandler*)handler; + if(pthread_create(&sh->in_thread, NULL, uic_simple_msg_handler_in_thread, sh)) { + return 1; + } + if(pthread_create(&sh->out_thread, NULL, uic_simple_msg_handler_out_thread, sh)) { + return 1; + } + return 0; +} + +int uic_simple_msg_handler_stop(UiMessageHandler *handler) { + UiSimpleMessageHandler *sh = (UiSimpleMessageHandler*)handler; + pthread_mutex_lock(&sh->queue_lock); + sh->stop = 0; + pthread_cond_signal(&sh->available); + pthread_mutex_unlock(&sh->queue_lock); + close(sh->in); + sh->in = -1; + + pthread_join(sh->in_thread, NULL); + pthread_join(sh->out_thread, NULL); + + return 0; +} + +int uic_simple_msg_handler_send(UiMessageHandler *handler, cxstring msg) { + UiSimpleMessageHandler *sh = (UiSimpleMessageHandler*)handler; + pthread_mutex_lock(&sh->queue_lock); + char header[32]; + snprintf(header, 32, "%zu\n", msg.length); + cxBufferPutString(sh->outbuf, header); + cxBufferWrite(msg.ptr, 1, msg.length, sh->outbuf); + pthread_cond_signal(&sh->available); + pthread_mutex_unlock(&sh->queue_lock); + return 0; +} + +#define HEADERBUF_SIZE 64 + +void* uic_simple_msg_handler_in_thread(void *data) { + UiSimpleMessageHandler *handler = data; + + char *msg = NULL; + size_t msg_size = 0; + size_t msg_pos = 0; // currently received message length + + char headerbuf[HEADERBUF_SIZE]; + size_t headerpos = 0; + + char buf[2048]; + ssize_t r; + while((r = read(handler->in, buf, 2024)) > 0) { + char *buffer = buf; + size_t available = r; + + while(available > 0) { + if(msg) { + // read message + size_t need = msg_size - msg_pos; + size_t cplen = r > need ? need : available; + memcpy(msg+msg_pos, buffer, cplen); + buffer += cplen; + available -= cplen; + msg_pos += cplen; + if(msg_pos == msg_size) { + // message complete + //fprintf(stderr, "send: %.*s\n", (int)msg_size, msg); + if(handler->handler.callback) { + handler->handler.callback(cx_strn(msg, msg_size)); + } + free(msg); + msg = NULL; + msg_size = 0; + msg_pos = 0; + } + } else { + size_t header_max = HEADERBUF_SIZE - headerpos - 1; + if(header_max > available) { + header_max = available; + } + // search for line break + int i; + int header_complete = 0; + for(i=0;i= HEADERBUF_SIZE) { + fprintf(stderr, "Error: message header too big\n"); + exit(-1); + } + } + } + + + } + perror("error"); + fprintf(stderr, "stop simple_msg_handler_in_thread\n"); + + return NULL; +} + +void* uic_simple_msg_handler_out_thread(void *data) { + UiSimpleMessageHandler *handler = data; + CxBuffer *buffer = handler->outbuf; + + pthread_mutex_lock(&handler->queue_lock); + + for(;;) { + if(buffer->pos == 0) { + pthread_cond_wait(&handler->available, &handler->queue_lock); + continue; + } else { + size_t n = buffer->pos; + size_t pos = 0; + while(n > 0) { + ssize_t w = write(handler->out, buffer->space + pos, n); + if(w <= 0) { + fprintf(stderr, "Error: output error\n"); + break; + } + n -= w; + pos += w; + } + if(n > 0) { + break; // error + } + buffer->pos = 0; + } + } + + pthread_mutex_unlock(&handler->queue_lock); + + return NULL; +} + +#endif diff -r 3da24640513a -r e57ca2747782 ui/common/message.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/message.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_MESSAGE_H +#define UIC_MESSAGE_H + + +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct UiMessageHandler UiMessageHandler; + +typedef void(*msg_received_callback)(cxstring msg); + +struct UiMessageHandler { + int (*start)(UiMessageHandler *handler); + int (*stop)(UiMessageHandler *handler); + int (*send)(UiMessageHandler *handler, cxstring msg); + + msg_received_callback callback; +}; + +typedef struct UiSimpleMessageHandler { + UiMessageHandler handler; + int in; + int out; +#ifndef _WIN32 + pthread_t in_thread; + pthread_t out_thread; + pthread_mutex_t queue_lock; + pthread_mutex_t avlbl_lock; + pthread_cond_t available; +#endif + CxBuffer *outbuf; + int stop; +} UiSimpleMessageHandler; + +int uic_message_send_(UiMessageHandler *handler, cxstring msg); +#define uic_message_send(handler, msg) uic_message_send_(handler, cx_strcast(msg)) + +UiMessageHandler* uic_simple_msg_handler(int in, int out, msg_received_callback callback); +int uic_simple_msg_handler_start(UiMessageHandler *handler); +int uic_simple_msg_handler_stop(UiMessageHandler *handler); +int uic_simple_msg_handler_send(UiMessageHandler *handler, cxstring msg); + +void* uic_simple_msg_handler_in_thread(void *data); +void* uic_simple_msg_handler_out_thread(void *data); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_MESSAGE_H */ + diff -r 3da24640513a -r e57ca2747782 ui/common/object.c --- a/ui/common/object.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/object.c Sat Dec 13 15:58:58 2025 +0100 @@ -32,6 +32,7 @@ #include "context.h" #include +#include #include "../ui/container.h" @@ -108,7 +109,7 @@ UiObject* uic_object_new_toplevel(void) { fflush(stdout); CxMempool *mp = cxMempoolCreateSimple(256); - UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); + UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObjectPrivate)); fflush(stdout); obj->ctx = uic_context(obj, mp); obj->ctx->parent = ui_global_context(); @@ -119,7 +120,7 @@ } UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) { - UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject)); + UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObjectPrivate)); newobj->ctx = ctx; newobj->widget = widget; uic_object_created(newobj); @@ -170,3 +171,22 @@ fprintf(stderr, "Error: uic_object_remove_second_last_container expected at least 2 containers\n"); } } + +// public API + +void ui_object_set(UiObject *obj, const char *key, void *data) { + UiObjectPrivate *p = (UiObjectPrivate*)obj; + if(!p->ext) { + p->ext = cxHashMapCreate(obj->ctx->mp->allocator, CX_STORE_POINTERS, 4); + } + if(data) { + cxMapPut(p->ext, key, data); + } else { + cxMapRemove(p->ext, key); + } +} + +void* ui_object_get(UiObject *obj, const char *key) { + UiObjectPrivate *p = (UiObjectPrivate*)obj; + return p->ext ? cxMapGet(p->ext, key) : NULL; +} diff -r 3da24640513a -r e57ca2747782 ui/common/object.h --- a/ui/common/object.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/object.h Sat Dec 13 15:58:58 2025 +0100 @@ -30,10 +30,16 @@ #define UIC_OBJECT_H #include "../ui/toolkit.h" +#include #ifdef __cplusplus extern "C" { #endif + +typedef struct UiObjectPrivate { + UiObject obj; + CxMap *ext; +} UiObjectPrivate; typedef void (*ui_object_callback)(UiObject *obj, void *userdata); diff -r 3da24640513a -r e57ca2747782 ui/common/objs.mk --- a/ui/common/objs.mk Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/objs.mk Sat Dec 13 15:58:58 2025 +0100 @@ -34,14 +34,16 @@ COMMON_OBJ += object$(OBJ_EXT) COMMON_OBJ += container$(OBJ_EXT) COMMON_OBJ += types$(OBJ_EXT) +COMMON_OBJ += app$(OBJ_EXT) COMMON_OBJ += properties$(OBJ_EXT) COMMON_OBJ += menu$(OBJ_EXT) COMMON_OBJ += toolbar$(OBJ_EXT) -COMMON_OBJ += ucx_properties$(OBJ_EXT) COMMON_OBJ += threadpool$(OBJ_EXT) COMMON_OBJ += condvar$(OBJ_EXT) COMMON_OBJ += args$(OBJ_EXT) COMMON_OBJ += wrapper$(OBJ_EXT) +COMMON_OBJ += utils$(OBJ_EXT) +COMMON_OBJ += message$(OBJ_EXT) TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)uic_%) TOOLKITSOURCE += $(COMMON_OBJ:%$(OBJ_EXT)=common/%.c) diff -r 3da24640513a -r e57ca2747782 ui/common/properties.c --- a/ui/common/properties.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/properties.c Sat Dec 13 15:58:58 2025 +0100 @@ -44,7 +44,7 @@ #include #include -#include "ucx_properties.h" +#include static CxMap *application_properties; static CxMap *language; @@ -54,8 +54,27 @@ static char *locales_dir; static char *pixmaps_dir; +static UiBool use_xdg_config_home = TRUE; + #endif +static UiBool load_on_startup = TRUE; +static void *properties_data = NULL; +static size_t properties_data_length = 0; + +void ui_load_properties_file_on_startup(UiBool enable) { + load_on_startup = enable; +} + +void ui_set_properties_data(const char *str, size_t len) { + if(str && len > 0) { + properties_data = malloc(len); + memcpy(properties_data, str, len); + } else { + properties_data = NULL; + properties_data_length = 0; + } +} char* ui_getappdir(void) { if(ui_appname() == NULL) { @@ -73,6 +92,8 @@ #define UI_ENV_HOME "USERPROFILE" #endif +#define UI_XDG_CONFIG_HOME_VAR "XDG_CONFIG_HOME" + char* ui_configfile(const char *name) { const char *appname = ui_appname(); if(!appname) { @@ -102,8 +123,23 @@ // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/ cxBufferPutString(&buf, "AppData\\Local\\"); #else - // app dir is $HOME/.$APPNAME/ - cxBufferPut(&buf, '.'); + if(use_xdg_config_home) { + // app dir is $HOME/.config/$APPNAME/ + char *xdghome = getenv(UI_XDG_CONFIG_HOME_VAR); + size_t xdghomelen = xdghome ? strlen(xdghome) : 0; + if(xdghome && xdghomelen > 0) { + cxBufferSeek(&buf, 0, SEEK_SET); + cxBufferPutString(&buf, xdghome); + if(xdghome[xdghomelen-1] != '/') { + cxBufferPut(&buf, '/'); + } + } else { + cxBufferPutString(&buf, ".config/"); + } + } else { + cxBufferPut(&buf, '.'); + } + #endif cxBufferPutString(&buf, appname); cxBufferPut(&buf, UI_PATH_SEPARATOR); @@ -126,10 +162,53 @@ #endif } +static int load_properties(FILE *file, CxMap *map) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + char buf[8192]; + size_t r; + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + while((r = fread(buf, 1, 8192, file)) > 0) { + cxPropertiesFilln(&prop, buf, r); + cxstring key; + cxstring value; + while((status = cxPropertiesNext(&prop, &key, &value)) == CX_PROPERTIES_NO_ERROR) { + cxMapPut(map, key, cx_strdup(value).ptr); + } + if(status > CX_PROPERTIES_OK) { + break; + } + } + return status <= CX_PROPERTIES_NO_DATA ? 0 : 1; +} + void uic_load_app_properties() { application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128); application_properties->collection.simple_destructor = free; + if(!load_on_startup) { + if(properties_data) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + cxPropertiesFilln(&prop, properties_data, properties_data_length); + + cxstring key; + cxstring value; + while((status = cxPropertiesNext(&prop, &key, &value)) == CX_PROPERTIES_NO_ERROR) { + cxMapPut(application_properties, key, cx_strdup(value).ptr); + } + + if(status > CX_PROPERTIES_NO_DATA) { + fprintf(stderr, "Error: cannot load properties: %d\n", (int)status); + } else { + cxMapRehash(application_properties); + } + } + return; + } + if(!ui_appname()) { // applications without name cannot load app properties return; @@ -139,9 +218,37 @@ if(!dir) { return; } + size_t len = strlen(dir); + if(len < 2) { + return; + } if(ui_mkdir(dir)) { + if(errno == ENOENT) { + char *parent = strdup(dir); + for(int i=len-2;i>=0;i--) { + if(parent[i] == '/') { + parent[i] = 0; + break; + } + } + // try creating the parent + int err = ui_mkdir(parent); + if(err) { + fprintf(stderr, "Error: Cannot create directory %s: %s\n", parent, strerror(errno)); + free(parent); + free(dir); + return; + } + free(parent); + + // try dir again + if(!ui_mkdir(dir)) { + errno = EEXIST; // success + } + } + if(errno != EEXIST) { - fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir); + fprintf(stderr, "Error: Cannot create directory %s: %s\n", dir, strerror(errno)); free(dir); return; } @@ -159,8 +266,8 @@ return; } - if(ucx_properties_load(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot load application properties.\n"); + if(load_properties(file, application_properties)) { + fprintf(stderr, "Error: Cannot load application properties.\n"); } fclose(file); @@ -181,11 +288,13 @@ } int ret = 0; - if(ucx_properties_store(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot store application properties.\n"); - ret = 1; + CxMapIterator i = cxMapIterator(application_properties); + cx_foreach(CxMapEntry *, entry, i) { + fprintf(file, "%.*s = %s\n", (int)entry->key->len, (char*)entry->key->data, (char*)entry->value); } + cxMapRehash(application_properties); + fclose(file); free(path); @@ -313,8 +422,8 @@ return 1; } - if(ucx_properties_load(lang, file)) { - fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path); + if(load_properties(file, lang)) { + fprintf(stderr, "Error: Cannot parse language file: %s.\n", path); } fclose(file); diff -r 3da24640513a -r e57ca2747782 ui/common/threadpool.c --- a/ui/common/threadpool.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/threadpool.c Sat Dec 13 15:58:58 2025 +0100 @@ -40,24 +40,20 @@ UiThreadpool* threadpool_new(int min, int max) { UiThreadpool *pool = malloc(sizeof(UiThreadpool)); - pool->queue = NULL; - pool->queue_len = 0; + pool->queue = ui_queue_create(); pool->num_idle = 0; pool->min_threads = min; pool->max_threads = max; - pthread_mutex_init(&pool->queue_lock, NULL); - pthread_mutex_init(&pool->avlbl_lock, NULL); - pthread_cond_init(&pool->available, NULL); - return pool; } int threadpool_start(UiThreadpool *pool) { + pool->nthreads = pool->min_threads; + pool->threads = calloc(pool->max_threads, sizeof(pthread_t)); /* create pool threads */ - for(int i=0;imin_threads;i++) { - pthread_t t; - if (pthread_create(&t, NULL, threadpool_func, pool) != 0) { + for(int i=0;inthreads;i++) { + if (pthread_create(&pool->threads[i], NULL, threadpool_func, pool) != 0) { fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno)); return 1; } @@ -65,6 +61,16 @@ return 0; } +int threadpool_join(UiThreadpool *pool) { + int err = 0; + for(int i=0;inthreads;i++) { + if(pthread_join(pool->threads[i], NULL)) { + err = 1; + } + } + return err; +} + void* threadpool_func(void *data) { UiThreadpool *pool = (UiThreadpool*)data; @@ -82,69 +88,17 @@ } threadpool_job* threadpool_get_job(UiThreadpool *pool) { - pthread_mutex_lock(&pool->queue_lock); - - threadpool_job *job = NULL; - pool->num_idle++; - while(job == NULL) { - if(pool->queue_len == 0) { - pthread_cond_wait(&pool->available, &pool->queue_lock); - continue; - } else { - pool_queue_t *q = pool->queue; - job = q->job; - pool->queue = q->next; - pool->queue_len--; - free(q); - } - } - pool->num_idle--; - - pthread_mutex_unlock(&pool->queue_lock); + threadpool_job *job = ui_queue_get_wait(pool->queue); return job; } -void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { - // TODO: handle errors - +void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { threadpool_job *job = malloc(sizeof(threadpool_job)); job->callback = func; job->data = data; - - pthread_mutex_lock(&pool->queue_lock); - threadpool_enqueue_job(pool, job); - - int create_thread = 0; - int destroy_thread = 0; - int diff = pool->queue_len - pool->num_idle; - - //if(pool->queue_len == 1) { - pthread_cond_signal(&pool->available); - //} - - pthread_mutex_unlock(&pool->queue_lock); + ui_queue_put(pool->queue, job); } -void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) { - pool_queue_t *q = malloc(sizeof(pool_queue_t)); - q->job = job; - q->next = NULL; - - if(pool->queue == NULL) { - pool->queue = q; - } else { - pool_queue_t *last_elem = pool->queue; - while(last_elem->next != NULL) { - last_elem = last_elem->next; - } - last_elem->next = q; - } - pool->queue_len++; -} - - - - UiThreadpool* ui_threadpool_create(int nthreads) { @@ -191,6 +145,71 @@ threadpool_run(pool, ui_threadpool_job_func, job); } +/* --------------------------------- Queue --------------------------------- */ + +UiQueue* ui_queue_create(void) { + UiQueue *queue = calloc(1, sizeof(UiQueue)); + pthread_mutex_init(&queue->lock, NULL); + pthread_mutex_init(&queue->avlbl_lock, NULL); + pthread_cond_init(&queue->available, NULL); + return queue; +} + +void ui_queue_free(UiQueue *queue) { + // The queue must be empty, we could free UiQueueElm, + // but not the payload data + pthread_mutex_destroy(&queue->lock); + pthread_mutex_destroy(&queue->avlbl_lock); + pthread_cond_destroy(&queue->available); + free(queue); +} + +void ui_queue_put(UiQueue *queue, void *data) { + // create queue element + UiQueueElm *elm = malloc(sizeof(UiQueueElm)); + elm->data = data; + elm->next = NULL; + + pthread_mutex_lock(&queue->lock); + + // put queue element at the end of the linked list + if(queue->elements) { + UiQueueElm *end = queue->elements; + while(end->next) { + end = end->next; + } + end->next = elm; + } else { + queue->elements = elm; + } + queue->length++; + + // signal new available data + pthread_cond_signal(&queue->available); + + pthread_mutex_unlock(&queue->lock); +} + +void* ui_queue_get_wait(UiQueue *queue) { + pthread_mutex_lock(&queue->lock); + + void *data = NULL; + while(data == NULL) { + if(queue->length == 0) { + pthread_cond_wait(&queue->available, &queue->lock); + continue; + } else { + UiQueueElm *q = queue->elements; + data = q->data; + queue->elements = q->next; + queue->length--; + free(q); + } + } + + pthread_mutex_unlock(&queue->lock); + return data; +} #endif diff -r 3da24640513a -r e57ca2747782 ui/common/threadpool.h --- a/ui/common/threadpool.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/threadpool.h Sat Dec 13 15:58:58 2025 +0100 @@ -39,6 +39,8 @@ extern "C" { #endif +typedef struct UiQueueElm UiQueueElm; +typedef struct UiQueue UiQueue; typedef struct UiJob { UiObject *obj; @@ -48,20 +50,30 @@ void *finish_data; } UiJob; +struct UiQueueElm { + void *data; + UiQueueElm *next; +}; + +struct UiQueue { + UiQueueElm *elements; + size_t length; + pthread_mutex_t lock; + pthread_mutex_t avlbl_lock; + pthread_cond_t available; +}; typedef struct _threadpool_job threadpool_job; typedef void*(*job_callback_f)(void *data); typedef struct _pool_queue pool_queue_t; struct UiThreadpool { - pthread_mutex_t queue_lock; - pthread_mutex_t avlbl_lock; - pthread_cond_t available; - pool_queue_t *queue; - uint32_t queue_len; - uint32_t num_idle; - int min_threads; - int max_threads; + UiQueue *queue; + uint32_t num_idle; + int min_threads; + int max_threads; + pthread_t *threads; + int nthreads; }; struct _threadpool_job { @@ -76,10 +88,15 @@ UiThreadpool* threadpool_new(int min, int max); int threadpool_start(UiThreadpool *pool); +int threadpool_join(UiThreadpool *pool); void* threadpool_func(void *data); threadpool_job* threadpool_get_job(UiThreadpool *pool); void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data); -void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job); + +UiQueue* ui_queue_create(void); +void ui_queue_free(UiQueue *queue); +void ui_queue_put(UiQueue *queue, void *data); +void* ui_queue_get_wait(UiQueue *queue); #ifdef __cplusplus } diff -r 3da24640513a -r e57ca2747782 ui/common/toolbar.c --- a/ui/common/toolbar.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/toolbar.c Sat Dec 13 15:58:58 2025 +0100 @@ -57,15 +57,15 @@ newargs.tooltip = nl_strdup(args->tooltip); newargs.onclick = args->onclick; newargs.onclickdata = args->onclickdata; - newargs.groups = uic_copy_groups(args->groups, ngroups); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.states = uic_copy_states(args->states, ngroups); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } void ui_toolbar_item_create(const char* name, UiToolbarItemArgs *args) { UiToolbarItem* item = malloc(sizeof(UiToolbarItem)); item->item.type = UI_TOOLBAR_ITEM; - item->args = itemargs_copy(args, &item->ngroups, &item->nvstates); + item->args = itemargs_copy(args, &item->nstates, &item->nvstates); cxMapPut(toolbar_items, name, item); } @@ -78,15 +78,15 @@ newargs.varname = nl_strdup(args->varname); newargs.onchange = args->onchange; newargs.onchangedata = args->onchangedata; - newargs.groups = uic_copy_groups(args->groups, ngroups); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.states = uic_copy_states(args->states, ngroups); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs *args) { UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem)); item->item.type = UI_TOOLBAR_TOGGLEITEM; - item->args = toggleitemargs_copy(args, &item->ngroups, &item->nvstates); + item->args = toggleitemargs_copy(args, &item->nstates, &item->nvstates); cxMapPut(toolbar_items, name, item); } @@ -95,7 +95,7 @@ newargs.label = nl_strdup(args->label); newargs.icon = nl_strdup(args->icon); newargs.tooltip = nl_strdup(args->tooltip); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } diff -r 3da24640513a -r e57ca2747782 ui/common/toolbar.h --- a/ui/common/toolbar.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/toolbar.h Sat Dec 13 15:58:58 2025 +0100 @@ -62,14 +62,14 @@ struct UiToolbarItem { UiToolbarItemI item; UiToolbarItemArgs args; - size_t ngroups; + size_t nstates; size_t nvstates; }; struct UiToolbarToggleItem { UiToolbarItemI item; UiToolbarToggleItemArgs args; - size_t ngroups; + size_t nstates; size_t nvstates; }; diff -r 3da24640513a -r e57ca2747782 ui/common/types.c --- a/ui/common/types.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/types.c Sat Dec 13 15:58:58 2025 +0100 @@ -33,13 +33,15 @@ #include #include -#include "../ui/tree.h" +#include "../ui/list.h" #include "types.h" #include "context.h" #include "../ui/image.h" static ui_list_init_func default_list_init; static void *default_list_init_userdata; +static ui_list_destroy_func default_list_destroy; +static void *default_list_destroy_userdata; UiObserver* ui_observer_new(ui_callback f, void *data) { UiObserver *observer = malloc(sizeof(UiObserver)); @@ -105,6 +107,11 @@ list->count = ui_list_count; } +void uic_ucx_list_destroy(UiContext *ctx, UiList *list, void *unused) { + cxListFree(list->data); + ui_free(ctx, list); +} + UiList* ui_list_new(UiContext *ctx, const char *name) { return ui_list_new2(ctx, name, default_list_init ? default_list_init : uic_ucx_list_init, default_list_init_userdata); } @@ -121,9 +128,13 @@ return list; } -void ui_list_free(UiList *list) { - cxListFree(list->data); - free(list); +void ui_list_free(UiContext *ctx, UiList *list) { + if(!default_list_destroy) { + uic_ucx_list_destroy(ctx, list, NULL); + } else { + default_list_destroy(ctx, list, default_list_destroy_userdata); + } + } void* ui_list_first(UiList *list) { @@ -201,6 +212,7 @@ UiModel* ui_model(UiContext *ctx, ...) { UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->ctx = ctx; va_list ap; va_start(ap, ctx); @@ -240,8 +252,18 @@ #define UI_MODEL_DEFAULT_ALLOC_SIZE 16 + +static void model_notify_observer(UiModel *model, int insert, int delete) { + UiModelChangeObserver *obs = model->observer; + while(obs) { + obs->update(model, obs->userdata, insert, delete); + obs = obs->next; + } +} + UiModel* ui_model_new(UiContext *ctx) { UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->ctx = ctx; info->alloc = UI_MODEL_DEFAULT_ALLOC_SIZE; info->types = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(UiModelType)); info->titles = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(char*)); @@ -249,16 +271,21 @@ return info; } -void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width) { +void ui_model_add_column(UiModel *model, UiModelType type, const char *title, int width) { + UiContext *ctx = model->ctx; if(model->columns >= model->alloc) { model->alloc += UI_MODEL_DEFAULT_ALLOC_SIZE; model->types = ui_realloc(ctx, model->types, model->alloc * sizeof(UiModelType)); model->titles = ui_realloc(ctx, model->titles, model->alloc * sizeof(char*)); model->columnsize = ui_realloc(ctx, model->columnsize, model->alloc * sizeof(int)); } - model->types[model->columns] = type; - model->titles[model->columns] = ui_strdup(ctx, title); - model->columnsize[model->columns] = width; + int index = model->columns; + model->types[index] = type; + model->titles[index] = ui_strdup(ctx, title); + model->columnsize[index] = width; + + model_notify_observer(model, index, -1); + model->columns++; } @@ -266,6 +293,7 @@ const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; UiModel* newmodel = cxMalloc(a, sizeof(UiModel)); + newmodel->ctx = ctx; *newmodel = *model; newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType)); @@ -281,11 +309,67 @@ return newmodel; } -void ui_model_free(UiContext *ctx, UiModel *mi) { - const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; +void ui_model_ref(UiModel *model) { + model->ref++; +} + +void ui_model_unref(UiModel *model) { + if(--model->ref == 0) { + ui_model_free(model); + } +} + +void ui_model_add_observer(UiModel *model, ui_model_update_func update, void *data) { + UiModelChangeObserver *observer = ui_malloc(model->ctx, sizeof(UiModelChangeObserver)); + observer->update = update; + observer->userdata = data; + observer->next = NULL; + + if(model->observer) { + UiModelChangeObserver *last = model->observer; + while(last->next) { + last = last->next; + } + last->next = observer; + } else { + model->observer = observer; + } +} + +void ui_model_remove_observer(UiModel *model, void *data) { + if(model->observer) { + UiModelChangeObserver *obs = model->observer; + UiModelChangeObserver *prev = NULL; + while(obs) { + if(obs->userdata == data) { + // remove + if(prev) { + prev->next = obs->next; + } else { + model->observer = obs->next; + } + // free + ui_free(model->ctx, obs); + break; + } + prev = obs; + obs = obs->next; + } + } +} + +void ui_model_free(UiModel *mi) { + UiContext *ctx = mi->ctx; + const CxAllocator* a = ctx->allocator; for(int i=0;icolumns;i++) { ui_free(ctx, mi->titles[i]); } + UiModelChangeObserver *obs = mi->observer; + while(obs) { + UiModelChangeObserver *n = obs->next; + cxFree(a, obs); + obs = n; + } cxFree(a, mi->types); cxFree(a, mi->titles); cxFree(a, mi->columnsize); @@ -724,9 +808,7 @@ } UIEXPORT void ui_listselection_free(UiListSelection selection) { - if (selection.rows) { - free(selection.rows); - } + free(selection.rows); } UIEXPORT UiStr ui_str(char *cstr) { @@ -787,6 +869,7 @@ } static int ui_set_op = 0; +static int ui_onchange_events_enabled = TRUE; void ui_setop_enable(int set) { ui_set_op = set; @@ -796,6 +879,14 @@ return ui_set_op; } +void ui_onchange_events_enable(UiBool enable) { + ui_onchange_events_enabled = enable; +} + +UiBool ui_onchange_events_is_enabled(void) { + return ui_onchange_events_enabled; +} + /* ---------------- List initializers and wrapper functions ---------------- */ void ui_global_list_initializer(ui_list_init_func func, void *userdata) { @@ -803,6 +894,11 @@ default_list_init_userdata = userdata; } +void ui_global_list_destructor(ui_list_destroy_func func, void *userdata) { + default_list_destroy = func; + default_list_destroy_userdata = userdata; +} + void ui_list_class_set_first(UiList *list, void*(*first)(UiList *list)) { list->first = first; } diff -r 3da24640513a -r e57ca2747782 ui/common/types.h --- a/ui/common/types.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/types.h Sat Dec 13 15:58:58 2025 +0100 @@ -36,6 +36,7 @@ #endif void uic_ucx_list_init(UiContext *ctx, UiList *list, void *unused); +void uic_ucx_list_destroy(UiContext *ctx, UiList *list, void *unused); void uic_int_copy(UiInteger *from, UiInteger *to); void uic_double_copy(UiDouble *from, UiDouble *to); diff -r 3da24640513a -r e57ca2747782 ui/common/utils.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/utils.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,151 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" +#include "properties.h" + +#include +#include + +#include +#include + +UiPathElm* ui_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; +} + +void ui_get_window_default_width(int *width, int *height) { + const char *width_str = ui_get_property("ui.window.width"); + const char *height_str = ui_get_property("ui.window.height"); + if(width_str && height_str) { + int w = atoi(width_str); + int h = atoi(height_str); + if(w > 0 && h > 0) { + *width = w; + *height = h; + } + } +} + +// from UCX json.c +static cxmutstr escape_string(cxstring str, bool escape_slash) { + // note: this function produces the string without enclosing quotes + // the reason is that we don't want to allocate memory just for that + CxBuffer buf = {0}; + + bool all_printable = true; + for (size_t i = 0; i < str.length; i++) { + unsigned char c = str.ptr[i]; + bool escape = c < 0x20 || c == '\\' || c == '"' + || (escape_slash && c == '/'); + + if (all_printable && escape) { + size_t capa = str.length + 32; + char *space = cxMallocDefault(capa); + if (space == NULL) return cx_mutstrn(NULL, 0); + cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); + cxBufferWrite(str.ptr, 1, i, &buf); + all_printable = false; + } + if (escape) { + cxBufferPut(&buf, '\\'); + if (c == '\"') { + cxBufferPut(&buf, '\"'); + } else if (c == '\n') { + cxBufferPut(&buf, 'n'); + } else if (c == '\t') { + cxBufferPut(&buf, 't'); + } else if (c == '\r') { + cxBufferPut(&buf, 'r'); + } else if (c == '\\') { + cxBufferPut(&buf, '\\'); + } else if (c == '/') { + cxBufferPut(&buf, '/'); + } else if (c == '\f') { + cxBufferPut(&buf, 'f'); + } else if (c == '\b') { + cxBufferPut(&buf, 'b'); + } else { + char code[6]; + snprintf(code, sizeof(code), "u%04x", (unsigned int) c); + cxBufferPutString(&buf, code); + } + } else if (!all_printable) { + cxBufferPut(&buf, c); + } + } + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); + } + cxBufferDestroy(&buf); + return ret; +} + +cxmutstr ui_escape_string(cxstring str) { + return escape_string(str, FALSE); +} diff -r 3da24640513a -r e57ca2747782 ui/common/utils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/utils.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,56 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef UIC_UTILS_H +#define UIC_UTILS_H + +#include "../ui/toolkit.h" +#include "../ui/text.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data); + +/* + * overrides width/height with values from + * ui.window.width and ui.window.height properties if available + */ +void ui_get_window_default_width(int *width, int *height); + +cxmutstr ui_escape_string(cxstring str); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_UTILS_H */ + diff -r 3da24640513a -r e57ca2747782 ui/common/wrapper.c --- a/ui/common/wrapper.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/wrapper.c Sat Dec 13 15:58:58 2025 +0100 @@ -125,6 +125,11 @@ cxListRemove(cxlist, index); } +void ui_srclist_swap(UiList *list, int i1, int i2) { + CxList *cxlist = list->data; + cxListSwap(cxlist, i1, i2); +} + void ui_srclist_clear(UiList *list) { CxList *cxlist = list->data; cxListClear(cxlist); diff -r 3da24640513a -r e57ca2747782 ui/common/wrapper.h --- a/ui/common/wrapper.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/wrapper.h Sat Dec 13 15:58:58 2025 +0100 @@ -30,7 +30,7 @@ #define UIC_WRAPPER_H #include "../ui/toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #ifdef __cplusplus extern "C" { @@ -56,6 +56,7 @@ UIEXPORT void ui_srclist_add(UiList *list, UiSubList *item); UIEXPORT void ui_srclist_insert(UiList *list, int index, UiSubList *item); UIEXPORT void ui_srclist_remove(UiList *list, int index); +UIEXPORT void ui_srclist_swap(UiList *list, int i1, int i2); UIEXPORT void ui_srclist_clear(UiList *list); UIEXPORT int ui_srclist_size(UiList *list); UIEXPORT void ui_srclist_generate_sublist_num_data(UiList *list); diff -r 3da24640513a -r e57ca2747782 ui/gtk/button.c --- a/ui/gtk/button.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/button.c Sat Dec 13 15:58:58 2025 +0100 @@ -106,7 +106,7 @@ UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) { GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->tooltip, args->onclick, args->onclickdata, 0, FALSE); ui_set_name_and_style(button, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_states(obj->ctx, button, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, button, &layout); @@ -174,9 +174,9 @@ static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) { if(gtk_toggle_button_get_active(widget)) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -308,9 +308,9 @@ args->value, args->onchange, args->onchangedata, - args->enable_group); + args->enable_state); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -351,9 +351,9 @@ static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) { if(gtk_check_button_get_active(widget)) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -370,10 +370,10 @@ args->onchange, args->onchangedata, (ui_toggled_func)ui_checkbox_enable_state, - args->enable_group); + args->enable_state); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -419,7 +419,7 @@ UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { GtkWidget *widget = gtk_switch_new(); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { @@ -541,7 +541,7 @@ GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args->label); ui_set_name_and_style(rbutton, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, rbutton, args->groups); + ui_set_widget_states(obj->ctx, rbutton, args->states); if(rgroup) { #if GTK_MAJOR_VERSION >= 4 if(rg) { @@ -895,7 +895,7 @@ } ui_set_name_and_style(button, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_states(obj->ctx, button, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, button, &layout); diff -r 3da24640513a -r e57ca2747782 ui/gtk/container.c --- a/ui/gtk/container.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/container.c Sat Dec 13 15:58:58 2025 +0100 @@ -112,6 +112,21 @@ return add; } +/* -------------------- Custom Container -------------------- */ + +void ui_custom_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiCustomContainer *custom = (UiCustomContainer*)ct; + custom->add(custom->obj, ct->widget, widget, custom->userdata); +} + +void ui_custom_container_create(UiObject *obj, UIWIDGET widget, ui_addwidget_func add_child, void *userdata) { + UiCustomContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiCustomContainer)); + container->container.add = ui_custom_container_add; + container->container.widget = widget; + container->add = add_child; + container->userdata = userdata; + uic_object_push_container(obj, (UiContainerX*)container); +} /* -------------------- Box Container -------------------- */ UiContainerX* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) { @@ -985,6 +1000,8 @@ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); hb->centerbox = box; UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box); + UI_HEADERBAR_SHOW_TITLE_WIDGET(ct->widget, TRUE); + UI_HEADERBAR_SETTINGS(ct->widget); } BOX_ADD(hb->centerbox, widget); } @@ -1140,6 +1157,7 @@ UiSplitPane* ui_create_splitpane_data(GtkWidget *pane, UiOrientation orientation, int max, int init) { UiSplitPane *ct = malloc(sizeof(UiSplitPane)); + memset(ct, 0, sizeof(UiSplitPane)); ct->current_pane = pane; ct->orientation = orientation; ct->max = max; diff -r 3da24640513a -r e57ca2747782 ui/gtk/container.h --- a/ui/gtk/container.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/container.h Sat Dec 13 15:58:58 2025 +0100 @@ -61,6 +61,13 @@ int close; }; +typedef struct UiCustomContainer { + UiContainerPrivate container; + UiObject *obj; + ui_addwidget_func add; + void *userdata; +} UiCustomContainer; + typedef struct UiBoxContainer { UiContainerPrivate container; UiSubContainerType type; diff -r 3da24640513a -r e57ca2747782 ui/gtk/entry.c --- a/ui/gtk/entry.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/entry.c Sat Dec 13 15:58:58 2025 +0100 @@ -79,7 +79,7 @@ #endif GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args->step); ui_set_name_and_style(spin, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, spin, args->groups); + ui_set_widget_states(obj->ctx, spin, args->states); if(args->width > 0) { gtk_widget_set_size_request(spin, args->width, -1); diff -r 3da24640513a -r e57ca2747782 ui/gtk/graphics.c --- a/ui/gtk/graphics.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/graphics.c Sat Dec 13 15:58:58 2025 +0100 @@ -107,14 +107,14 @@ widget, "draw", G_CALLBACK(draw_callback), - NULL); + drawingarea); #endif g_signal_connect( - widget, - "destroy", - G_CALLBACK(destroy_drawingarea), - drawingarea); + widget, + "destroy", + G_CALLBACK(destroy_drawingarea), + drawingarea); return widget; } diff -r 3da24640513a -r e57ca2747782 ui/gtk/headerbar.c --- a/ui/gtk/headerbar.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/headerbar.c Sat Dec 13 15:58:58 2025 +0100 @@ -164,7 +164,7 @@ enum UiToolbarPos pos) { GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE); - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states); WIDGET_ADD_CSS_CLASS(button, "flat"); headerbar_add(headerbar, box, button, pos); @@ -178,7 +178,7 @@ enum UiToolbarPos pos) { GtkWidget *button = gtk_toggle_button_new(); - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states); WIDGET_ADD_CSS_CLASS(button, "flat"); ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.tooltip, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); diff -r 3da24640513a -r e57ca2747782 ui/gtk/headerbar.h --- a/ui/gtk/headerbar.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/headerbar.h Sat Dec 13 15:58:58 2025 +0100 @@ -46,15 +46,20 @@ #define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w) #define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w) #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) adw_header_bar_set_show_title(ADW_HEADER_BAR(h), b) +#define UI_HEADERBAR_SETTINGS(h) adw_header_bar_set_centering_policy(ADW_HEADER_BAR(h), ADW_CENTERING_POLICY_LOOSE) #else #define UI_HEADERBAR GtkHeaderBar* #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) +#define UI_HEADERBAR_SETTINGS(h) #if GTK_MAJOR_VERSION >= 4 #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) gtk_header_bar_set_show_title(GTK_HEADER_BAR(h), b) #else #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) gtk_header_bar_set_show_title(GTK_HEADER_BAR(h), b) #endif #endif diff -r 3da24640513a -r e57ca2747782 ui/gtk/image.c --- a/ui/gtk/image.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/image.c Sat Dec 13 15:58:58 2025 +0100 @@ -30,6 +30,7 @@ #include "container.h" #include "menu.h" +#include "widget.h" #include "../common/context.h" #include "../common/object.h" @@ -67,8 +68,14 @@ GtkWidget *drawingarea = gtk_drawing_area_new(); GtkWidget *toplevel; GtkWidget *widget = drawingarea; - - gtk_widget_set_size_request(drawingarea, 100, 100); + + int width = args->width; + int height = args->height; + if(width == 0 && height == 0) { + width = 100; + height = 100; + } + ui_widget_size_request(drawingarea, width, height); #if GTK_MAJOR_VERSION < 4 GtkWidget *eventbox = gtk_event_box_new(); diff -r 3da24640513a -r e57ca2747782 ui/gtk/list.c --- a/ui/gtk/list.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/list.c Sat Dec 13 15:58:58 2025 +0100 @@ -80,6 +80,7 @@ memset(tableview, 0, sizeof(UiListView)); tableview->obj = obj; tableview->model = args->model; + tableview->multiselection = args->multiselection; tableview->onactivate = args->onactivate; tableview->onactivatedata = args->onactivatedata; tableview->onselection = args->onselection; @@ -98,6 +99,11 @@ tableview->onsave = args->onsave; tableview->onsavedata = args->onsavedata; +#if GTK_CHECK_VERSION(4, 0, 0) + tableview->coldata.listview = tableview; + tableview->coldata.column = 0; +#endif + if(args->getvalue2) { tableview->getvalue = args->getvalue2; tableview->getvaluedata = args->getvalue2data; @@ -200,7 +206,7 @@ static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { UiColData *col = userdata; UiModel *model = col->listview->model; - UiModelType type = model->types[col->model_column]; + UiModelType type = model->types[col->column]; if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); GtkWidget *image = gtk_image_new(); @@ -281,16 +287,17 @@ UiColData *col = userdata; UiList *list = col->listview->var ? col->listview->var->value : NULL; UiListView *listview = col->listview; + int datacolumn = listview->columns[col->column]; ObjWrapper *obj = gtk_list_item_get_item(item); UiModel *model = col->listview->model; - UiModelType type = model->types[col->model_column]; + UiModelType type = model->types[col->column]; // cache the GtkListItem CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); UiRowItems *row = cxMapGet(listview->bound_rows, row_key); if(row) { - if(row->items[col->model_column] == NULL) { + if(row->items[col->column] == NULL) { row->bound++; } } else { @@ -298,10 +305,10 @@ cxMapPut(listview->bound_rows, row_key, row); row->bound = 1; } - row->items[col->model_column] = item; + row->items[col->column] = item; UiBool freevalue = FALSE; - void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue); + void *data = listview->getvalue(list, obj->data, obj->i, datacolumn, listview->getvaluedata, &freevalue); GtkWidget *child = gtk_list_item_get_child(item); PangoAttrList *attributes = NULL; @@ -319,7 +326,7 @@ } } - int style_col = col->data_column; + int style_col = datacolumn; if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { style_col++; // col->data_column is the icon, we need the next col for the label } @@ -363,7 +370,7 @@ } case UI_ICON_TEXT_FREE: { - void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue); + void *data2 = listview->getvalue(list, obj->data, obj->i, datacolumn+1, listview->getvaluedata, &freevalue); if(type == UI_ICON_TEXT_FREE) { freevalue = TRUE; } @@ -387,7 +394,7 @@ if(entry) { entry->listview = col->listview; entry->row = obj->i; - entry->col = col->data_column; + entry->col = datacolumn; entry->previous_value = strdup(data); } ENTRY_SET_TEXT(child, data); @@ -405,13 +412,13 @@ } } -static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { +static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { ObjWrapper *obj = gtk_list_item_get_item(item); UiListView *listview = col->listview; CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); UiRowItems *row = cxMapGet(listview->bound_rows, row_key); if(row) { - row->items[col->model_column] = NULL; + row->items[col->column] = NULL; row->bound--; if(row->bound == 0) { cxMapRemove(listview->bound_rows, row_key); @@ -457,17 +464,16 @@ } listview->numcolumns = 1; - listview->columns = malloc(sizeof(UiColData)); - listview->columns->listview = listview; - listview->columns->data_column = 0; - listview->columns->model_column = 0; + listview->columns = malloc(sizeof(int)); + listview->columns[0] = 0; listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); + g_signal_connect(factory, "unbind", G_CALLBACK(column_factory_unbind), &listview->coldata); GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection); GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); @@ -540,7 +546,7 @@ return scroll_area; } -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { // to simplify things and share code with ui_tableview_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); @@ -554,17 +560,15 @@ } listview->numcolumns = 1; - listview->columns = malloc(sizeof(UiColData)); - listview->columns->listview = listview; - listview->columns->data_column = 0; - listview->columns->model_column = 0; + listview->columns = malloc(sizeof(int)); + listview->columns[0] = 0; listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); @@ -591,8 +595,8 @@ list->obj = listview; list->update = ui_listview_update2; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; ui_update_liststore(ls, list); } else if (args->static_elements && args->static_nelm > 0) { @@ -619,10 +623,34 @@ gtk_selection_model_select_item(model, index, TRUE); } -void ui_combobox_select(UIWIDGET dropdown, int index) { +void ui_dropdown_select(UIWIDGET dropdown, int index) { gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index); } +static void add_column(UiListView *tableview, int index) { + UiModel *model = tableview->model; + + UiColData *col = malloc(sizeof(UiColData)); + col->listview = tableview; + col->column = index; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + g_object_set_data_full(G_OBJECT(factory), "coldata", col, (GDestroyNotify)free); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[index], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_insert_column(GTK_COLUMN_VIEW(tableview->widget), index, column); + + int size = model->columnsize[index]; + if(size > 0) { + gtk_column_view_column_set_fixed_width(column, size); + } else if(size < 0) { + gtk_column_view_column_set_expand(column, TRUE); + } +} + UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { GListStore *ls = g_list_store_new(G_TYPE_OBJECT); //g_list_store_append(ls, v1); @@ -650,9 +678,13 @@ // create columns from UiModel UiModel *model = args->model; - int columns = model ? model->columns : 0; + int columns = 0; + if(model) { + columns = model->columns; + ui_model_add_observer(model, ui_listview_update_model, tableview); + } - tableview->columns = calloc(columns, sizeof(UiColData)); + tableview->columns = calloc(columns, sizeof(int)); tableview->numcolumns = columns; tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); @@ -660,30 +692,14 @@ int addi = 0; for(int i=0;icolumns[i].listview = tableview; - tableview->columns[i].model_column = i; - tableview->columns[i].data_column = i+addi; + tableview->columns[i] = i+addi; if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { // icon+text has 2 data columns addi++; } - GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - UiColData *col = &tableview->columns[i]; - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); - - GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); - gtk_column_view_column_set_resizable(column, true); - gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); - - int size = model->columnsize[i]; - if(size > 0) { - gtk_column_view_column_set_fixed_width(column, size); - } else if(size < 0) { - gtk_column_view_column_set_expand(column, TRUE); - } + add_column(tableview, i); } // bind listview to list @@ -734,6 +750,49 @@ return scroll_area; } +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index) { + UiListView *listview = userdata; + if(insert_index >= listview->numcolumns) { + listview->numcolumns = insert_index+1; + listview->columns = realloc(listview->columns, listview->numcolumns * sizeof(UiColData)); + } + + gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), NULL); + cxMapClear(listview->bound_rows); + + if(insert_index) { + int prev = 0; + if(insert_index > 0) { + prev = listview->columns[insert_index-1]; + } + listview->columns[insert_index] = prev+1; + add_column(listview, insert_index); + + if(insert_index+1 < listview->numcolumns) { + // the data index of trailing columns must be adjusted + UiModelType type = model->types[insert_index]; + int add = 1; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + add++; + } + for(int i=insert_index+1;inumcolumns;i++) { + listview->columns[i] += add; + } + } + } // TODO: delete_index + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + GtkSelectionModel *selection_model = create_selection_model(listview, ls, listview->multiselection); + gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), selection_model); + listview->selectionmodel = selection_model; + listview->liststore = ls; + + if(listview->var) { + UiList *list = listview->var->value; + ui_list_update(list); + } +} + static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { UiListSelection sel = { 0, NULL }; GtkBitset *bitset = gtk_selection_model_get_selection(model); @@ -860,7 +919,9 @@ void ui_listview_update2(UiList *list, int i) { UiListView *view = list->obj; + view->current_row = -1; if(i < 0) { + cxMapClear(view->bound_rows); ui_update_liststore(view->liststore, list); } else { void *value = list->get(list, i); @@ -873,9 +934,12 @@ CxHashKey row_key = cx_hash_key(&i, sizeof(int)); UiRowItems *row = cxMapGet(view->bound_rows, row_key); if(row) { + UiColData coldata; + coldata.listview = view; for(int c=0;cnumcolumns;c++) { if(row->items[c] != NULL) { - column_factory_bind(NULL, row->items[c], &view->columns[c]); + coldata.column = c; + column_factory_bind(NULL, row->items[c], &coldata); } } } @@ -915,7 +979,7 @@ ui_setop_enable(FALSE); } -UiListSelection ui_combobox_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection(UiList *list) { UiListView *view = list->obj; guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); UiListSelection sel = { 0, NULL }; @@ -927,7 +991,7 @@ return sel; } -void ui_combobox_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { ui_setop_enable(TRUE); UiListView *view = list->obj; if(selection.count > 0) { @@ -1156,7 +1220,7 @@ // create treeview GtkWidget *view = gtk_tree_view_new(); ui_set_name_and_style(view, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, view, args->groups); + ui_set_widget_states(obj->ctx, view, args->states); GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); @@ -1272,7 +1336,7 @@ //g_object_unref(path); } -void ui_combobox_select(UIWIDGET dropdown, int index) { +void ui_dropdown_select(UIWIDGET dropdown, int index) { gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index); } @@ -1514,16 +1578,16 @@ -/* --------------------------- ComboBox --------------------------- */ +/* --------------------------- Dropdown --------------------------- */ -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { GtkWidget *combobox = gtk_combo_box_new(); if(args->width > 0) { gtk_widget_set_size_request(combobox, args->width, -1); } ui_set_name_and_style(combobox, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, combobox, args->groups); + ui_set_widget_states(obj->ctx, combobox, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, combobox, &layout); @@ -1544,8 +1608,8 @@ if(var) { listview->var = var; list->update = ui_combobox_modelupdate; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; list->obj = listview; list->update(list, -1); } else if(args->static_nelm > 0) { @@ -1622,7 +1686,7 @@ g_object_unref(store); } -UiListSelection ui_combobox_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection(UiList *list) { UiListView *combobox = list->obj; UiListSelection ret; ret.rows = malloc(sizeof(int*)); @@ -1631,7 +1695,7 @@ return ret; } -void ui_combobox_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { ui_setop_enable(TRUE); UiListView *combobox = list->obj; if(selection.count > 0) { @@ -2045,6 +2109,10 @@ if(v->var) { ui_destroy_boundvar(v->obj->ctx, v->var); } + if(v->model) { + ui_model_remove_observer(v->model, v); + ui_model_unref(v->model); + } if(v->elements) { for(int i=0;inelm;i++) { free(v->elements[i]); @@ -2160,6 +2228,8 @@ UiList *list = uisublist.var->value; list->obj = sublist_ptr; list->update = ui_listbox_list_update; + list->getselection = ui_listbox_list_getselection; + list->setselection = ui_listbox_list_setselection; } } @@ -2181,7 +2251,7 @@ SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); ui_set_name_and_style(listbox, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, listbox, args->groups); + ui_set_widget_states(obj->ctx, listbox, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, scroll_area, &layout); @@ -2223,6 +2293,8 @@ UiList *list = var->value; list->obj = uilistbox; list->update = ui_listbox_dynamic_update; + list->getselection = ui_listbox_dynamic_getselection; + list->setselection = ui_listbox_dynamic_setselection; ui_listbox_dynamic_update(list, -1); } @@ -2294,6 +2366,32 @@ ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); } +void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel) { + UiListBox *uilistbox = list->obj; + gtk_list_box_unselect_all(uilistbox->listbox); + if(sel.count > 0) { + int index = sel.rows[0]; + if(index >= 0) { + GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, index); + if(row) { + gtk_list_box_select_row(uilistbox->listbox, row); + } + } + } +} + +UiListSelection ui_listbox_dynamic_getselection(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListBox *uilistbox = list->obj; + GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox); + if(row) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = gtk_list_box_row_get_index(row); + } + return sel; +} + void ui_listbox_update(UiListBox *listbox, int from, int to) { CxIterator i = cxListIterator(listbox->sublists); size_t pos = 0; @@ -2659,6 +2757,39 @@ ui_sourcelist_update_finished(); } +void ui_listbox_list_setselection(UiList *list, UiListSelection sel) { + UiListBoxSubList *sublist = list->obj; + UiListBox *uilistbox = sublist->listbox; + gtk_list_box_unselect_all(uilistbox->listbox); + if(sel.count > 0) { + int index = sel.rows[0]; + if(index >= 0 && index < sublist->numitems) { + int global_index = sublist->startpos + index; + GtkListBoxRow *row = gtk_list_box_get_row_at_index(uilistbox->listbox, global_index); + if(row) { + gtk_list_box_select_row(uilistbox->listbox, row); + } + } + } +} + +UiListSelection ui_listbox_list_getselection(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListBoxSubList *sublist = list->obj; + UiListBox *uilistbox = sublist->listbox; + GtkListBoxRow *row = gtk_list_box_get_selected_row(uilistbox->listbox); + if(row) { + int index = gtk_list_box_row_get_index(row); + size_t startpos = sublist->startpos; + if(index >= startpos && index < startpos+sublist->numitems) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = index - startpos; + } + } + return sel; +} + void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); if(!data) { diff -r 3da24640513a -r e57ca2747782 ui/gtk/list.h --- a/ui/gtk/list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -26,10 +26,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TREE_H -#define TREE_H +#ifndef LIST_H +#define LIST_H -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include @@ -38,6 +38,7 @@ extern "C" { #endif +typedef struct UiListView UiListView; typedef struct UiColData UiColData; #if GTK_CHECK_VERSION(4, 10, 0) @@ -47,11 +48,17 @@ } UiRowItems; #endif -typedef struct UiListView { +struct UiColData { + UiListView *listview; + int column; +}; + +struct UiListView { UiObject *obj; GtkWidget *widget; UiVar *var; UiModel *model; + UiBool multiselection; ui_getvaluefunc2 getvalue; void *getvaluedata; ui_getstylefunc getstyle; @@ -65,8 +72,9 @@ CxMap *bound_rows; GListStore *liststore; GtkSelectionModel *selectionmodel; - UiColData *columns; + int *columns; int numcolumns; + UiColData coldata; PangoAttrList *current_row_attributes; #else int style_offset; @@ -85,12 +93,6 @@ void *onsavedata; UiListSelection selection; -} UiListView; - -struct UiColData { - UiListView *listview; - int model_column; - int data_column; }; typedef struct UiTreeEventData { @@ -136,6 +138,7 @@ void ui_update_liststore(GListStore *liststore, UiList *list); void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm); +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index); void ui_listview_update2(UiList *list, int i); UiListSelection ui_listview_getselection2(UiList *list); void ui_listview_setselection2(UiList *list, UiListSelection selection); @@ -155,6 +158,7 @@ GtkWidget* ui_get_tree_widget(UIWIDGET widget); +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index); void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); void ui_listview_setselection(UiList *list, UiListSelection selection); @@ -186,13 +190,18 @@ GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata); void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); void ui_combobox_modelupdate(UiList *list, int i); -UiListSelection ui_combobox_getselection(UiList *list); -void ui_combobox_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_dropdown_getselection(UiList *list); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); void ui_listbox_dynamic_update(UiList *list, int i); +void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel); +UiListSelection ui_listbox_dynamic_getselection(UiList *list); + void ui_listbox_update(UiListBox *listbox, int from, int to); void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index); void ui_listbox_list_update(UiList *list, int i); +void ui_listbox_list_setselection(UiList *list, UiListSelection sel); +UiListSelection ui_listbox_list_getselection(UiList *list); void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data); @@ -200,5 +209,5 @@ } #endif -#endif /* TREE_H */ +#endif /* LIST_H */ diff -r 3da24640513a -r e57ca2747782 ui/gtk/menu.c --- a/ui/gtk/menu.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/menu.c Sat Dec 13 15:58:58 2025 +0100 @@ -129,11 +129,11 @@ gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget); - if(i->groups) { - CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); - cxListAddArray(groups, i->groups, i->ngroups); - uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); - cxListFree(groups); + if(i->states) { + CxList *states = cxArrayListCreateSimple(sizeof(int), i->nstates); + cxListAddArray(states, i->states, i->nstates); + uic_add_state_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, states); + cxListFree(states); } } @@ -462,10 +462,10 @@ g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); g_object_unref(action); - if(i->groups) { - CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); - cxListAddArray(groups, i->groups, i->ngroups); - uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); + if(i->states) { + CxList *groups = cxArrayListCreateSimple(sizeof(int), i->nstates); + cxListAddArray(groups, i->states, i->nstates); + uic_add_state_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); cxListFree(groups); } diff -r 3da24640513a -r e57ca2747782 ui/gtk/text.c --- a/ui/gtk/text.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/text.c Sat Dec 13 15:58:58 2025 +0100 @@ -32,6 +32,7 @@ #include "text.h" #include "container.h" +#include "widget.h" #include @@ -53,9 +54,9 @@ int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end); if(sel != textview->last_selection_state) { if(sel) { - ui_set_group(textview->ctx, UI_GROUP_SELECTION); + ui_set_state(textview->ctx, UI_GROUP_SELECTION); } else { - ui_unset_group(textview->ctx, UI_GROUP_SELECTION); + ui_unset_state(textview->ctx, UI_GROUP_SELECTION); } } textview->last_selection_state = sel; @@ -112,7 +113,7 @@ GtkWidget *text_area = gtk_text_view_new(); ui_set_name_and_style(text_area, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, text_area, args->groups); + ui_set_widget_states(obj->ctx, text_area, args->states); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); g_signal_connect( @@ -144,17 +145,7 @@ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area); - if(args->width > 0 || args->height > 0) { - int width = args->width; - int height = args->height; - if(width == 0) { - width = -1; - } - if(height == 0) { - height = -1; - } - gtk_widget_set_size_request(scroll_area, width, height); - } + ui_widget_size_request(scroll_area, args->width, args->height); // font and padding //PangoFontDescription *font; @@ -335,6 +326,10 @@ void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) { + if(!ui_onchange_events_is_enabled()) { + return; + } + UiText *value = textarea->var->value; UiEvent e; @@ -608,7 +603,7 @@ static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs *args) { GtkWidget *textfield = gtk_entry_new(); ui_set_name_and_style(textfield, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, textfield, args->groups); + ui_set_widget_states(obj->ctx, textfield, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); @@ -698,14 +693,18 @@ } void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { - UiString *value = textfield->var->value; + if(!ui_onchange_events_is_enabled()) { + return; + } + + UiString *value = textfield->var ? textfield->var->value : NULL; UiEvent e; e.obj = textfield->obj; e.window = e.obj->window; e.document = textfield->obj->ctx->document; e.eventdata = value; - e.eventdatatype = UI_EVENT_DATA_TEXT_VALUE; + e.eventdatatype = value ? UI_EVENT_DATA_TEXT_VALUE : 0; e.intval = 0; e.set = ui_get_setop(); @@ -720,12 +719,14 @@ void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) { if(textfield->onactivate) { + UiString *value = textfield->var ? textfield->var->value : NULL; + UiEvent e; e.obj = textfield->obj; e.window = e.obj->window; e.document = textfield->obj->ctx->document; - e.eventdata = NULL; - e.eventdatatype = 0; + e.eventdata = value; + e.eventdatatype = value ? UI_EVENT_DATA_TEXT_VALUE : 0; e.intval = 0; e.set = ui_get_setop(); textfield->onactivate(&e, textfield->onactivatedata); @@ -956,6 +957,10 @@ pathtf->stack = gtk_stack_new(); gtk_widget_set_name(pathtf->stack, "path-textfield-box"); + if(args->width > 0) { + gtk_widget_set_size_request(pathtf->stack, args->width, -1); + } + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, pathtf->stack, &layout); @@ -1127,6 +1132,10 @@ G_CALLBACK(ui_path_textfield_destroy), pathtf); + if(args->width > 0) { + gtk_widget_set_size_request(eventbox, args->width, -1); + } + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, eventbox, &layout); diff -r 3da24640513a -r e57ca2747782 ui/gtk/toolbar.c --- a/ui/gtk/toolbar.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/toolbar.c Sat Dec 13 15:58:58 2025 +0100 @@ -139,7 +139,7 @@ } gtk_tool_item_set_is_important(button, TRUE); - ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + ui_set_widget_nstates(obj->ctx, GTK_WIDGET(button), item->args.states, item->nstates); if(item->args.onclick) { UiEventData *event = cxMalloc( @@ -181,7 +181,7 @@ if(item->args.tooltip) { gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); } - ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + ui_set_widget_nstates(obj->ctx, GTK_WIDGET(button), item->args.states, item->nstates); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER); if(var) { @@ -394,7 +394,7 @@ if(item->args.icon) { ui_button_set_icon_name(button, item->args.icon); } - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); gtk_header_bar_pack_start(hb, button); diff -r 3da24640513a -r e57ca2747782 ui/gtk/toolkit.c --- a/ui/gtk/toolkit.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/toolkit.c Sat Dec 13 15:58:58 2025 +0100 @@ -39,6 +39,7 @@ #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/threadpool.h" +#include "../common/app.h" #include #include @@ -51,13 +52,6 @@ static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -void *open_data; -static ui_callback exit_func; -void *exit_data; - static ui_callback appclose_fnc; static void *appclose_udata; @@ -95,30 +89,13 @@ return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } #ifdef UI_APPLICATION static void app_startup(GtkApplication* app, gpointer userdata) { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); } static void app_activate(GtkApplication* app, gpointer userdata) { @@ -126,9 +103,7 @@ } static void app_shutdown(GtkApplication *app, gpointer userdata) { - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); ui_app_save_settings(); } @@ -148,13 +123,9 @@ free(appid.ptr); #else - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); gtk_main(); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); ui_app_save_settings(); #endif if(exit_on_shutdown) { @@ -164,7 +135,7 @@ #ifndef UI_GTK2 void ui_app_quit() { - g_application_quit(G_APPLICATION(app)); + g_application_quit(G_APPLICATION(app)); // TODO: fix, does not work } GtkApplication* ui_get_application() { @@ -175,7 +146,7 @@ void ui_show(UiObject *obj) { gboolean visible = gtk_widget_is_visible(obj->widget); - uic_check_group_widgets(obj->ctx); + uic_check_state_widgets(obj->ctx); #if GTK_MAJOR_VERSION >= 4 gtk_window_present(GTK_WINDOW(obj->widget)); #elif GTK_MAJOR_VERSION <= 3 @@ -263,14 +234,14 @@ #endif } -void ui_set_visible(UIWIDGET widget, int visible) { +void ui_set_visible(UIWIDGET widget, UiBool visible) { #if GTK_MAJOR_VERSION >= 4 gtk_widget_set_visible(widget, visible); #else if(visible) { - gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_show_all(widget); } else { + gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_hide(widget); } #endif @@ -346,6 +317,11 @@ #if GTK_MAJOR_VERSION == 4 static const char *ui_gtk_css = +".ui-entry-box {\n" +" background-color: alpha(currentColor, 0.1);" +" border-radius: 6px;" +" padding: 7px;" +"}\n" "#path-textfield-box {\n" " background-color: alpha(currentColor, 0.1);" " border-radius: 6px;" @@ -529,17 +505,17 @@ } } -void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) { - if(!groups) { +void ui_set_widget_states(UiContext *ctx, GtkWidget *widget, const int *states) { + if(!states) { return; } - size_t ngroups = uic_group_array_size(groups); - ui_set_widget_ngroups(ctx, widget, groups, ngroups); + size_t nstates = uic_state_array_size(states); + ui_set_widget_nstates(ctx, widget, states, nstates); } -void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) { - if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); +void ui_set_widget_nstates(UiContext *ctx, GtkWidget *widget, const int *states, size_t nstates) { + if(nstates > 0) { + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, states, nstates); ui_set_enabled(widget, FALSE); } } @@ -548,14 +524,14 @@ if(!states) { return; } - size_t nstates = uic_group_array_size(states); + size_t nstates = uic_state_array_size(states); ui_set_widget_nvisibility_states(ctx, widget, states, nstates); } void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups) { if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); ui_set_visible(widget, FALSE); } } diff -r 3da24640513a -r e57ca2747782 ui/gtk/toolkit.h --- a/ui/gtk/toolkit.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/toolkit.h Sat Dec 13 15:58:58 2025 +0100 @@ -178,8 +178,8 @@ int ui_get_scalefactor(); void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style); -void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups); -void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups); +void ui_set_widget_states(UiContext *ctx, GtkWidget *widget, const int *states); +void ui_set_widget_nstates(UiContext *ctx, GtkWidget *widget, const int *states, size_t nstates); void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states); void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups); diff -r 3da24640513a -r e57ca2747782 ui/gtk/webview.c --- a/ui/gtk/webview.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/webview.c Sat Dec 13 15:58:58 2025 +0100 @@ -30,6 +30,7 @@ #include "container.h" #include "webview.h" +#include "widget.h" #ifdef UI_WEBVIEW @@ -38,6 +39,8 @@ ui_set_name_and_style(webview, args->name, args->style_class); + ui_widget_size_request(webview, args->width, args->height); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); if(var) { WebViewData *data = malloc(sizeof(WebViewData)); @@ -57,7 +60,7 @@ } } - ui_set_widget_groups(obj->ctx, webview, args->groups); + ui_set_widget_states(obj->ctx, webview, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, webview, &layout); diff -r 3da24640513a -r e57ca2747782 ui/gtk/widget.c --- a/ui/gtk/widget.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/widget.c Sat Dec 13 15:58:58 2025 +0100 @@ -31,7 +31,20 @@ #include "../common/object.h" -UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { +void ui_widget_size_request(UIWIDGET w, int width, int height) { + if(width > 0 || height > 0) { + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(w, width, height); + } +} + + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { UIWIDGET widget = create_widget(obj, args, userdata); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; diff -r 3da24640513a -r e57ca2747782 ui/gtk/widget.h --- a/ui/gtk/widget.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/widget.h Sat Dec 13 15:58:58 2025 +0100 @@ -35,7 +35,12 @@ extern "C" { #endif - +/* + * Sets a widget width/height. + * + * If wdith or height is 0, the dimension is not changed + */ +void ui_widget_size_request(UIWIDGET w, int width, int height); #ifdef __cplusplus diff -r 3da24640513a -r e57ca2747782 ui/gtk/window.c --- a/ui/gtk/window.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/gtk/window.c Sat Dec 13 15:58:58 2025 +0100 @@ -35,6 +35,7 @@ #include "../common/context.h" #include "../common/menu.h" #include "../common/toolbar.h" +#include "../common/utils.h" #include @@ -141,7 +142,7 @@ ui_set_property("ui.window.splitview.pos", buf); } -static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool splitview, UiBool simple) { +static UiObject* create_window(const char *title, UiBool sidebar, UiBool splitview, UiBool simple) { UiObject *obj = uic_object_new_toplevel(); #ifdef UI_LIBADWAITA @@ -152,8 +153,6 @@ obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); #endif - obj->window = window_data; - #if GTK_CHECK_VERSION(4, 0, 0) obj->ctx->action_map = G_ACTION_MAP(obj->widget); #endif @@ -169,16 +168,7 @@ int window_width = window_default_width; int window_height = window_default_height; if(!simple) { - const char *width = ui_get_property("ui.window.width"); - const char *height = ui_get_property("ui.window.height"); - if(width && height) { - int w = atoi(width); - int h = atoi(height); - if(w > 0 && h > 0) { - window_width = w; - window_height = h; - } - } + ui_get_window_default_width(&window_width, &window_height); } gtk_window_set_default_size( GTK_WINDOW(obj->widget), @@ -205,6 +195,21 @@ obj); #endif + int splitview_pos = 0; + if(splitview) { + const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); + splitview_pos= splitview_window_default_pos; + if(splitview_pos < 0) { + splitview_pos = window_width / 2; + } + if(splitview_pos_str && splitview_window_use_prop) { + int sv_pos = atoi(splitview_pos_str); + if(sv_pos > 0) { + splitview_pos = sv_pos; + } + } + } + GtkWidget *vbox = ui_gtk_vbox_new(0); #ifdef UI_LIBADWAITA GtkWidget *toolbar_view = adw_toolbar_view_new(); @@ -223,18 +228,7 @@ G_CALLBACK(save_window_splitview_pos), NULL); - const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); - int pos = splitview_window_default_pos; - if(pos < 0) { - pos = window_width / 2; - } - if(splitview_pos_str && splitview_window_use_prop) { - int splitview_pos = atoi(splitview_pos_str); - if(splitview_pos > 0) { - pos = splitview_pos; - } - } - gtk_paned_set_position(GTK_PANED(content), pos); + gtk_paned_set_position(GTK_PANED(content), splitview_pos); GtkWidget *right_panel = adw_toolbar_view_new(); GtkWidget *right_vbox = ui_gtk_vbox_new(0); @@ -345,14 +339,32 @@ GtkWidget *content_box = ui_gtk_vbox_new(0); WINDOW_SET_CONTENT(obj->widget, vbox); - if(sidebar) { + if(sidebar || splitview) { GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); - GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); - gtk_paned_add1(GTK_PANED(paned), sidebar_vbox); - gtk_paned_add2(GTK_PANED(paned), content_box); + if(sidebar) { + GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); + gtk_paned_add1(GTK_PANED(paned), sidebar_vbox); + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); + gtk_paned_set_position(GTK_PANED(paned), 200); + } + + if(splitview) { + GtkWidget *content_paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + gtk_paned_set_position(GTK_PANED(content_paned), splitview_pos); + gtk_paned_add2(GTK_PANED(paned), content_paned); + + GtkWidget *right_content_box = ui_gtk_vbox_new(0); + gtk_paned_add1(GTK_PANED(content_paned), content_box); + gtk_paned_add2(GTK_PANED(content_paned), right_content_box); + + g_object_set_data(G_OBJECT(obj->widget), "ui_window_splitview", content_paned); + g_object_set_data(G_OBJECT(obj->widget), "ui_left_panel", content_box); + g_object_set_data(G_OBJECT(obj->widget), "ui_right_panel", right_content_box); + } else { + gtk_paned_add2(GTK_PANED(paned), content_box); + } + BOX_ADD_EXPAND(GTK_BOX(vbox), paned); - g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); - gtk_paned_set_position (GTK_PANED(paned), 200); } else { BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); } @@ -380,20 +392,20 @@ } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE, FALSE); +UiObject* ui_window(const char *title) { + return create_window(title, FALSE, FALSE, FALSE); } -UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE, FALSE, FALSE); +UiObject *ui_sidebar_window(const char *title) { + return create_window(title, TRUE, FALSE, FALSE); } -UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar) { - return create_window(title, NULL, sidebar, TRUE, FALSE); +UiObject *ui_splitview_window(const char *title, UiBool sidebar) { + return create_window(title, sidebar, TRUE, FALSE); } -UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE, TRUE); +UiObject* ui_simple_window(const char *title) { + return create_window(title, FALSE, FALSE, TRUE); } void ui_window_size(UiObject *obj, int width, int height) { diff -r 3da24640513a -r e57ca2747782 ui/motif/Fsb.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/Fsb.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,2642 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +//define FSB_ENABLE_DETAIL + +#include "Fsb.h" +#include "FsbP.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "pathbar.h" + +#include "../common/utils.h" + +#ifdef FSB_ENABLE_DETAIL +#include +#endif + +#define WIDGET_SPACING 5 +#define WINDOW_SPACING 8 + +#define BUTTON_EXTRA_SPACE 4 + +#define DATE_FORMAT_SAME_YEAR "%b %d %H:%M" +#define DATE_FORMAT_OTHER_YEAR "%b %d %Y" + +#define KB_SUFFIX "KiB" +#define MB_SUFFIX "MiB" +#define GB_SUFFIX "GiB" +#define TB_SUFFIX "TiB" + +#define FSB_ERROR_TITLE "Error" +#define FSB_ERROR_CHAR "Character '/' is not allowed in file names" +#define FSB_ERROR_RENAME "Cannot rename file: %s" +#define FSB_ERROR_DELETE "Cannot delete file: %s" +#define FSB_ERROR_CREATE_FOLDER "Cannot create folder: %s" +#define FSB_ERROR_OPEN_DIR "Cannot open directory: %s" + +#define FSB_DETAIL_HEADINGS "Name|Size|Last Modified" + +static void fsb_class_init(void); +static void fsb_class_part_init (WidgetClass wc); +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args); +static void fsb_resize(Widget widget); +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes); +static void fsb_destroy(Widget widget); +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); +static Boolean fsb_acceptfocus(Widget widget, Time *time); + +static void fsb_insert_child(Widget child); + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb); + +static int FSBGlobFilter(const char *a, const char *b); + +static void FSBUpdateTitle(Widget w); + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg); + +static void FSBRename(XnFileSelectionBox fsb, const char *path); +static void FSBDelete(XnFileSelectionBox fsb, const char *path); + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item); + +static char* set_selected_path(XnFileSelectionBox data, XmString item); + +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd); + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback); + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListSelect(Widget fsb, Widget view, const char *item); +static void FileListCleanup(Widget fsb, Widget view, void *userData); +static void FileListDestroy(Widget fsb, Widget view, void *userData); + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count); + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListDetailSelect(Widget fsb, Widget view, const char *item); +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData); +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData); +static void FileListDetailAdjustColWidth(Widget grid); +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth); +#endif + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value); + +static void CreateUI(XnFileSelectionBox w); +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc createProc, void *userData, Boolean useDirList); +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex); +static void SelectView(XnFileSelectionBox f, int view); + +static char* FSBDialogTitle(Widget w); + +static FSBViewWidgets CreateListView(Widget fsb, ArgList args, int n, void *userData); +static FSBViewWidgets CreateDetailView(Widget fsb, ArgList args, int n, void *userData); + +static const char* GetHomeDir(void); + +static char* ConcatPath(const char *parent, const char *name); +static char* FileName(char *path); +static char* ParentPath(const char *path); +//static int CheckFileName(const char *name); + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path); +static void filedialog_cleanup_filedata(XnFileSelectionBox data); + + +static XtResource resources[] = { + {XmNokCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.okCallback), XmRCallback, NULL}, + {XmNcancelCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.cancelCallback), XmRCallback, NULL}, + {XnNwidgetSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.widgetSpacing), XmRImmediate, (XtPointer)WIDGET_SPACING}, + {XnNwindowSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.windowSpacing), XmRImmediate, (XtPointer)WINDOW_SPACING}, + {XnNfsbType, XnCfsbType, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.type), XmRImmediate, (XtPointer)FILEDIALOG_OPEN}, + {XnNshowHidden, XnCshowHidden, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHidden), XmRImmediate, (XtPointer)False}, + {XnNshowHiddenButton, XnCshowHiddenButton, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHiddenButton), XmRImmediate, (XtPointer)True}, + {XnNshowViewMenu, XnCshowViewMenu, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showViewMenu), XmRImmediate, (XtPointer)False}, + {XnNselectedView, XnCselectedView, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.selectedview), XmRImmediate, (XtPointer)0}, + + {XnNdirectory, XnCdirectory, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.currentPath), XmRString, NULL}, + {XnNselectedPath, XnCselectedPath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.selectedPath), XmRString, NULL}, + {XnNhomePath, XnChomePath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.homePath), XmRString, NULL}, + + {XnNfilter,XnCfilter,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.filterStr), XmRString, "*"}, + {XnNfilterFunc,XnCfilterFunc,XmRFunction,sizeof(FSBFilterFunc),XtOffset(XnFileSelectionBox, fsb.filterFunc), XmRFunction, NULL}, + + {XnNlabelListView,XnClabelListView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelListView), XmRString, "List"}, + {XnNlabelDetailView,XnClabelDetailView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDetailView), XmRString, "Detail"}, + {XnNlabelOpenFileTitle,XnClabelOpenFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpenFileTitle), XmRString, "Open File"}, + {XnNlabelSaveFileTitle,XnClabelSaveFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSaveFileTitle), XmRString, "Save File"}, + {XnNlabelDirUp,XnClabelDirUp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirUp), XmRString, "Dir Up"}, + {XnNlabelHome,XnClabelHome,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHome), XmRString, "Home"}, + {XnNlabelNewFolder,XnClabelNewFolder,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFolder), XmRString, "New Folder"}, + {XnNlabelFilterButton,XnClabelFilterButton,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFilterButton), XmRString, "Filter"}, + {XnNlabelShowHiddenFiles,XnClabelShowHiddenFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelShowHiddenFiles), XmRString, "Show hiden files"}, + {XnNlabelDirectories,XnClabelDirectories,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectories), XmRString, "Directories"}, + {XnNlabelFiles,XnClabelFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFiles), XmRString, "Files"}, + {XnNlabelRename,XnClabelRename,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelRename), XmRString, "Rename"}, + {XnNlabelDelete,XnClabelDelete,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDelete), XmRString, "Delete"}, + {XnNlabelOpen,XnClabelOpen,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpen), XmRString, "Open"}, + {XnNlabelSave,XnClabelSave,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSave), XmRString, "Save"}, + {XnNlabelOk,XnClabelOk,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOk), XmRString, "OK"}, + {XnNlabelCancel,XnClabelCancel,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelCancel), XmRString, "Cancel"}, + {XnNlabelHelp,XnClabelHelp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHelp), XmRString, "Help"}, + {XnNlabelFileName,XnClabelFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFileName), XmRString, "New File Name"}, + {XnNlabelDirectoryName,XnClabelDirectoryName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectoryName), XmRString, "Directory name:"}, + {XnNlabelNewFileName,XnClabelNewFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFileName), XmRString, "New file name:"}, + {XnNlabelDeleteFile,XnClabelDeleteFile,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDeleteFile), XmRString, "Delete file '%s'?"}, + {XnNdetailHeadings,XnCdetailHeadings,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.detailHeadings), XmRString,FSB_DETAIL_HEADINGS}, + {XnNdateFormatSameYear,XnCdateFormatSameYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatSameYear), XmRString,DATE_FORMAT_SAME_YEAR}, + {XnNdateFormatOtherYear,XnNdateFormatOtherYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatOtherYear), XmRString,DATE_FORMAT_OTHER_YEAR}, + {XnNsuffixBytes,XnCsuffixBytes,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixBytes), XmRString,"bytes"}, + {XnNsuffixKB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixKB), XmRString,KB_SUFFIX}, + {XnNsuffixMB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixMB), XmRString,MB_SUFFIX}, + {XnNsuffixGB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixGB), XmRString,GB_SUFFIX}, + {XnNsuffixTB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixTB), XmRString,TB_SUFFIX}, + + {XnNerrorTitle,XnCerrorTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorTitle), XmRString,FSB_ERROR_TITLE}, + {XnNerrorIllegalChar,XnCerrorIllegalChar,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorIllegalChar), XmRString,FSB_ERROR_CHAR}, + {XnNerrorRename,XnCerrorRename,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorRename), XmRString,FSB_ERROR_RENAME}, + {XnNerrorCreateFolder,XnCerrorCreateFolder,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorFolder), XmRString,FSB_ERROR_CREATE_FOLDER}, + {XnNerrorDelete,XnCerrorDelete,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorDelete), XmRString,FSB_ERROR_DELETE}, + {XnNerrorOpenDir,XnCerrorOpenDir,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorOpenDir), XmRString,FSB_ERROR_OPEN_DIR} +}; + +static XtActionsRec actionslist[] = { + {"focusIn", FocusInAP}, + {"NULL", NULL} +}; + + +static char defaultTranslations[] = ": focusIn()"; + +static XtResource constraints[] = {}; + +FSBClassRec fsbWidgetClassRec = { + // Core Class + { + (WidgetClass)&xmFormClassRec, + "XnFSB", // class_name + sizeof(FSBRec), // widget_size + fsb_class_init, // class_initialize + fsb_class_part_init, // class_part_initialize + FALSE, // class_inited + fsb_init, // initialize + NULL, // initialize_hook + fsb_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + resources, // resources + XtNumber(resources), // num_resources + NULLQUARK, // xrm_class + True, // compress_motion + True, // compress_exposure + True, // compress_enterleave + False, // visible_interest + fsb_destroy, // destroy + fsb_resize, // resize + XtInheritExpose, // expose + fsb_set_values, // set_values + NULL, // set_values_hook + XtInheritSetValuesAlmost, // set_values_almost + NULL, // get_values_hook + fsb_acceptfocus, // accept_focus + XtVersion, // version + NULL, // callback_offsets + defaultTranslations, // tm_table + XtInheritQueryGeometry, // query_geometry + XtInheritDisplayAccelerator, // display_accelerator + NULL, // extension + }, + // Composite Class + { + XtInheritGeometryManager, // geometry_manager + XtInheritChangeManaged, // change_managed + fsb_insert_child, // insert_child + XtInheritDeleteChild, // delete_child + NULL, // extension + }, + // Constraint Class + { + constraints, // resources + XtNumber(constraints), // num_resources + sizeof(XmFormConstraintRec), // constraint_size + NULL, // initialize + NULL, // destroy + NULL, // set_value + NULL, // extension + }, + // XmManager Class + { + XtInheritTranslations, // translations + NULL, // syn_resources + 0, // num_syn_resources + NULL, // syn_constraint_resources + 0, // num_syn_constraint_resources + XmInheritParentProcess, // parent_process + NULL // extension + }, + // XmBulletinBoard + { + FALSE, + NULL, + XmInheritFocusMovedProc, + NULL + }, + // XmForm Class + { + NULL + }, + // FSB Class + { + 0 + } +}; + +WidgetClass xnFsbWidgetClass = (WidgetClass)&fsbWidgetClassRec; + + +static void fsb_class_init(void) { + +} + +static void fsb_class_part_init (WidgetClass wc) { + FSBClassRec *fsbClass = (FSBClassRec*)wc; + XmFormClassRec *formClass = (XmFormClassRec*)xmFormWidgetClass; + + fsbClass->constraint_class.initialize = formClass->constraint_class.initialize; + fsbClass->constraint_class.set_values = formClass->constraint_class.set_values; +} + + +#define STRDUP_RES(a) if(a) a = strdup(a) +#define XMS_STRDUP_RES(a) if(a) a = XmStringCopy(a) + +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) { + XnFileSelectionBox fsb = (XnFileSelectionBox)neww; + (xmFormClassRec.core_class.initialize)(request, neww, args, num_args); + + fsb->fsb.disable_set_values = 0; + + STRDUP_RES(fsb->fsb.homePath); + STRDUP_RES(fsb->fsb.selectedPath); + STRDUP_RES(fsb->fsb.currentPath); + STRDUP_RES(fsb->fsb.filterStr); + STRDUP_RES(fsb->fsb.labelListView); + STRDUP_RES(fsb->fsb.labelDetailView); + STRDUP_RES(fsb->fsb.labelOpenFileTitle); + STRDUP_RES(fsb->fsb.labelSaveFileTitle); + XMS_STRDUP_RES(fsb->fsb.labelDirUp); + XMS_STRDUP_RES(fsb->fsb.labelHome); + XMS_STRDUP_RES(fsb->fsb.labelNewFolder); + XMS_STRDUP_RES(fsb->fsb.labelFilterButton); + XMS_STRDUP_RES(fsb->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(fsb->fsb.labelDirectories); + XMS_STRDUP_RES(fsb->fsb.labelFiles); + XMS_STRDUP_RES(fsb->fsb.labelRename); + XMS_STRDUP_RES(fsb->fsb.labelDelete); + XMS_STRDUP_RES(fsb->fsb.labelOpen); + XMS_STRDUP_RES(fsb->fsb.labelSave); + XMS_STRDUP_RES(fsb->fsb.labelCancel); + XMS_STRDUP_RES(fsb->fsb.labelHelp); + XMS_STRDUP_RES(fsb->fsb.labelFileName); + XMS_STRDUP_RES(fsb->fsb.labelDirectoryName); + XMS_STRDUP_RES(fsb->fsb.labelNewFileName); + STRDUP_RES(fsb->fsb.labelDeleteFile); + STRDUP_RES(fsb->fsb.detailHeadings); + STRDUP_RES(fsb->fsb.dateFormatSameYear); + STRDUP_RES(fsb->fsb.dateFormatOtherYear); + STRDUP_RES(fsb->fsb.suffixBytes); + STRDUP_RES(fsb->fsb.suffixKB); + STRDUP_RES(fsb->fsb.suffixMB); + STRDUP_RES(fsb->fsb.suffixGB); + STRDUP_RES(fsb->fsb.suffixTB); + STRDUP_RES(fsb->fsb.errorTitle); + STRDUP_RES(fsb->fsb.errorIllegalChar); + STRDUP_RES(fsb->fsb.errorRename); + STRDUP_RES(fsb->fsb.errorFolder); + STRDUP_RES(fsb->fsb.errorDelete); + STRDUP_RES(fsb->fsb.errorOpenDir); + + CreateUI((XnFileSelectionBox)fsb); + + XtAddCallback(neww, XmNmapCallback, fsb_mapcb, NULL); +} + +#define STR_FREE(a) if(a) free(a) +#define XMSTR_FREE(a) if(a) XmStringFree(a) + +static void fsb_destroy(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + + // destroy all views + for(int i=0;ifsb.numviews;i++) { + FSBView v = w->fsb.view[i]; + v.destroy(widget, v.widget, v.userData); + } + + STR_FREE(w->fsb.homePath); + + // free filelists + filedialog_cleanup_filedata(w); + STR_FREE(w->fsb.currentPath); + STR_FREE(w->fsb.selectedPath); + STR_FREE(w->fsb.filterStr); + + PathBarDestroy(w->fsb.pathBar); + + // free strings + STR_FREE(w->fsb.labelListView); + STR_FREE(w->fsb.labelDetailView); + STR_FREE(w->fsb.labelOpenFileTitle); + STR_FREE(w->fsb.labelSaveFileTitle); + + XMSTR_FREE(w->fsb.labelDirUp); + XMSTR_FREE(w->fsb.labelHome); + XMSTR_FREE(w->fsb.labelNewFolder); + XMSTR_FREE(w->fsb.labelFilterButton); + XMSTR_FREE(w->fsb.labelShowHiddenFiles); + XMSTR_FREE(w->fsb.labelDirectories); + XMSTR_FREE(w->fsb.labelFiles); + XMSTR_FREE(w->fsb.labelRename); + XMSTR_FREE(w->fsb.labelDelete); + XMSTR_FREE(w->fsb.labelOpen); + XMSTR_FREE(w->fsb.labelSave); + XMSTR_FREE(w->fsb.labelCancel); + XMSTR_FREE(w->fsb.labelHelp); + XMSTR_FREE(w->fsb.labelFileName); + XMSTR_FREE(w->fsb.labelDirectoryName); + XMSTR_FREE(w->fsb.labelNewFileName); + STR_FREE(w->fsb.labelDeleteFile); + STR_FREE(w->fsb.detailHeadings); + + STR_FREE(w->fsb.dateFormatSameYear); + STR_FREE(w->fsb.dateFormatOtherYear); + STR_FREE(w->fsb.suffixBytes); + STR_FREE(w->fsb.suffixKB); + STR_FREE(w->fsb.suffixMB); + STR_FREE(w->fsb.suffixGB); + STR_FREE(w->fsb.suffixTB); + + STR_FREE(w->fsb.errorTitle); + STR_FREE(w->fsb.errorIllegalChar); + STR_FREE(w->fsb.errorRename); + STR_FREE(w->fsb.errorFolder); + STR_FREE(w->fsb.errorDelete); + STR_FREE(w->fsb.errorOpenDir); +} + +static void fsb_resize(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.resize)(widget); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.realize)(widget, mask, attributes); + + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void FSBUpdateTitle(Widget w) { + if(XtParent(w)->core.widget_class == xmDialogShellWidgetClass) { + char *title = FSBDialogTitle(w); + XtVaSetValues(XtParent(w), XmNtitle, title, NULL); + } +} + +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + Boolean r = False; + + XnFileSelectionBox o = (XnFileSelectionBox)old; + XnFileSelectionBox n = (XnFileSelectionBox)neww; + + int setOkBtnLabel = 0; + int ismanaged = XtIsManaged(neww); + Dimension width, height; + if(!ismanaged) { + width = n->core.width; + height = n->core.height; + if(n->fsb.pathBar) { + n->fsb.pathBar->disableResize = True; + } + } + + if(o->fsb.selectedview != n->fsb.selectedview) { + int selectedview = n->fsb.selectedview; + n->fsb.selectedview = o->fsb.selectedview; + SelectView(n, selectedview); + } + + char *updateDir = NULL; + int selectItem = 0; + if(o->fsb.selectedPath != n->fsb.selectedPath) { + STR_FREE(o->fsb.selectedPath); + STRDUP_RES(n->fsb.selectedPath); + XmTextFieldSetString(n->fsb.name, FileName(n->fsb.selectedPath)); + // also update current directory + updateDir = ParentPath(n->fsb.selectedPath); + selectItem = 1; + } + if(o->fsb.currentPath != n->fsb.currentPath) { + STR_FREE(o->fsb.currentPath); + updateDir = strdup(n->fsb.currentPath); + n->fsb.currentPath = NULL; + } + + if(o->fsb.filterStr != n->fsb.filterStr) { + STR_FREE(o->fsb.filterStr); + STRDUP_RES(n->fsb.filterStr); + XmTextFieldSetString(XmDropDownGetText(n->fsb.filter), n->fsb.filterStr); + if(!updateDir) { + filedialog_update_dir(n, NULL); + } + } + + if(updateDir) { + filedialog_update_dir(n, updateDir); + PathBarSetPath(n->fsb.pathBar, updateDir); + free(updateDir); + } + + if(o->fsb.type != n->fsb.type) { + if(n->fsb.type == FILEDIALOG_OPEN) { + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.separator, NULL); + XtUnmanageChild(n->fsb.name); + XtUnmanageChild(n->fsb.nameLabel); + } else { + XtManageChild(n->fsb.name); + XtManageChild(n->fsb.nameLabel); + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.nameLabel, NULL); + } + FSBUpdateTitle(neww); + setOkBtnLabel = 1; + } + + // label strings + int updateTitle = 0; + if(o->fsb.labelListView != n->fsb.labelListView) { + STR_FREE(o->fsb.labelListView); + STRDUP_RES(n->fsb.labelListView); + XmString label = XmStringCreateLocalized(n->fsb.labelListView); + XtVaSetValues(n->fsb.viewSelectorList, XmNlabelString, label, NULL); + XmStringFree(label); + } + if(o->fsb.labelDetailView != n->fsb.labelDetailView) { + STR_FREE(o->fsb.labelDetailView); + STRDUP_RES(n->fsb.labelDetailView); + XmString label = XmStringCreateLocalized(n->fsb.labelDetailView); + XtVaSetValues(n->fsb.viewSelectorDetail, XmNlabelString, label, NULL); + if(n->fsb.detailToggleButton) { + XtVaSetValues(n->fsb.detailToggleButton, XmNlabelString, label, NULL); + } + XmStringFree(label); + } + if(o->fsb.labelOpenFileTitle != n->fsb.labelOpenFileTitle) { + STR_FREE(o->fsb.labelOpenFileTitle); + STRDUP_RES(n->fsb.labelOpenFileTitle); + updateTitle = 1; + } + if(o->fsb.labelSaveFileTitle != n->fsb.labelSaveFileTitle) { + STR_FREE(o->fsb.labelSaveFileTitle); + STRDUP_RES(n->fsb.labelSaveFileTitle); + updateTitle = 1; + } + + if(o->fsb.labelDirUp != n->fsb.labelDirUp) { + XMSTR_FREE(o->fsb.labelDirUp); + XMS_STRDUP_RES(n->fsb.labelDirUp); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelDirUp, NULL); + } + if(o->fsb.labelHome != n->fsb.labelHome) { + XMSTR_FREE(o->fsb.labelHome); + XMS_STRDUP_RES(n->fsb.labelHome); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelHome, NULL); + } + if(o->fsb.labelNewFolder != n->fsb.labelNewFolder) { + XMSTR_FREE(o->fsb.labelNewFolder); + XMS_STRDUP_RES(n->fsb.labelNewFolder); + XtVaSetValues(n->fsb.newFolder, XmNlabelString, n->fsb.labelNewFolder, NULL); + } + if(o->fsb.labelFilterButton != n->fsb.labelFilterButton) { + XMSTR_FREE(o->fsb.labelFilterButton); + XMS_STRDUP_RES(n->fsb.labelFilterButton); + XtVaSetValues(n->fsb.filterButton, XmNlabelString, n->fsb.labelFilterButton, NULL); + } + if(o->fsb.labelShowHiddenFiles != n->fsb.labelShowHiddenFiles) { + XMSTR_FREE(o->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(n->fsb.labelShowHiddenFiles); + XtVaSetValues(n->fsb.showHiddenButtonW, XmNlabelString, n->fsb.labelShowHiddenFiles, NULL); + } + if(o->fsb.labelDirectories != n->fsb.labelDirectories) { + XMSTR_FREE(o->fsb.labelDirectories); + XMS_STRDUP_RES(n->fsb.labelDirectories); + XtVaSetValues(n->fsb.lsDirLabel, XmNlabelString, n->fsb.labelDirectories, NULL); + } + if(o->fsb.labelFiles != n->fsb.labelFiles) { + XMSTR_FREE(o->fsb.labelFiles); + XMS_STRDUP_RES(n->fsb.labelFiles); + XtVaSetValues(n->fsb.lsFileLabel, XmNlabelString, n->fsb.labelFiles, NULL); + } + int recreateContextMenu = 0; + if(o->fsb.labelRename != n->fsb.labelRename) { + XMSTR_FREE(o->fsb.labelRename); + XMS_STRDUP_RES(n->fsb.labelRename); + recreateContextMenu = 1; + } + if(o->fsb.labelDelete != n->fsb.labelDelete) { + XMSTR_FREE(o->fsb.labelDelete); + XMS_STRDUP_RES(n->fsb.labelDelete); + recreateContextMenu = 1; + } + + if(o->fsb.labelOpen != n->fsb.labelOpen) { + XMSTR_FREE(o->fsb.labelOpen); + XMS_STRDUP_RES(n->fsb.labelOpen); + setOkBtnLabel = 1; + } + if(o->fsb.labelSave != n->fsb.labelSave) { + XMSTR_FREE(o->fsb.labelSave); + XMS_STRDUP_RES(n->fsb.labelSave); + setOkBtnLabel = 1; + } + if(o->fsb.labelCancel != n->fsb.labelCancel) { + XMSTR_FREE(o->fsb.labelCancel); + XMS_STRDUP_RES(n->fsb.labelCancel); + XtVaSetValues(n->fsb.cancelBtn, XmNlabelString, n->fsb.labelCancel, NULL); + } + if(o->fsb.labelHelp != n->fsb.labelHelp) { + XMSTR_FREE(o->fsb.labelHelp); + XMS_STRDUP_RES(n->fsb.labelHelp); + XtVaSetValues(n->fsb.helpBtn, XmNlabelString, n->fsb.labelHelp, NULL); + } + if(o->fsb.labelFileName != n->fsb.labelFileName) { + XMSTR_FREE(o->fsb.labelFileName); + XMS_STRDUP_RES(n->fsb.labelFileName); + XtVaSetValues(n->fsb.nameLabel, XmNlabelString, n->fsb.labelFileName, NULL); + } + if(o->fsb.labelDirectoryName != n->fsb.labelDirectoryName) { + XMSTR_FREE(o->fsb.labelDirectoryName); + XMS_STRDUP_RES(n->fsb.labelDirectoryName); + } + if(o->fsb.labelNewFileName != n->fsb.labelNewFileName) { + XMSTR_FREE(o->fsb.labelNewFileName); + XMS_STRDUP_RES(n->fsb.labelNewFileName); + } + + if(o->fsb.labelDeleteFile != n->fsb.labelDeleteFile) { + STR_FREE(o->fsb.labelDeleteFile); + STRDUP_RES(n->fsb.labelDeleteFile); + } +#ifdef FSB_ENABLE_DETAIL + if(o->fsb.detailHeadings != n->fsb.detailHeadings) { + STR_FREE(o->fsb.detailHeadings); + STRDUP_RES(n->fsb.detailHeadings); + XtVaSetValues(n->fsb.grid, XmNsimpleHeadings, n->fsb.detailHeadings, NULL); + } +#endif + if(o->fsb.dateFormatSameYear != n->fsb.dateFormatSameYear) { + STR_FREE(o->fsb.dateFormatSameYear); + STRDUP_RES(n->fsb.dateFormatSameYear); + } + if(o->fsb.dateFormatOtherYear != n->fsb.dateFormatOtherYear) { + STR_FREE(o->fsb.dateFormatOtherYear); + STRDUP_RES(n->fsb.dateFormatOtherYear); + } + if(o->fsb.suffixBytes != n->fsb.suffixBytes) { + STR_FREE(o->fsb.suffixBytes); + STRDUP_RES(n->fsb.suffixBytes); + } + if(o->fsb.suffixMB != n->fsb.suffixMB) { + STR_FREE(o->fsb.suffixMB); + STRDUP_RES(n->fsb.suffixMB); + } + if(o->fsb.suffixGB != n->fsb.suffixGB) { + STR_FREE(o->fsb.suffixGB); + STRDUP_RES(n->fsb.suffixGB); + } + if(o->fsb.suffixTB != n->fsb.suffixTB) { + STR_FREE(o->fsb.suffixTB); + STRDUP_RES(n->fsb.suffixTB); + } + if(o->fsb.errorTitle != n->fsb.errorTitle) { + STR_FREE(o->fsb.errorTitle); + STRDUP_RES(n->fsb.errorTitle); + } + if(o->fsb.errorIllegalChar != n->fsb.errorIllegalChar) { + STR_FREE(o->fsb.errorIllegalChar); + STRDUP_RES(n->fsb.errorIllegalChar); + } + if(o->fsb.errorRename != n->fsb.errorRename) { + STR_FREE(o->fsb.errorRename); + STRDUP_RES(n->fsb.errorRename); + } + if(o->fsb.errorFolder != n->fsb.errorFolder) { + STR_FREE(o->fsb.errorFolder); + STRDUP_RES(n->fsb.errorFolder); + } + if(o->fsb.errorDelete != n->fsb.errorDelete) { + STR_FREE(o->fsb.errorDelete); + STRDUP_RES(n->fsb.errorDelete); + } + if(o->fsb.errorOpenDir != n->fsb.errorOpenDir) { + STR_FREE(o->fsb.errorOpenDir); + STRDUP_RES(n->fsb.errorOpenDir); + } + + if(updateTitle) { + FSBUpdateTitle(neww); + } + if(recreateContextMenu) { + XtDestroyWidget(n->fsb.listContextMenu); + XtDestroyWidget(n->fsb.gridContextMenu); + n->fsb.listContextMenu = CreateContextMenu(n, n->fsb.filelist, FileContextMenuCB); + n->fsb.gridContextMenu = CreateContextMenu(n, n->fsb.grid, FileContextMenuCB); + } + if(setOkBtnLabel) { + XtVaSetValues(n->fsb.okBtn, XmNlabelString, n->fsb.type == FILEDIALOG_OPEN ? n->fsb.labelOpen : n->fsb.labelSave, NULL); + } + + if(!ismanaged && !n->fsb.disable_set_values) { + n->fsb.disable_set_values = 1; + XtVaSetValues(neww, XmNwidth, width, XmNheight, height, NULL); + n->fsb.disable_set_values = 0; + + if(n->fsb.pathBar) + n->fsb.pathBar->disableResize = False; + } + + if(selectItem) { + if(ismanaged) { + FSBSelectItem(n, FileName(n->fsb.selectedPath)); + } + } + + Boolean fr = (xmFormClassRec.core_class.set_values)(old, request, neww, args, num_args); + return fr ? fr : r; +} + +static void fsb_insert_child(Widget child) { + XnFileSelectionBox p = (XnFileSelectionBox)XtParent(child); + (xmFormClassRec.composite_class.insert_child)(child); + + if(!p->fsb.gui_created) { + return; + } + + // custom child widget insert + XtVaSetValues(child, + XmNbottomAttachment, XmATTACH_WIDGET, + XmNbottomWidget, p->fsb.bottom_widget, + XmNbottomOffset, p->fsb.widgetSpacing, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, p->fsb.windowSpacing, + XmNrightAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, p->fsb.windowSpacing, + NULL); + + + XtVaSetValues(p->fsb.listform, + XmNbottomWidget, child, + XmNbottomOffset, 0, + NULL); + + p->fsb.workarea = child; +} + +Boolean fsb_acceptfocus(Widget widget, Time *time) { + return 0; +} + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + pathbar_resize(w->fsb.pathBar->widget, w->fsb.pathBar, NULL); + + if(w->fsb.type == FILEDIALOG_OPEN) { + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + } else { + XmProcessTraversal(w->fsb.name, XmTRAVERSE_CURRENT); + } + + + if(w->fsb.selectedPath) { + FSBSelectItem(w, FileName(w->fsb.selectedPath)); + } +} + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { + +} + +static int apply_filter(XnFileSelectionBox w, const char *pattern, const char *string) { + if(!pattern) return 0; + + FSBFilterFunc func = w->fsb.filterFunc ? w->fsb.filterFunc : FSBGlobFilter; + return func(pattern, string); +} + +static int FSBGlobFilter(const char *a, const char *b) { + return fnmatch(a, b, 0); +} + + +static void errCB(Widget w, XtPointer d, XtPointer cbs) { + XtDestroyWidget(w); +} + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg) { + Arg args[16]; + int n = 0; + + XmString titleStr = XmStringCreateLocalized((char*)title); + XmString msg = XmStringCreateLocalized((char*)errmsg); + + XtSetArg(args[n], XmNdialogTitle, titleStr); n++; + XtSetArg(args[n], XmNselectionLabelString, msg); n++; + XtSetArg(args[n], XmNokLabelString, w->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, w->fsb.labelCancel); n++; + + Widget dialog = XmCreatePromptDialog ((Widget)w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget cancel = XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON); + XtUnmanageChild(cancel); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, errCB, NULL); + + XtManageChild(dialog); + + XmStringFree(titleStr); + XmStringFree(msg); +} + +static void rename_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + char *fileName = NULL; + XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + // make sure the new file name doesn't contain a path separator + if(strchr(fileName, '/')) { + ErrDialog(fsb, fsb->fsb.errorTitle, fsb->fsb.errorIllegalChar); + XtFree(fileName); + return; + } + + char *parentPath = ParentPath(path); + char *newPath = ConcatPath(parentPath, fileName); + + if(rename(path, newPath)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorRename, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + filedialog_update_dir(fsb, parentPath); + } + + free(parentPath); + free(newPath); + XtFree(fileName); + XtDestroyWidget(XtParent(w)); +} + +static void selectionbox_cancel(Widget w, XtPointer data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBRename(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + + XmString filename = XmStringCreateLocalized(name); + XtSetArg(args[n], XmNselectionLabelString,fsb->fsb.labelNewFileName); n++; + XtSetArg(args[n], XmNtextString, filename); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelRename); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "RenameFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)rename_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + XmStringFree(filename); + XtManageChild(dialog); +} + +static void delete_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + if(unlink(path)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorDelete, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + char *parentPath = ParentPath(path); + filedialog_update_dir(fsb, parentPath); + free(parentPath); + } + + XtDestroyWidget(XtParent(w)); +} + +static void FSBDelete(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + size_t len = strlen(name); + size_t msglen = len + strlen(fsb->fsb.labelDeleteFile) + 4; + char *msg = malloc(msglen); + snprintf(msg, msglen, fsb->fsb.labelDeleteFile, name); + + XmString prompt = XmStringCreateLocalized(msg); + XtSetArg(args[n], XmNselectionLabelString, prompt); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelDelete); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "DeleteFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)delete_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + free(msg); + XmStringFree(prompt); + XtManageChild(dialog); +} + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item) { + FSBView view = fsb->fsb.view[fsb->fsb.selectedview]; + if(view.select) { + view.select((Widget)fsb, view.widget, item); + } +} + +static char* set_selected_path(XnFileSelectionBox data, XmString item) +{ + char *name = NULL; + XmStringGetLtoR(item, XmFONTLIST_DEFAULT_TAG, &name); + if(!name) { + return NULL; + } + char *path = ConcatPath(data->fsb.currentPath, name); + XtFree(name); + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; + + return path; +} + +// item0: rename +// item1: delete +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd) { + intptr_t i = (intptr_t)index; + Widget parent = XtParent(item); + XnFileSelectionBox fsb = NULL; + XtVaGetValues(parent, XmNuserData, &fsb, NULL); + + const char *path = fsb->fsb.selectedPath; + if(path) { + if(i == 0) { + FSBRename(fsb, path); + } else if(i == 1) { + FSBDelete(fsb, path); + } + } +} + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback) { + return XmVaCreateSimplePopupMenu( + parent, "popup", callback, XmNpopupEnabled, XmPOPUP_AUTOMATIC, + XmNuserData, fsb, + XmVaPUSHBUTTON, fsb->fsb.labelRename, 'R', NULL, NULL, + XmVaPUSHBUTTON, fsb->fsb.labelDelete, 'D', NULL, NULL, + NULL); +} + + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) { + XnFileSelectionBox data = userData; + FileListWidgetAdd(data, data->fsb.filelist, data->fsb.showHidden, filter, files, filecount); +} + +static void FileListSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numItems = 0; + XmStringTable items = NULL; + XtVaGetValues(w->fsb.filelist, XmNitemCount, &numItems, XmNitems, &items, NULL); + + for(int i=0;ifsb.filelist, i+1, False); + break; + } + XtFree(str); + } +} + +static void FileListCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + XmListDeleteAllItems(data->fsb.filelist); +} + +static void FileListDestroy(Widget fsb, Widget view, void *userData) { + // unused +} + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + data->fsb.selIsDir = False; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } +} + +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *name = NULL; + XmStringGetLtoR(cb->item, XmFONTLIST_DEFAULT_TAG, &name); + XmTextFieldSetString(data->fsb.name, name); + XtFree(name); + } else { + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = False; + } + } +} + + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count) +{ + if(count > 0) { + XmStringTable items = calloc(count, sizeof(XmString)); + int i = 0; + + for(int j=0;jpath); + if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) { + continue; + } + + items[i] = XmStringCreateLocalized(name); + i++; + } + XmListAddItems(w, items, i, 0); + for(i=0;ifsb.grid, data->fsb.showHidden, filter, files, filecount, maxnamelen); +} +#endif + +/* + * create file size string with kb/mb/gb/tb suffix + */ +static char* size_str(XnFileSelectionBox fsb, FileElm *f) { + char *str = malloc(16); + uint64_t size = f->size; + + if(f->isDirectory) { + str[0] = '\0'; + } else if(size < 0x400) { + snprintf(str, 16, "%d %s", (int)size, fsb->fsb.suffixBytes); + } else if(size < 0x100000) { + float s = (float)size/0x400; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x2800 && diff != 0) { + // size < 10 KiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixKB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixKB); + } + } else if(size < 0x40000000) { + float s = (float)size/0x100000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0xa00000 && diff != 0) { + // size < 10 MiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixMB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixMB); + } + } else if(size < 0x1000000000ULL) { + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 GiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixGB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixGB); + } + } else { + size /= 1024; + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 TiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixTB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixTB); + } + } + return str; +} + +static char* date_str(XnFileSelectionBox fsb, time_t tm) { + struct tm t; + struct tm n; + time_t now = time(NULL); + + localtime_r(&tm, &t); + localtime_r(&now, &n); + + char *str = malloc(24); + if(t.tm_year == n.tm_year) { + strftime(str, 24, fsb->fsb.dateFormatSameYear, &t); + } else { + strftime(str, 24, fsb->fsb.dateFormatOtherYear, &t); + } + return str; +} + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailAdjustColWidth(Widget grid) { + XmLGridColumn column0 = XmLGridGetColumn(grid, XmCONTENT, 0); + XmLGridColumn column1 = XmLGridGetColumn(grid, XmCONTENT, 1); + XmLGridColumn column2 = XmLGridGetColumn(grid, XmCONTENT, 2); + + Dimension col0Width = XmLGridColumnWidthInPixels(column0); + Dimension col1Width = XmLGridColumnWidthInPixels(column1); + Dimension col2Width = XmLGridColumnWidthInPixels(column2); + + Dimension totalWidth = col0Width + col1Width + col2Width; + + Dimension gridWidth = 0; + Dimension gridShadow = 0; + XtVaGetValues(grid, XmNwidth, &gridWidth, XmNshadowThickness, &gridShadow, NULL); + + Dimension widthDiff = gridWidth - totalWidth - gridShadow - gridShadow; + + if(gridWidth > totalWidth) { + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, col0Width + widthDiff - XmLGridVSBWidth(grid) - 2, + XmNcolumnSizePolicy, XmCONSTANT, + NULL); + } +} + +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth) +{ + XmLGridAddRows(grid, XmCONTENT, 1, count); + + int row = 0; + for(int i=0;ipath); + if((!showHidden && name[0] == '.') || (!e->isDirectory && apply_filter(fsb, filter, name))) { + continue; + } + + // name + XmString str = XmStringCreateLocalized(name); + XtVaSetValues(grid, + XmNcolumn, 0, + XmNrow, row, + XmNcellString, str, NULL); + XmStringFree(str); + // size + char *szbuf = size_str(fsb, e); + str = XmStringCreateLocalized(szbuf); + XtVaSetValues(grid, + XmNcolumn, 1, + XmNrow, row, + XmNcellString, str, NULL); + free(szbuf); + XmStringFree(str); + // date + char *datebuf = date_str(fsb, e->lastModified); + str = XmStringCreateLocalized(datebuf); + XtVaSetValues(grid, + XmNcolumn, 2, + XmNrow, row, + XmNcellString, str, NULL); + free(datebuf); + XmStringFree(str); + + XtVaSetValues(grid, XmNrow, row, XmNrowUserData, e, NULL); + row++; + } + + // remove unused rows + if(count > row) { + XmLGridDeleteRows(grid, XmCONTENT, row, count-row); + } + + if(maxWidth < 16) { + maxWidth = 16; + } + + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, maxWidth, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 1, + XmNcolumnRangeEnd, 1, + XmNcolumnWidth, 9, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 2, + XmNcolumnRangeEnd, 2, + XmNcolumnWidth, 16, + XmNcellAlignment, XmALIGNMENT_RIGHT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + + FileListDetailAdjustColWidth(grid); +} + +static void FileListDetailSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numRows = 0; + XtVaGetValues(w->fsb.grid, XmNrows, &numRows, NULL); + + XmLGridColumn col = XmLGridGetColumn(w->fsb.grid, XmCONTENT, 0); + for(int i=0;ifsb.grid, XmCONTENT, i); + FileElm *elm = NULL; + XtVaGetValues(w->fsb.grid, XmNrowPtr, row, XmNcolumnPtr, col, XmNrowUserData, &elm, NULL); + if(elm) { + if(!strcmp(item, FileName(elm->path))) { + XmLGridSelectRow(w->fsb.grid, i, False); + XmLGridFocusAndShowRow(w->fsb.grid, i+1); + break; + } + } + } +} + +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + // cleanup grid + Cardinal rows = 0; + XtVaGetValues(data->fsb.grid, XmNrows, &rows, NULL); + XmLGridDeleteRows(data->fsb.grid, XmCONTENT, 0, rows); +} + +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData) { + // unused +} +#endif + +static void create_folder(Widget w, XnFileSelectionBox data, XmSelectionBoxCallbackStruct *cbs) { + char *fileName = NULL; + XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + char *newFolder = ConcatPath(data->fsb.currentPath ? data->fsb.currentPath : "", fileName); + if(mkdir(newFolder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorFolder, strerror(errno)); + ErrDialog(data, data->fsb.errorTitle, errmsg); + } else { + char *p = strdup(data->fsb.currentPath); + filedialog_update_dir(data, p); + free(p); + } + free(newFolder); + + XtDestroyWidget(XtParent(w)); +} + +static void new_folder_cancel(Widget w, XnFileSelectionBox data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u) +{ + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNdialogTitle, data->fsb.labelNewFolder); n++; + XtSetArg (args[n], XmNselectionLabelString, data->fsb.labelDirectoryName); n++; + XtSetArg(args[n], XmNokLabelString, data->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, data->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)create_folder, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)new_folder_cancel, data); + + XtManageChild(dialog); + +} + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u) { + const char *homePath = data->fsb.homePath ? data->fsb.homePath : GetHomeDir(); + filedialog_update_dir(data, homePath); + PathBarSetPath(data->fsb.pathBar, homePath); +} + + +/* + * file_cmp_field + * 0: compare path + * 1: compare size + * 2: compare mtime + */ +static int file_cmp_field = 0; + +/* + * 1 or -1 + */ +static int file_cmp_order = 1; + +static int filecmp(const void *f1, const void *f2) +{ + const FileElm *file1 = f1; + const FileElm *file2 = f2; + if(file1->isDirectory != file2->isDirectory) { + return file1->isDirectory < file2->isDirectory; + } + + int cmp_field = file_cmp_field; + int cmp_order = file_cmp_order; + if(file1->isDirectory) { + cmp_field = 0; + cmp_order = 1; + } + + int ret = 0; + switch(cmp_field) { + case 0: { + ret = strcmp(FileName(file1->path), FileName(file2->path)); + break; + } + case 1: { + if(file1->size < file2->size) { + ret = -1; + } else if(file1->size == file2->size) { + ret = 0; + } else { + ret = 1; + } + break; + } + case 2: { + if(file1->lastModified < file2->lastModified) { + ret = -1; + } else if(file1->lastModified == file2->lastModified) { + ret = 0; + } else { + ret = 1; + } + break; + } + } + + return ret * cmp_order; +} + + +static void free_files(FileElm *ls, int count) +{ + for(int i=0;ifsb.dirs, data->fsb.dircount); + free_files(data->fsb.files, data->fsb.filecount); + data->fsb.dirs = NULL; + data->fsb.files = NULL; + data->fsb.dircount = 0; + data->fsb.filecount = 0; + data->fsb.maxnamelen = 0; +} + +#define FILE_ARRAY_SIZE 1024 + +static void file_array_add(FileElm **files, int *alloc, int *count, FileElm elm) { + int c = *count; + int a = *alloc; + if(c >= a) { + a *= 2; + FileElm *newarray = realloc(*files, sizeof(FileElm) * a); + + *files = newarray; + *alloc = a; + } + + (*files)[c] = elm; + c++; + *count = c; +} + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path) +{ + DIR *dir = NULL; + if(path) { + // try to check first, if we can open the path + dir = opendir(path); + if(!dir) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorOpenDir, strerror(errno)); + + ErrDialog(data, data->fsb.errorTitle, errmsg); + return 1; + } + } + + FSBView view = data->fsb.view[data->fsb.selectedview]; + view.cleanup((Widget)data, view.widget, view.userData); + + if(view.useDirList) { + XmListDeleteAllItems(data->fsb.dirlist); + } + + /* read dir and insert items */ + if(path) { + int dircount = 0; + int filecount = 0; + size_t maxNameLen = 0; + + FileElm *dirs = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + FileElm *files = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + int dirs_alloc = FILE_ARRAY_SIZE; + int files_alloc = FILE_ARRAY_SIZE; + + filedialog_cleanup_filedata(data); + + /* dir reading complete - set the path textfield */ + XmTextFieldSetString(data->fsb.path, (char*)path); + char *oldPath = data->fsb.currentPath; + data->fsb.currentPath = strdup(path); + if(oldPath) { + free(oldPath); + } + path = data->fsb.currentPath; + + struct dirent *ent; + while((ent = readdir(dir)) != NULL) { + if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + char *entpath = ConcatPath(path, ent->d_name); + + struct stat s; + if(stat(entpath, &s)) { + free(entpath); + continue; + } + + FileElm new_entry; + new_entry.path = entpath; + new_entry.isDirectory = S_ISDIR(s.st_mode); + new_entry.size = (uint64_t)s.st_size; + new_entry.lastModified = s.st_mtime; + + size_t nameLen = strlen(ent->d_name); + if(nameLen > maxNameLen) { + maxNameLen = nameLen; + } + + if(new_entry.isDirectory) { + file_array_add(&dirs, &dirs_alloc, &dircount, new_entry); + } else { + file_array_add(&files, &files_alloc, &filecount, new_entry); + } + } + closedir(dir); + + data->fsb.dirs = dirs; + data->fsb.files = files; + data->fsb.dircount = dircount; + data->fsb.filecount = filecount; + data->fsb.maxnamelen = maxNameLen; + + // sort file arrays + qsort(dirs, dircount, sizeof(FileElm), filecmp); + qsort(files, filecount, sizeof(FileElm), filecmp); + } + + Widget filterTF = XmDropDownGetText(data->fsb.filter); + char *filter = XmTextFieldGetString(filterTF); + char *filterStr = filter; + if(!filter || strlen(filter) == 0) { + filterStr = "*"; + } + + if(view.useDirList) { + FileListWidgetAdd(data, data->fsb.dirlist, data->fsb.showHidden, NULL, data->fsb.dirs, data->fsb.dircount); + view.update( + (Widget)data, + view.widget, + NULL, + 0, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } else { + view.update( + (Widget)data, + view.widget, + data->fsb.dirs, + data->fsb.dircount, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } + + if(filter) { + XtFree(filter); + } + + return 0; +} + + +static void dirlist_activate(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + if(!filedialog_update_dir(data, path)) { + PathBarSetPath(data->fsb.pathBar, path); + data->fsb.selIsDir = TRUE; + } + } +} + +static void dirlist_select(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = TRUE; + } +} + +static void filedialog_enable_detailview(Widget w, XnFileSelectionBox data, XmToggleButtonCallbackStruct *tb) { + SelectView(data, tb->set); // 0: list, 1: detail +} + + +static void filedialog_setshowhidden( + Widget w, + XnFileSelectionBox data, + XmToggleButtonCallbackStruct *tb) +{ + data->fsb.showHidden = tb->set; + filedialog_update_dir(data, NULL); +} + +static void filedialog_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); +} + +static void filedialog_update_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); + +} + +static void filedialog_goup(Widget w, XnFileSelectionBox data, XtPointer d) +{ + char *newPath = ParentPath(data->fsb.currentPath); + filedialog_update_dir(data, newPath); + PathBarSetPath(data->fsb.pathBar, newPath); + free(newPath); +} + +static void filedialog_ok(Widget w, XnFileSelectionBox data, XtPointer d) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *newName = XmTextFieldGetString(data->fsb.name); + if(newName) { + if(strchr(newName, '/')) { + ErrDialog(data, data->fsb.errorTitle, data->fsb.errorIllegalChar); + XtFree(newName); + return; + } + + if(strlen(newName) > 0) { + char *selPath = ConcatPath(data->fsb.currentPath, newName); + if(data->fsb.selectedPath) free(data->fsb.selectedPath); + data->fsb.selectedPath = selPath; + } + XtFree(newName); + + data->fsb.selIsDir = False; + } + } + + if(data->fsb.selectedPath) { + if(!data->fsb.selIsDir) { + data->fsb.status = FILEDIALOG_OK; + data->fsb.end = True; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } + } +} + +static void filedialog_cancel(Widget w, XnFileSelectionBox data, XtPointer d) +{ + data->fsb.end = 1; + data->fsb.status = FILEDIALOG_CANCEL; + FileSelectionCallback(data, data->fsb.cancelCallback, XmCR_CANCEL, data->fsb.currentPath); +} + +static void filedialog_help(Widget w, XnFileSelectionBox data, XtPointer d) +{ + FileSelectionCallback(data, data->manager.help_callback, XmCR_HELP, data->fsb.currentPath); +} + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value) { + XmFileSelectionBoxCallbackStruct cbs; + memset(&cbs, 0, sizeof(XmFileSelectionBoxCallbackStruct)); + + char *dir = fsb->fsb.currentPath; + size_t dirlen = dir ? strlen(dir) : 0; + if(dir && dirlen > 0) { + char *dir2 = NULL; + if(dir[dirlen-1] != '/') { + // add a trailing / to the dir string + dir2 = malloc(dirlen+2); + memcpy(dir2, dir, dirlen); + dir2[dirlen] = '/'; + dir2[dirlen+1] = '\0'; + dirlen++; + dir = dir2; + } + cbs.dir = XmStringCreateLocalized(dir); + cbs.dir_length = dirlen; + if(dir2) { + free(dir2); + } + } else { + cbs.dir = XmStringCreateLocalized(""); + cbs.dir_length = 0; + } + cbs.reason = reason; + + cbs.value = XmStringCreateLocalized((char*)value); + cbs.length = strlen(value); + + XtCallCallbackList((Widget)fsb, cb, (XtPointer)&cbs); + + XmStringFree(cbs.dir); + XmStringFree(cbs.value); +} + +static void CreateUI(XnFileSelectionBox w) { + Arg args[32]; + int n = 0; + XmString str; + + int widget_spacing = w->fsb.widgetSpacing; + int window_spacing = w->fsb.windowSpacing; + + Widget form = (Widget)w; + int type = w->fsb.type; + + XtVaSetValues((Widget)w, XmNautoUnmanage, False, NULL); + + /* upper part of the gui */ + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirUp); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNresizable, True); n++; + XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++; + w->fsb.dirUp = XmCreatePushButton(form, "DirUp", args, n); + XtManageChild(w->fsb.dirUp); + XtAddCallback(w->fsb.dirUp, XmNactivateCallback, + (XtCallbackProc)filedialog_goup, w); + + // View Option Menu + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget viewframe = XmCreateForm(form, "vframe", args, n); + XtManageChild(viewframe); + + w->fsb.viewMenu = XmCreatePulldownMenu(viewframe, "menu", NULL, 0); + + Widget view; + if(w->fsb.showViewMenu) { + n = 0; + XtSetArg(args[n], XmNsubMenuId, w->fsb.viewMenu); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNmarginWidth, 0); n++; + view = XmCreateOptionMenu(viewframe, "option_menu", args, n); + XtManageChild(view); + w->fsb.viewOption = view; + w->fsb.detailToggleButton = NULL; + } else { + n = 0; + str = XmStringCreateLocalized(w->fsb.labelDetailView); + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNfillOnSelect, True); n++; + XtSetArg(args[n], XmNindicatorOn, False); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + if(w->fsb.selectedview == 1) { + XtSetArg(args[n], XmNset, 1); n++; + } + w->fsb.detailToggleButton = XmCreateToggleButton(viewframe, "ToggleDetailView", args, n); + XtManageChild(w->fsb.detailToggleButton); + view = w->fsb.detailToggleButton; + XmStringFree(str); + + XtAddCallback( + w->fsb.detailToggleButton, + XmNvalueChangedCallback, + (XtCallbackProc)filedialog_enable_detailview, + w); + + w->fsb.viewOption = NULL; + } + + n = 0; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightWidget, view); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelNewFolder); n++; + w->fsb.newFolder = XmCreatePushButton(viewframe, "NewFolder", args, n); + XtManageChild(w->fsb.newFolder); + XtAddCallback( + w->fsb.newFolder, + XmNactivateCallback, + (XtCallbackProc)FSBNewFolder, + w); + + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHome); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.newFolder); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + w->fsb.home = XmCreatePushButton(viewframe, "Home", args, n); + XtManageChild(w->fsb.home); + XtAddCallback( + w->fsb.home, + XmNactivateCallback, + (XtCallbackProc)FSBHome, + w); + + // match visual appearance of detailToggleButton with the other buttons + if(w->fsb.detailToggleButton) { + Dimension highlight, shadow; + XtVaGetValues(w->fsb.newFolder, XmNshadowThickness, &shadow, XmNhighlightThickness, &highlight, NULL); + XtVaSetValues(w->fsb.detailToggleButton, XmNshadowThickness, shadow, XmNhighlightThickness, highlight, NULL); + } + + // pathbar + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirUp); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, viewframe); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); n++; + Widget pathBarFrame = XmCreateFrame(form, "pathbar_frame", args, n); + XtManageChild(pathBarFrame); + w->fsb.pathBar = CreatePathBar(pathBarFrame, args, 0); + w->fsb.pathBar->getpathelm = ui_default_pathelm_func; + w->fsb.pathBar->updateDir = (updatedir_callback)filedialog_update_dir; + w->fsb.pathBar->updateDirData = w; + XtManageChild(w->fsb.pathBar->widget); + w->fsb.path = XmCreateTextField(form, "textfield", args, 0); + + XtVaSetValues(w->fsb.dirUp, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL); + if(!w->fsb.showViewMenu) { + XtVaSetValues(viewframe, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL); + } + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, pathBarFrame); n++; + XtSetArg(args[n], XmNtopOffset, 2*widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + w->fsb.filterForm = XmCreateForm(form, "filterform", args, n); + XtManageChild(w->fsb.filterForm); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirectories); n++; + w->fsb.lsDirLabel = XmCreateLabel(w->fsb.filterForm, "labelDirs", args, n); + XtManageChild(w->fsb.lsDirLabel); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 35); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++; + w->fsb.lsFileLabel = XmCreateLabel(w->fsb.filterForm, "labelFiles", args, n); + XtManageChild(w->fsb.lsFileLabel); + + if(w->fsb.showHiddenButton) { + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelShowHiddenFiles); n++; + XtSetArg(args[n], XmNset, w->fsb.showHidden); n++; + w->fsb.showHiddenButtonW = XmCreateToggleButton(w->fsb.filterForm, "showHidden", args, n); + XtManageChild(w->fsb.showHiddenButtonW); + XtAddCallback(w->fsb.showHiddenButtonW, XmNvalueChangedCallback, + (XtCallbackProc)filedialog_setshowhidden, w); + } + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFilterButton); n++; + if(w->fsb.showHiddenButton) { + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.showHiddenButtonW); n++; + } else { + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + } + w->fsb.filterButton = XmCreatePushButton(w->fsb.filterForm, "filedialog_filter", args, n); + XtManageChild(w->fsb.filterButton); + XtAddCallback(w->fsb.filterButton, XmNactivateCallback, + (XtCallbackProc)filedialog_filter, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.lsFileLabel); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.filterButton); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshowLabel, False); n++; + XtSetArg(args[n], XmNuseTextField, True); n++; + XtSetArg(args[n], XmNverify, False); n++; + w->fsb.filter = XmCreateDropDown(w->fsb.filterForm, "filedialog_filter_textfield", args, n); + XtManageChild(w->fsb.filter); + XmTextFieldSetString(XmDropDownGetText(w->fsb.filter), w->fsb.filterStr); + XtAddCallback(XmDropDownGetText(w->fsb.filter), XmNactivateCallback, + (XtCallbackProc)filedialog_filter, w); + XtAddCallback(w->fsb.filter, XmNupdateTextCallback, + (XtCallbackProc)filedialog_update_filter, w); + Widget filterList = XmDropDownGetList(w->fsb.filter); + str = XmStringCreateSimple("*"); + XmListAddItem(filterList, str, 0); + XmStringFree(str); + + /* lower part */ + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNtopOffset, widget_spacing * 2); n++; + Widget buttons = XmCreateForm(form, "buttons", args, n); + XtManageChild(buttons); + + n = 0; + str = type == FILEDIALOG_OPEN ? w->fsb.labelOpen : w->fsb.labelSave; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 14); n++; + w->fsb.okBtn = XmCreatePushButton(buttons, "filedialog_open", args, n); + XtManageChild(w->fsb.okBtn); + XmStringFree(str); + XtAddCallback(w->fsb.okBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_ok, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHelp); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 86); n++; + w->fsb.helpBtn = XmCreatePushButton(buttons, "filedialog_help", args, n); + XtManageChild(w->fsb.helpBtn); + XtAddCallback(w->fsb.helpBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_help, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 43); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 57); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelCancel); n++; + w->fsb.cancelBtn = XmCreatePushButton(buttons, "filedialog_cancel", args, n); + XtManageChild(w->fsb.cancelBtn); + XtAddCallback(w->fsb.cancelBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_cancel, w); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, buttons); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, 1); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, 1); n++; + w->fsb.separator = XmCreateSeparator(form, "ofd_separator", args, n); + XtManageChild(w->fsb.separator); + + Widget bottomWidget = w->fsb.separator; + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, w->fsb.separator); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + w->fsb.name = XmCreateTextField(form, "textfield", args, n); + XtAddCallback(w->fsb.name, XmNactivateCallback, + (XtCallbackProc)filedialog_ok, w); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, w->fsb.name); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFileName); n++; + w->fsb.nameLabel = XmCreateLabel(form, "label", args, n); + + if(type == FILEDIALOG_SAVE) { + bottomWidget = w->fsb.nameLabel; + XtManageChild(w->fsb.name); + XtManageChild(w->fsb.nameLabel); + } + w->fsb.bottom_widget = bottomWidget; + + + // middle + // form for dir/file lists + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, w->fsb.filterForm); n++; + XtSetArg(args[n], XmNtopOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, bottomWidget); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNwidth, 580); n++; + XtSetArg(args[n], XmNheight, 400); n++; + w->fsb.listform = XmCreateForm(form, "fds_listform", args, n); + + // dir/file lists + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopWidget, w->fsb.lsDirLabel); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 35); n++; + w->fsb.dirlist = XmCreateScrolledList(w->fsb.listform, "dirlist", args, n); + Dimension width, height; + XtMakeResizeRequest(w->fsb.dirlist, 150, 200, &width, &height); + XtManageChild(w->fsb.dirlist); + + XtAddCallback( + w->fsb.dirlist, + XmNdefaultActionCallback, + (XtCallbackProc)dirlist_activate, + w); + XtAddCallback( + w->fsb.dirlist, + XmNbrowseSelectionCallback, + (XtCallbackProc)dirlist_select, + w); + + // FileList + XnFileSelectionBoxAddView( + (Widget)w, + w->fsb.labelListView, + CreateListView, + FileListUpdate, + FileListSelect, + FileListCleanup, + FileListDestroy, + True, + w); + + // Detail FileList +#ifdef FSB_ENABLE_DETAIL + XnFileSelectionBoxAddView( + (Widget)w, + w->fsb.labelDetailView, + CreateDetailView, + FileListDetailUpdate, + FileListDetailSelect, + FileListDetailCleanup, + FileListDetailDestroy, + True, + w); +#endif + + /* + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + //XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + //XtSetArg(args[n], XmNbottomWidget, w->fsb.filelist); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++; + w->fsb.lsFileLabel = XmCreateLabel(w->fsb.listform, "label", args, n); + XtManageChild(w->fsb.lsFileLabel); + */ + + XtManageChild(w->fsb.listform); + + int selview = w->fsb.selectedview; + if(selview < 2) { + XtManageChild(w->fsb.view[selview].widget); + } else { + w->fsb.selectedview = 0; + } + + + if(w->fsb.selectedPath) { + char *parentPath = ParentPath(w->fsb.selectedPath); + filedialog_update_dir(w, parentPath); + PathBarSetPath(w->fsb.pathBar, parentPath); + free(parentPath); + + if(w->fsb.type == FILEDIALOG_SAVE) { + XmTextFieldSetString(w->fsb.name, FileName(w->fsb.selectedPath)); + } + } else { + char cwd[PATH_MAX]; + const char *currentPath = w->fsb.currentPath; + if(!currentPath) { + if(getcwd(cwd, PATH_MAX)) { + currentPath = cwd; + } else { + currentPath = GetHomeDir(); + } + } + + filedialog_update_dir(w, currentPath); + PathBarSetPath(w->fsb.pathBar, w->fsb.currentPath); + } + + + w->fsb.selectedview = selview; + + XtVaSetValues((Widget)w, XmNcancelButton, w->fsb.cancelBtn, NULL); + + w->fsb.gui_created = 1; +} + +static char* FSBDialogTitle(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + if(w->fsb.type == FILEDIALOG_OPEN) { + return w->fsb.labelOpenFileTitle; + } else { + return w->fsb.labelSaveFileTitle; + } +} + +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc create, void *userData, Boolean useDirList) { + Arg args[64]; + int n = 0; + + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + if(useDirList) { + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++; + XtSetArg(args[n], XmNleftOffset, w->fsb.widgetSpacing); n++; + } else { + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, w->fsb.widgetSpacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + } + + return create(w->fsb.listform, args, n, userData); +} + + +typedef struct FSBViewSelection { + XnFileSelectionBox fsb; + int index; +} FSBViewSelection; + +static void SelectView(XnFileSelectionBox f, int view) { + FSBView current = f->fsb.view[f->fsb.selectedview]; + FSBView newview = f->fsb.view[view]; + + XtUnmanageChild(current.widget); + if(newview.useDirList != current.useDirList) { + if(current.useDirList) { + XtUnmanageChild(f->fsb.listform); + } else { + XtManageChild(f->fsb.listform); + } + } + + current.cleanup((Widget)f, current.widget, current.userData); + XtManageChild(newview.widget); + + f->fsb.selectedview = view; + + filedialog_update_dir(f, NULL); + XmProcessTraversal(newview.focus, XmTRAVERSE_CURRENT); +} + +static void SelectViewCallback(Widget w, FSBViewSelection *data, XtPointer u) { + SelectView(data->fsb, data->index); +} + +static void SelectViewItemDestroy(Widget w, FSBViewSelection *data, XtPointer u) { + free(data); +} + +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex) { + Arg args[4]; + int n = 0; + + XmString label = XmStringCreateLocalized((char*)name); + + XtSetArg(args[n], XmNlabelString, label); n++; + XtSetArg(args[1], XmNpositionIndex, w->fsb.selectedview == w->fsb.numviews ? 0 : w->fsb.numviews+1); n++; + Widget item = XmCreatePushButton(w->fsb.viewMenu, "menuitem", args, n); + + if(viewIndex == 0) { + w->fsb.viewSelectorList = item; + } else if(viewIndex == 1) { + w->fsb.viewSelectorDetail = item; + } + + XtManageChild(item); + XmStringFree(label); + + FSBViewSelection *data = malloc(sizeof(FSBViewSelection)); + data->fsb = w; + data->index = viewIndex; + + XtAddCallback( + item, + XmNactivateCallback, + (XtCallbackProc)SelectViewCallback, + data); + XtAddCallback( + item, + XmNdestroyCallback, + (XtCallbackProc)SelectViewItemDestroy, + data); +} + +static FSBViewWidgets CreateListView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox fsb = (XnFileSelectionBox)userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget frame = XmCreateFrame(parent, "filelistframe", args, n); + + fsb->fsb.filelist = XmCreateScrolledList(frame, "filelist", NULL, 0); + XtManageChild(fsb->fsb.filelist); + + XtAddCallback( + fsb->fsb.filelist, + XmNdefaultActionCallback, + (XtCallbackProc)FileListActivateCB, + userData); + XtAddCallback( + fsb->fsb.filelist, + XmNbrowseSelectionCallback, + (XtCallbackProc)FileListSelectCB, + userData); + + fsb->fsb.listContextMenu = CreateContextMenu(fsb, fsb->fsb.filelist, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = frame; + widgets.focus = fsb->fsb.filelist; + return widgets; +} + +#ifdef FSB_ENABLE_DETAIL +static void set_path_from_row(XnFileSelectionBox data, int row) { + FileElm *elm = NULL; + XmLGridRow rowPtr = XmLGridGetRow(data->fsb.grid, XmCONTENT, row); + XtVaGetValues(data->fsb.grid, XmNrowPtr, rowPtr, XmNrowUserData, &elm, NULL); + if(!elm) { + fprintf(stderr, "error: no row data\n"); + return; + } + + char *path = strdup(elm->path); + + data->fsb.selIsDir = False; + if(data->fsb.type == FILEDIALOG_SAVE) { + XmTextFieldSetString(data->fsb.name, FileName(path)); + } + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; +} + +static void grid_select(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + set_path_from_row(data, cb->row); +} + +static void grid_activate(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + set_path_from_row(data, cb->row); + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); +} + +static void grid_key_pressed(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + char chars[16]; + KeySym keysym; + int nchars; + + nchars = XLookupString(&cb->event->xkey, chars, 15, &keysym, NULL); + + if(nchars == 0) return; + + // if data->showHidden is 0, data->files contains more items than the grid + // this means SelectedRow might not be the correct index for data->files + // we have to count files manually and increase 'row', if the file + // is actually displayed in the grid + int row = 0; + int selectedRow = XmLGridGetSelectedRow(w); + + int match = -1; + + for(int i=0;ifsb.filecount;i++) { + const char *name = FileName(data->fsb.files[i].path); + if(!data->fsb.showHidden && name[0] == '.') continue; + + size_t namelen = strlen(name); + + size_t cmplen = namelen < nchars ? namelen : nchars; + if(!memcmp(name, chars, cmplen)) { + if(row <= selectedRow) { + if(match == -1) { + match = row; + } + } else { + match = row; + break; + } + } + + row++; + } + + if(match > -1) { + XmLGridSelectRow(w, match, True); + XmLGridFocusAndShowRow(w, match+1); + } else { + XBell(XtDisplay(w), 0); + } +} + +static void grid_header_clicked(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + int new_cmp_field = 0; + switch(cb->column) { + case 0: { + new_cmp_field = 0; + break; + } + case 1: { + new_cmp_field = 1; + break; + } + case 2: { + new_cmp_field = 2; + break; + } + } + + if(new_cmp_field == file_cmp_field) { + file_cmp_order = -file_cmp_order; // revert sort order + } else { + file_cmp_field = new_cmp_field; // change file cmp order to new field + file_cmp_order = 1; + } + + int sort_type = file_cmp_order == 1 ? XmSORT_ASCENDING : XmSORT_DESCENDING; + XmLGridSetSort(data->fsb.grid, file_cmp_field, sort_type); + + qsort(data->fsb.files, data->fsb.filecount, sizeof(FileElm), filecmp); + + // refresh widget + filedialog_update_dir(data, NULL); +} + +static FSBViewWidgets CreateDetailView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox w = userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget gridcontainer = XmCreateFrame(parent, "gridcontainer", args, n); + XtManageChild(gridcontainer); + + n = 0; + XtSetArg(args[n], XmNcolumns, 3); n++; + XtSetArg(args[n], XmNheadingColumns, 0); n++; + XtSetArg(args[n], XmNheadingRows, 1); n++; + XtSetArg(args[n], XmNallowColumnResize, 1); n++; + XtSetArg(args[n], XmNsimpleHeadings, w->fsb.detailHeadings); n++; + XtSetArg(args[n], XmNhorizontalSizePolicy, XmCONSTANT); n++; + + w->fsb.grid = XmLCreateGrid(gridcontainer, "grid", args, n); + XmLGridSetIgnoreModifyVerify(w->fsb.grid, True); + XtManageChild(w->fsb.grid); + + XtVaSetValues( + w->fsb.grid, + XmNcellDefaults, True, + XtVaTypedArg, XmNblankBackground, XmRString, "white", 6, + XtVaTypedArg, XmNcellBackground, XmRString, "white", 6, + NULL); + + XtAddCallback(w->fsb.grid, XmNselectCallback, (XtCallbackProc)grid_select, w); + XtAddCallback(w->fsb.grid, XmNactivateCallback, (XtCallbackProc)grid_activate, w); + XtAddCallback(w->fsb.grid, XmNheaderClickCallback, (XtCallbackProc)grid_header_clicked, w); + XtAddCallback(w->fsb.grid, XmNgridKeyPressedCallback, (XtCallbackProc)grid_key_pressed, w); + + // context menu + w->fsb.gridContextMenu = CreateContextMenu(w, w->fsb.grid, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = gridcontainer; + widgets.focus = w->fsb.grid; + return widgets; +} +#endif + + +/* ------------------------------ Path Utils ------------------------------ */ + +const char* GetHomeDir(void) { + char *home = getenv("HOME"); + if(!home) { + home = getenv("USERPROFILE"); + if(!home) { + home = "/"; + } + } + return home; +} + +static char* ConcatPath(const char *parent, const char *name) +{ + size_t parentlen = strlen(parent); + size_t namelen = strlen(name); + + size_t pathlen = parentlen + namelen + 2; + char *path = malloc(pathlen); + + memcpy(path, parent, parentlen); + if(parentlen > 0 && parent[parentlen-1] != '/') { + path[parentlen] = '/'; + parentlen++; + } + if(name[0] == '/') { + name++; + namelen--; + } + memcpy(path+parentlen, name, namelen); + path[parentlen+namelen] = '\0'; + return path; +} + +static char* FileName(char *path) { + int si = 0; + int osi = 0; + int i = 0; + int p = 0; + char c; + while((c = path[i]) != 0) { + if(c == '/') { + osi = si; + si = i; + p = 1; + } + i++; + } + + char *name = path + si + p; + if(name[0] == 0) { + name = path + osi + p; + if(name[0] == 0) { + return path; + } + } + + return name; +} + +static char* ParentPath(const char *path) { + char *name = FileName((char*)path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + if(parentlen == 0) { + parentlen++; + } + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + +// unused at the moment, maybe reactivate if more illegal characters +// are defined +/* +static int CheckFileName(const char *fileName) { + size_t len = strlen(fileName); + for(int i=0;ifsb.numviews >= FSB_MAX_VIEWS) { + fprintf(stderr, "XnFileSelectionBox: too many views\n"); + return; + } + + FSBView view; + view.update = update; + view.select = select; + view.cleanup = cleanup; + view.destroy = destroy; + view.useDirList = useDirList; + view.userData = userData; + + FSBViewWidgets widgets = CreateView(f, create, userData, useDirList); + view.widget = widgets.view; + view.focus = widgets.focus; + + AddViewMenuItem(f, name, f->fsb.numviews); + + f->fsb.view[f->fsb.numviews++] = view; +} + +/* +void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm) { + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + PathBarSetDirList(f->fsb.pathBar, dirlist, nelm); +} +*/ + +Widget XnFileSelectionBoxWorkArea(Widget fsb) { + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + return f->fsb.workarea; +} + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + switch(child) { + case XnFSB_DIR_UP_BUTTON: return w->fsb.dirUp; + case XnFSB_HOME_BUTTON: return w->fsb.home; + case XnFSB_NEW_FOLDER_BUTTON: return w->fsb.newFolder; + case XnFSB_DETAIL_TOGGLE_BUTTON: return w->fsb.detailToggleButton; + case XnFSB_VIEW_OPTION_BUTTON: return w->fsb.viewOption; + case XnFSB_FILTER_DROPDOWN: return w->fsb.filter; + case XnFSB_FILTER_BUTTON: return w->fsb.filterButton; + case XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON: return w->fsb.showHiddenButtonW; + case XnFSB_DIRECTORIES_LABEL: return w->fsb.lsDirLabel; + case XnFSB_FILES_LABEL: return w->fsb.lsFileLabel; + case XnFSB_DIRLIST: return w->fsb.dirlist; + case XnFSB_FILELIST: return w->fsb.filelist; + case XnFSB_GRID: return w->fsb.grid; + case XnFSB_OK_BUTTON: return w->fsb.okBtn; + case XnFSB_CANCEL_BUTTON: return w->fsb.cancelBtn; + case XnFSB_HELP_BUTTON: return w->fsb.helpBtn; + } + return NULL; +} + +void XnFileSelectionBoxDeleteFilters(Widget fsb) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + Widget filterList = XmDropDownGetList(w->fsb.filter); + XmListDeleteAllItems(filterList); +} + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + Widget filterList = XmDropDownGetList(w->fsb.filter); + + XmString str = XmStringCreateSimple((char*)filter); + XmListAddItem(filterList, str, 0); + XmStringFree(str); +} diff -r 3da24640513a -r e57ca2747782 ui/motif/Fsb.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/Fsb.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,233 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSB_H +#define FSB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern WidgetClass xnFsbWidgetClass; + +#define FILEDIALOG_OPEN 1 +#define FILEDIALOG_SAVE 2 + +#define FILEDIALOG_OK 1 +#define FILEDIALOG_CANCEL 2 + +#define XnNwidgetSpacing "fsbWidgetSpacing" +#define XnNwindowSpacing "fsbWindowSpacing" + +#define XnNfsbType "fsbType" +#define XnCfsbType "fsbType" + +#define XnNshowHidden "showHidden" +#define XnCshowHidden "showHidden" +#define XnNshowHiddenButton "showHiddenButton" +#define XnCshowHiddenButton "showHiddenButton" + +#define XnNshowViewMenu "showViewMenu" +#define XnCshowViewMenu "showViewMenu" + +#define XnNselectedView "fsbSelectedView" +#define XnCselectedView "fsbSelectedView" + +#define XnNdirectory "directory" +#define XnCdirectory "directory" +#define XnNselectedPath "selectedPath" +#define XnCselectedPath "selectedPath" +#define XnNhomePath "homePath" +#define XnChomePath "homePath" + +#define XnNfilter "filter" +#define XnCfilter "filter" + +#define XnNfilterFunc "filterFunc" +#define XnCfilterFunc "filterFunc" + +#define XnNlabelListView "labelListView" +#define XnClabelListView "labelListView" +#define XnNlabelDetailView "labelDetailView" +#define XnClabelDetailView "labelDetailView" +#define XnNlabelOpenFileTitle "labelOpenFileTitle" +#define XnClabelOpenFileTitle "labelOpenFileTitle" +#define XnNlabelSaveFileTitle "labelSaveFileTitle" +#define XnClabelSaveFileTitle "labelSaveFileTitel" +#define XnNlabelDirUp "labelDirUp" +#define XnClabelDirUp "labelDirUp" +#define XnNlabelHome "labelHome" +#define XnClabelHome "labelHome" +#define XnNlabelNewFolder "labelNewFolder" +#define XnClabelNewFolder "labelNewFolder" +#define XnNlabelFilter "labelFilter" +#define XnClabelFilter "labelFilter" +#define XnNlabelFilterButton "labelFilterButton" +#define XnClabelFilterButton "labelFilterButton" +#define XnNlabelShowHiddenFiles "labelShowHiddenFiles" +#define XnClabelShowHiddenFiles "labelShowHiddenFiles" +#define XnNlabelDirectories "labelDirectories" +#define XnClabelDirectories "labelDirectories" +#define XnNlabelFiles "labelFiles" +#define XnClabelFiles "labelFiles" +#define XnNlabelRename "labelRename" +#define XnClabelRename "labelRename" +#define XnNlabelDelete "labelDelete" +#define XnClabelDelete "labelDelete" +#define XnNlabelOpen "labelOpen" +#define XnClabelOpen "labelOpen" +#define XnNlabelSave "labelSave" +#define XnClabelSave "labelSave" +#define XnNlabelOk "labelOk" +#define XnClabelOk "labelOk" +#define XnNlabelCancel "labelCancel" +#define XnClabelCancel "labelCancel" +#define XnNlabelHelp "labelHelp" +#define XnClabelHelp "labelHelp" +#define XnNlabelFileName "labelFileName" +#define XnClabelFileName "labelFileName" +#define XnNlabelDirectoryName "labelDirectoryName" +#define XnClabelDirectoryName "labelDirectoryName" +#define XnNlabelNewFileName "labelNewFileName" +#define XnClabelNewFileName "labelNewFileName" +#define XnNlabelDeleteFile "labelDeleteFile" +#define XnClabelDeleteFile "labelDeleteFile" +#define XnNdetailHeadings "detailHeadings" +#define XnCdetailHeadings "detailHeadings" +#define XnNdateFormatSameYear "dateFormatSameYear" +#define XnCdateFormatSameYear "dateFormatSameYear" +#define XnNdateFormatOtherYear "dateFormatOtherYear" +#define XnCdateFormatOtherYear "dateFormatOtherYear" +#define XnNsuffixBytes "suffixBytes" +#define XnCsuffixBytes "suffixBytes" +#define XnNsuffixKB "suffixKB" +#define XnCsuffixKB "suffixKB" +#define XnNsuffixMB "suffixMB" +#define XnCsuffixMB "suffixMB" +#define XnNsuffixGB "suffixGB" +#define XnCsuffixGB "suffixGB" +#define XnNsuffixTB "suffixTB" +#define XnCsuffixTB "suffixTB" +#define XnNerrorTitle "errorTitle" +#define XnCerrorTitle "errorTitle" +#define XnNerrorIllegalChar "errorIllegalChar" +#define XnCerrorIllegalChar "errorIllegalChar" +#define XnNerrorRename "errorRename" +#define XnCerrorRename "errorRename" +#define XnNerrorCreateFolder "errorCreateFolder" +#define XnCerrorCreateFolder "errorCreateFolder" +#define XnNerrorDelete "errorDelete" +#define XnCerrorDelete "errorDelete" +#define XnNerrorOpenDir "errorOpenDir" +#define XnCerrorOpenDir "errorOpenDir" + +/* + * int FSBFilterFunc(const char *pattern, const char *string) + * + * Checks whether the string matches the pattern + * + * Return + * zero if the string matches the pattern + * non-zero if there is no match + */ +typedef int(*FSBFilterFunc)(const char*, const char*); + + +typedef struct FileElm FileElm; +struct FileElm { + char *path; + int isDirectory; + unsigned long long size; + time_t lastModified; +}; + +typedef struct { + Widget view; + Widget focus; +} FSBViewWidgets; + +enum XnFSBChild { + XnFSB_DIR_UP_BUTTON = 0, + XnFSB_HOME_BUTTON, + XnFSB_NEW_FOLDER_BUTTON, + XnFSB_DETAIL_TOGGLE_BUTTON, + XnFSB_VIEW_OPTION_BUTTON, + XnFSB_FILTER_DROPDOWN, + XnFSB_FILTER_BUTTON, + XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON, + XnFSB_DIRECTORIES_LABEL, + XnFSB_FILES_LABEL, + XnFSB_DIRLIST, + XnFSB_FILELIST, + XnFSB_GRID, + XnFSB_OK_BUTTON, + XnFSB_CANCEL_BUTTON, + XnFSB_HELP_BUTTON +}; + +typedef FSBViewWidgets(*FSBViewCreateProc)(Widget parent, ArgList args, int n, void *userData); +typedef void(*FSBViewUpdateProc)(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +typedef void(*FSBViewSelectProc)(Widget fsb, Widget view, const char *item); +typedef void(*FSBViewCleanupProc)(Widget fsb, Widget view, void *userData); +typedef void(*FSBViewDestroyProc)(Widget fsb, Widget view, void *userData); + +Widget XnCreateFileSelectionDialog( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +Widget XnCreateFileSelectionBox( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +void XnFileSelectionBoxAddView( + Widget fsb, + const char *name, + FSBViewCreateProc create, + FSBViewUpdateProc update, + FSBViewSelectProc select, + FSBViewCleanupProc cleanup, + FSBViewDestroyProc destroy, + Boolean useDirList, + void *userData); + +//void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm); + +Widget XnFileSelectionBoxWorkArea(Widget fsb); + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child); + +void XnFileSelectionBoxDeleteFilters(Widget fsb); + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter); + +#ifdef __cplusplus +} +#endif + +#endif /* FSB_H */ diff -r 3da24640513a -r e57ca2747782 ui/motif/FsbP.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/FsbP.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,212 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSBP_H +#define FSBP_H + +#include +#include +#include +#include +#include + +#include "Fsb.h" +#include "pathbar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FSB_MAX_VIEWS 8 + + +typedef struct FSBView FSBView; +struct FSBView { + Widget widget; + Widget focus; + FSBViewUpdateProc update; + FSBViewSelectProc select; + FSBViewCleanupProc cleanup; + FSBViewDestroyProc destroy; + void *userData; + Boolean useDirList; +}; + + +typedef struct FSBClassPart { + int unused; +} FSBClassPart; + +typedef struct FSBClassRec { + CoreClassPart core_class; + CompositeClassPart composite_class; + ConstraintClassPart constraint_class; + XmManagerClassPart manager_class; + XmBulletinBoardClassPart bulletin_board_class; + XmFormClassPart form_class; + FSBClassPart fsb_class; +} FSBClassRec; + +typedef struct FSBPart { + XtCallbackList okCallback; + XtCallbackList cancelCallback; + + Dimension widgetSpacing; + Dimension windowSpacing; + + Boolean showHiddenButton; + + Widget path; + PathBar *pathBar; + Widget filter; + Widget filterButton; + Widget showHiddenButtonW; + + FSBFilterFunc filterFunc; + + char *filterStr; + + Widget dirUp; + Widget home; + Widget newFolder; + + Widget viewSelectorList; + Widget viewSelectorDetail; + + Widget viewMenu; + Widget viewOption; + Widget detailToggleButton; + + Widget filterForm; + Widget lsDirLabel; + Widget lsFileLabel; + + Widget listContextMenu; + Widget gridContextMenu; + + // icon view + + // dir/file list view + Widget listform; + Widget dirlist; + + FSBView view[FSB_MAX_VIEWS]; + int numviews; + int selectedview; + + Widget filelist; + Widget grid; + + Widget separator; + + Widget nameLabel; + Widget name; + + Widget bottom_widget; + + Widget workarea; + + Widget okBtn; + Widget cancelBtn; + Widget helpBtn; + + FileElm *dirs; + FileElm *files; + int dircount; + int filecount; + int maxnamelen; + + char *homePath; + + char *currentPath; + char *selectedPath; + int selIsDir; + Boolean showHidden; + Boolean showViewMenu; + + int type; + + int end; + int status; + + int disable_set_values; + int gui_created; + + char *labelListView; + char *labelDetailView; + char* labelOpenFileTitle; + char* labelSaveFileTitle; + XmString labelDirUp; + XmString labelHome; + XmString labelNewFolder; + XmString labelFilterButton; + XmString labelShowHiddenFiles; + XmString labelDirectories; + XmString labelFiles; + XmString labelRename; + XmString labelDelete; + XmString labelOpen; + XmString labelSave; + XmString labelOk; + XmString labelCancel; + XmString labelHelp; + XmString labelFileName; + XmString labelDirectoryName; + XmString labelNewFileName; + char *labelDeleteFile; + + char *detailHeadings; + + char *dateFormatSameYear; + char *dateFormatOtherYear; + char *suffixBytes; + char *suffixKB; + char *suffixMB; + char *suffixGB; + char *suffixTB; + + char *errorTitle; + char *errorIllegalChar; + char *errorRename; + char *errorFolder; + char *errorDelete; + char *errorOpenDir; +} FSBPart; + +typedef struct FSBRec { + CorePart core; + CompositePart composite; + ConstraintPart constraint; + XmManagerPart manager; + XmBulletinBoardPart bulletin_board; + XmFormPart form; + FSBPart fsb; +} FSBRec; + +typedef struct FSBRec *XnFileSelectionBox; + +#ifdef __cplusplus +} +#endif + +#endif /* FSBP_H */ + diff -r 3da24640513a -r e57ca2747782 ui/motif/Grid.c --- a/ui/motif/Grid.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/Grid.c Sat Dec 13 15:58:58 2025 +0100 @@ -60,7 +60,7 @@ XmRDimension, sizeof (Dimension), XtOffsetOf( GridRec, - mywidget.columnspacing), + grid.columnspacing), XmRImmediate, (XtPointer) 0 }, @@ -70,17 +70,47 @@ XmRDimension, sizeof (Dimension), XtOffsetOf( GridRec, - mywidget.rowspacing), + grid.rowspacing), + XmRImmediate, + (XtPointer) 0 + }, + { + gridPaddingLeft, + gridPaddingLeft, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + grid.padding_left), XmRImmediate, (XtPointer) 0 }, { - gridMargin, - gridMargin, + gridPaddingRight, + gridPaddingRight, XmRDimension, sizeof (Dimension), XtOffsetOf( GridRec, - mywidget.margin), + grid.padding_right), + XmRImmediate, + (XtPointer) 0 + }, + { + gridPaddingTop, + gridPaddingTop, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + grid.padding_top), + XmRImmediate, + (XtPointer) 0 + }, + { + gridPaddingBottom, + gridPaddingBottom, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + grid.padding_bottom), XmRImmediate, (XtPointer) 0 } @@ -305,8 +335,8 @@ void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) { Grid mn = (Grid)new; - mn->mywidget.max_col = 0; - mn->mywidget.max_row = 0; + mn->grid.max_col = 0; + mn->grid.max_row = 0; } void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) { @@ -364,17 +394,17 @@ } void GridChangeManaged(Widget widget) { - + grid_place_children((Grid)widget); } Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { GridConstraintRec *constraints = neww->core.constraints; Grid grid = (Grid)XtParent(neww); - if(constraints->grid.x > grid->mywidget.max_col) { - grid->mywidget.max_col = constraints->grid.x; + if(constraints->grid.x > grid->grid.max_col) { + grid->grid.max_col = constraints->grid.x; } - if(constraints->grid.y > grid->mywidget.max_row) { - grid->mywidget.max_row = constraints->grid.y; + if(constraints->grid.y > grid->grid.max_row) { + grid->grid.max_row = constraints->grid.y; } } @@ -389,25 +419,31 @@ GridConstraintRec *constraints = neww->core.constraints; Grid grid = (Grid)XtParent(neww); - if(constraints->grid.x > grid->mywidget.max_col) { - grid->mywidget.max_col = constraints->grid.x; + if(constraints->grid.x > grid->grid.max_col) { + grid->grid.max_col = constraints->grid.x; } - if(constraints->grid.y > grid->mywidget.max_row) { - grid->mywidget.max_row = constraints->grid.y; + if(constraints->grid.y > grid->grid.max_row) { + grid->grid.max_row = constraints->grid.y; } constraints->grid.pref_width = neww->core.width; constraints->grid.pref_height = neww->core.height; } void grid_place_children(Grid w) { - int ncols = w->mywidget.max_col+1; - int nrows = w->mywidget.max_row+1; + if(!XtIsRealized((Widget)w)) { + return; + } + + int ncols = w->grid.max_col+1; + int nrows = w->grid.max_row+1; GridDef *cols = calloc(ncols, sizeof(GridDef)); GridDef *rows = calloc(nrows, sizeof(GridDef)); int num_cols_expanding = 0; int num_rows_expanding = 0; - int req_width = 0; - int req_height = 0; + int req_width = w->grid.padding_left + w->grid.padding_right; + int req_height = w->grid.padding_top + w->grid.padding_bottom; + int width = w->core.width; + int height = w->core.height; //printf("container width: %d\n", (int)w->core.width); @@ -428,6 +464,12 @@ if(constraints->grid.pref_width < constraints->grid.min_width) { constraints->grid.pref_width = constraints->grid.min_width; } + int elm_width = constraints->grid.pref_width + constraints->grid.margin_left + constraints->grid.margin_right; + int elm_height = constraints->grid.pref_height + constraints->grid.margin_top + constraints->grid.margin_bottom; + if(!XtIsManaged(child)) { + elm_width = 0; + elm_height = 0; + } if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) { continue; @@ -489,12 +531,12 @@ span_width = last_col->size; } - int diff = constraints->grid.pref_width - span_width; + int diff = elm_width - span_width; if(diff > 0) { last_col->size += diff; } - } else if(constraints->grid.pref_width > col->size) { - col->size = constraints->grid.pref_width; + } else if(elm_width > col->size) { + col->size = elm_width; } // row size if(constraints->grid.rowspan > 1) { @@ -505,12 +547,12 @@ span_height = last_row->size; } - int diff = constraints->grid.pref_height - span_height; + int diff = elm_height - span_height; if(diff > 0) { last_row->size += diff; } - } else if(constraints->grid.pref_height > row->size) { - row->size = constraints->grid.pref_height; + } else if(elm_height > row->size) { + row->size = elm_height; } } span_max = 50000; // not sure if this is unreasonable low or high @@ -530,10 +572,23 @@ req_height += rows[i].size; } + int total_colspacing = 0; + int total_rowspacing = 0; + for(int i=0;i+1 0) { + total_colspacing += w->grid.columnspacing; + } + } + for(int i=0;i+1 0) { + total_rowspacing += w->grid.rowspacing; + } + } + if(req_width > 0 && req_height > 0) { // add col/row spacing - req_width += (ncols-1)*w->mywidget.columnspacing; - req_height += (nrows-1)*w->mywidget.rowspacing; + req_width += total_colspacing; //(ncols-1)*w->grid.columnspacing; + req_height += total_rowspacing; //(nrows-1)*w->grid.rowspacing; Widget parent = w->core.parent; Dimension rwidth = req_width; @@ -545,9 +600,9 @@ //rheight = w->core.height; } - if(!w->mywidget.sizerequest) { + if(!w->grid.sizerequest) { Dimension actual_width, actual_height; - w->mywidget.sizerequest = TRUE; + w->grid.sizerequest = TRUE; //printf("sizerequest: %d x %d\n", (int)req_width, (int)req_height); @@ -558,7 +613,7 @@ //XtGeometryResult result = XtMakeGeometryRequest((Widget)w, &request, &reply); XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height); - w->mywidget.sizerequest = FALSE; + w->grid.sizerequest = FALSE; //printf("size request: %d %d\n", (int)actual_width, (int)actual_height); } @@ -568,37 +623,41 @@ // how much space can we add to each expanding col/row int hexpand = 0; - int width_diff = (int)w->core.width - req_width; + int width_diff = width - req_width; int hexpand2 = 0; if(width_diff > 0 && num_cols_expanding > 0) { hexpand = width_diff / num_cols_expanding; hexpand2 = width_diff-hexpand*num_cols_expanding; } - int x = 0; + int x = w->grid.padding_left; for(int i=0;imywidget.columnspacing; + if(cols[i].size > 0) { + x += cols[i].size + w->grid.columnspacing; + } hexpand2 = 0; } int vexpand = 0; - int height_diff = (int)w->core.height - req_height; + int height_diff = height - req_height; int vexpand2 = 0; if(height_diff > 0 && num_rows_expanding > 0) { vexpand = height_diff / num_rows_expanding; vexpand2 = height_diff-vexpand*num_rows_expanding; } - int y = 0; + int y = w->grid.padding_bottom; for(int i=0;imywidget.rowspacing; + if(rows[i].size > 0) { + y += rows[i].size += w->grid.rowspacing; + } vexpand2 = 0; } @@ -608,8 +667,8 @@ GridConstraintRec *constraints = child->core.constraints; GridDef c = cols[constraints->grid.x]; GridDef r = rows[constraints->grid.y]; - int x = c.pos; - int y = r.pos; + int x = c.pos + constraints->grid.margin_left; + int y = r.pos + constraints->grid.margin_top; int width = constraints->grid.pref_width; int height = constraints->grid.pref_height; if(constraints->grid.hfill) { @@ -617,12 +676,12 @@ Dimension cwidth = 0; for(int j=0;jgrid.colspan;j++) { if(constraints->grid.x+j < ncols) { - cwidth += cols[constraints->grid.x+j].size + (j > 0 ? w->mywidget.columnspacing : 0); + cwidth += cols[constraints->grid.x+j].size + (j > 0 ? w->grid.columnspacing : 0); } } width = cwidth; } else { - width = c.size - w->mywidget.columnspacing; + width = c.size - w->grid.columnspacing - constraints->grid.margin_left - constraints->grid.margin_right; } } if(constraints->grid.vfill) { @@ -630,15 +689,15 @@ Dimension cheight = 0; for(int j=0;jgrid.rowspan;j++) { if(constraints->grid.y+j < nrows) { - cheight += rows[constraints->grid.y+j].size + (j > 0 ? w->mywidget.rowspacing : 0); + cheight += rows[constraints->grid.y+j].size + (j > 0 ? w->grid.rowspacing : 0); } } height = cheight; } else { - height = r.size - w->mywidget.rowspacing; + height = r.size - w->grid.rowspacing - constraints->grid.margin_top - constraints->grid.margin_bottom; } } - + if(width > 0 && height > 0) { XtConfigureWidget(child, x, y, width, height, child->core.border_width); } diff -r 3da24640513a -r e57ca2747782 ui/motif/Grid.h --- a/ui/motif/Grid.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/Grid.h Sat Dec 13 15:58:58 2025 +0100 @@ -42,8 +42,11 @@ // resources #define gridColumnSpacing "gridColumnSpacing" -#define gridRowSpacing "gridRowSpacing" -#define gridMargin "gridMargin" +#define gridRowSpacing "gridRowSpacing" +#define gridPaddingLeft "gridPaddingLeft" +#define gridPaddingRight "gridPaddingRight" +#define gridPaddingTop "gridPaddingTop" +#define gridPaddingBottom "gridPaddingBottom" // constraints #define gridColumn "gridColumn" @@ -75,21 +78,20 @@ CoreClassPart core_class; CompositeClassPart composite_class; ConstraintClassPart constraint_class; - XmManagerClassPart manager_class; - GridClassPart mywidgetclass; + XmManagerClassPart manager_class; + GridClassPart gridwidgetclass; } GridClassRec; typedef struct GridPart { - int margin_left; - int margin_right; - int margin_top; - int margin_bottom; + int padding_left; + int padding_right; + int padding_top; + int padding_bottom; int max_col; int max_row; Dimension columnspacing; Dimension rowspacing; - Dimension margin; Boolean sizerequest; } GridPart; @@ -99,7 +101,7 @@ CompositePart composite; ConstraintPart constraint; XmManagerPart manager; - GridPart mywidget; + GridPart grid; } GridRec; typedef struct GridContraintPart { diff -r 3da24640513a -r e57ca2747782 ui/motif/button.c --- a/ui/motif/button.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/button.c Sat Dec 13 15:58:58 2025 +0100 @@ -61,7 +61,7 @@ XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); if(args->onclick) { UiEventData *eventdata = malloc(sizeof(UiEventData)); @@ -77,7 +77,7 @@ XtAddCallback( button, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } @@ -118,9 +118,9 @@ XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); - ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_state); XmStringFree(label); return button; @@ -146,9 +146,9 @@ XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); - ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_state); XmStringFree(label); return button; @@ -162,9 +162,9 @@ if(event->value > 0) { // button in configured to enable/disable states if(tb->set) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -224,7 +224,7 @@ XtAddCallback( widget, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, event); } @@ -249,9 +249,9 @@ if(event->value > 0) { // button in configured to enable/disable states if(tb->set) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -341,7 +341,7 @@ XtAddCallback( rbutton, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, event); } @@ -365,7 +365,7 @@ XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { @@ -402,7 +402,7 @@ event->userdata = args->onchangedata; event->observers = NULL; event->var = var; - event->value = args->enable_group; + event->value = args->enable_state; XtAddCallback( button, XmNvalueChangedCallback, @@ -411,7 +411,7 @@ XtAddCallback( button, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, event); XmStringFree(label); diff -r 3da24640513a -r e57ca2747782 ui/motif/container.c --- a/ui/motif/container.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/container.c Sat Dec 13 15:58:58 2025 +0100 @@ -55,6 +55,18 @@ container->add(container, widget); } +void ui_container_apply_grid_margin( + Arg *args, + int *n, + int margin_left, int margin_right, int margin_top, int margin_bottom) +{ + int c = *n; + XtSetArg(args[c], gridMarginLeft, margin_left); c++; + XtSetArg(args[c], gridMarginRight, margin_right); c++; + XtSetArg(args[c], gridMarginTop, margin_top); c++; + XtSetArg(args[c], gridMarginBottom, margin_bottom); c++; + *n = c; +} /* ---------------------------- Box Container ---------------------------- */ @@ -66,9 +78,9 @@ int n = 0; if(orientation == UI_BOX_VERTICAL) { - //XtSetArg(xargs[n], gridRowSpacing, args->spacing); n++; + XtSetArg(xargs[n], gridRowSpacing, args->spacing); n++; } else { - //XtSetArg(xargs[n], gridColumnSpacing, args->spacing); n++; + XtSetArg(xargs[n], gridColumnSpacing, args->spacing); n++; } Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); @@ -102,7 +114,7 @@ } static Widget ui_box_container_prepare(UiBoxContainer *box, UiLayout *layout, Arg *args, int *n) { - int a = *n; + ui_container_apply_grid_margin(args, n, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); box->n++; return box->container.widget; } @@ -151,7 +163,6 @@ UiLayout layout = UI_ARGS2LAYOUT(args); Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); - XtSetArg(xargs[n], gridMargin, args->margin); n++; XtSetArg(xargs[n], gridColumnSpacing, args->columnspacing); n++; XtSetArg(xargs[n], gridRowSpacing, args->rowspacing); n++; Widget grid = XtCreateManagedWidget(args->name ? args->name : "gridcontainer", gridClass, parent, xargs, n); @@ -218,6 +229,7 @@ } *n = a; + ui_container_apply_grid_margin(args, n, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); return ctn->widget; } @@ -237,9 +249,15 @@ UiLayout layout = UI_ARGS2LAYOUT(args); Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + if(args->width > 0) { + XtSetArg(xargs[n], XmNwidth, args->width); n++; + } + if(args->height > 0) { + XtSetArg(xargs[n], XmNheight, args->height); n++; + } char *name = args->name ? (char*)args->name : "frame"; - Widget frame = XmCreateFrame(parent, name, xargs, 6); + Widget frame = XmCreateFrame(parent, name, xargs, n); XtManageChild(frame); ui_container_add(ctn, frame); @@ -259,7 +277,8 @@ UiContainerArgs sub_args = { .spacing = args->spacing, .columnspacing = args->columnspacing, - .rowspacing = args->rowspacing + .rowspacing = args->rowspacing, + .margin = args->padding }; switch(args->subcontainer) { default: break; @@ -334,9 +353,10 @@ if(numbuttons == 0) { return; } + width--; int button_width = width / numbuttons; int x = 0; - + CxIterator i = cxListIterator(tabview->tabs); cx_foreach(UiTab *, tab, i) { if(i.index + 1 == numbuttons) { @@ -432,6 +452,7 @@ tabview->current_index = -1; UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer)); + memset(ct, 0, sizeof(UiTabViewContainer)); ct->container.widget = form; ct->container.type = UI_CONTAINER_TABVIEW; ct->container.prepare = ui_tabview_container_prepare; @@ -682,6 +703,12 @@ int n = 0; XtSetArg(xargs[n], XmNscrollingPolicy, XmAUTOMATIC); n++; + if(args->width > 0) { + XtSetArg(xargs[n], XmNwidth, args->width); n++; + } + if(args->height > 0) { + XtSetArg(xargs[n], XmNheight, args->height); n++; + } Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", xargs, n); diff -r 3da24640513a -r e57ca2747782 ui/motif/container.h --- a/ui/motif/container.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/container.h Sat Dec 13 15:58:58 2025 +0100 @@ -129,6 +129,10 @@ Widget ui_container_prepare(UiContainerPrivate *container, UiLayout *layout, Arg *args, int *n); void ui_container_add(UiContainerPrivate *container, Widget widget); +void ui_container_apply_grid_margin( + Arg *args, + int *n, + int margin_left, int margin_right, int margin_top, int margin_bottom); void ui_motif_tabview_select(UiMotifTabView *tabview, int tab); void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child); diff -r 3da24640513a -r e57ca2747782 ui/motif/entry.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/entry.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,281 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "entry.h" + + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + Arg xargs[16]; + int n = 0; + + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(args->step == 0) { + args->step = 1; + } + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + + XtSetArg(xargs[n], XmNminimumValue, 0); n++; + XtSetArg(xargs[n], XmNmaximumValue, 100); n++; + XtSetArg(xargs[n], XmNincrementValue, 1); n++; + XtSetArg(xargs[n], XmNspinBoxChildType, XmNUMERIC); n++; + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + char *name = args->name ? (char*)args->name : "button"; + Widget spinbox = XmCreateSimpleSpinBox(parent, name, xargs, n); + XtManageChild(spinbox); + ui_container_add(ctn, spinbox); + + ui_set_widget_groups(obj->ctx, spinbox, args->states); + + WidgetList children; + Cardinal num_children; + unsigned char type; + + Widget textfield = NULL; + XtVaGetValues( + spinbox, + XmNchildren, &children, + XmNnumChildren, &num_children, + NULL); + + for(int i = 0;iobj = obj; + data->textfield = textfield; + data->var = var; + data->vartype = vartype; + data->obs = NULL; + data->onchange = args->onchange; + data->onchangedata = args->onchangedata; + data->value = 0; + data->min = min; + data->max = max; + data->increment = args->step; + data->digits = args->digits; + + UiObserver **obs = NULL; + if(var) { + double value = 0; + switch(vartype) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = data; + value = (double)i->value; + obs = &i->observers; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = data; + value = d->value; + obs = &d->observers; + break; + } + case UI_VAR_RANGE: { + UiRange *r = var->value; + r->get = ui_spinbutton_getrangeval; + r->set = ui_spinbutton_setrangeval; + r->setrange = ui_spinbutton_setrange; + r->setextent = ui_spinbutton_setextent; + r->obj = data; + value = r->value; + obs = &r->observers; + break; + } + } + ui_spinbox_set_value(data, value); + } + data->obs = obs; + + XtAddCallback( + spinbox, + XmNvalueChangedCallback, + (XtCallbackProc)ui_spinbox_value_changed, + data); + + XtAddCallback( + spinbox, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_data, + data); + + XmTextFieldSetString(textfield, "0"); + + + return spinbox; +} + +void ui_spinbox_set_value(UiSpinBox *spinbox, double value) { + if(value < spinbox->min) { + value = spinbox->min; + } + if(value > spinbox->max) { + value = spinbox->max; + } + + char buf[32]; + snprintf(buf, 32, "%.*f", spinbox->digits, spinbox->value); + XmTextFieldSetString(spinbox->textfield, buf); + spinbox->value = value; +} + +void ui_spinbox_value_changed(Widget widget, UiSpinBox *spinbox, XmSpinBoxCallbackStruct *cb) { + Boolean update_value = TRUE; + double value = spinbox->value; + switch(cb->reason) { + case XmCR_OK: { + update_value = FALSE; + break; + } + case XmCR_SPIN_NEXT: { + value += spinbox->increment; + break; + } + case XmCR_SPIN_PRIOR: { + value -= spinbox->increment; + break; + } + } + + if(update_value) { + ui_spinbox_set_value(spinbox, value); + + UiEvent event; + event.obj = spinbox->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = (int64_t)value; + event.set = ui_get_setop(); + + if(spinbox->onchange) { + spinbox->onchange(&event, spinbox->onchangedata); + } + + UiObserver *obs = *spinbox->obs; + ui_notify_evt(*spinbox->obs, &event); + } +} + +int64_t ui_spinbutton_getint(UiInteger *i) { + UiSpinBox *spinbox = i->obj; + i->value = (int64_t)spinbox->value; + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + UiSpinBox *spinbox = i->obj; + ui_spinbox_set_value(spinbox, (double)val); + i->value = spinbox->value; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + UiSpinBox *spinbox = d->obj; + d->value = spinbox->value; + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + UiSpinBox *spinbox = d->obj; + ui_spinbox_set_value(spinbox, val); + d->value = spinbox->value; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + UiSpinBox *spinbox = r->obj; + r->value = spinbox->value; + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + UiSpinBox *spinbox = r->obj; + ui_spinbox_set_value(spinbox, val); + r->value = spinbox->value; +} +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + UiSpinBox *spinbox = r->obj; + spinbox->min = min; + spinbox->max = max; + r->min = min; + r->max = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + UiSpinBox *spinbox = r->obj; + spinbox->increment = extent; + r->extent = extent; +} diff -r 3da24640513a -r e57ca2747782 ui/motif/entry.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/entry.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,77 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ENTRY_H +#define ENTRY_H + +#include "../ui/entry.h" +#include "container.h" +#include "toolkit.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiSpinBox { + UiObject *obj; + Widget textfield; + UiVar *var; + UiVarType vartype; + UiObserver **obs; + ui_callback onchange; + void* onchangedata; + double value; + double min; + double max; + double increment; + int digits; +} UiSpinBox; + +void ui_spinbox_set_value(UiSpinBox *spinbox, double value); + +void ui_spinbox_value_changed(Widget widget, UiSpinBox *spinbox, XmSpinBoxCallbackStruct *cb); + +int64_t ui_spinbutton_getint(UiInteger *i); +void ui_spinbutton_setint(UiInteger *i, int64_t val); + +double ui_spinbutton_getdouble(UiDouble *d); +void ui_spinbutton_setdouble(UiDouble *d, double val); + +double ui_spinbutton_getrangeval(UiRange *r); +void ui_spinbutton_setrangeval(UiRange *r, double val); +void ui_spinbutton_setrange(UiRange *r, double min, double max); +void ui_spinbutton_setextent(UiRange *r, double extent); + +#ifdef __cplusplus +} +#endif + +#endif /* ENTRY_H */ + diff -r 3da24640513a -r e57ca2747782 ui/motif/label.c --- a/ui/motif/label.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/label.c Sat Dec 13 15:58:58 2025 +0100 @@ -110,15 +110,15 @@ } void ui_label_set(UiString *s, const char *str) { + Widget w = s->obj; + XmString s1 = XmStringCreateLocalized(str ? (char*)str : ""); + XtVaSetValues(w, XmNlabelString, s1, NULL); + XmStringFree(s1); if(s->value.free) { s->value.free(s->value.ptr); s->value.free = NULL; s->value.ptr = NULL; } - Widget w = s->obj; - XmString s1 = XmStringCreateLocalized(str ? (char*)str : ""); - XtVaSetValues(w, XmNlabelString, s1, NULL); - XmStringFree(s1); } /* -------------------------- progressbar/spiner -------------------------- */ diff -r 3da24640513a -r e57ca2747782 ui/motif/list.c --- a/ui/motif/list.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/list.c Sat Dec 13 15:58:58 2025 +0100 @@ -28,6 +28,7 @@ #include #include +#include #include "container.h" @@ -62,6 +63,9 @@ } else { XtSetArg(xargs[n], XmNselectionPolicy, XmSINGLE_SELECT); n++; } + if(args->height > 0) { + XtSetArg(xargs[n], XmNheight, args->height); n++; + } char *name = args->name ? (char*)args->name : "listview"; Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); @@ -91,6 +95,8 @@ listview->onselection = args->onselection; listview->onselectiondata = args->onselectiondata; + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; if(var) { UiList *list = var->value; list->obj = listview; @@ -98,6 +104,23 @@ list->getselection = ui_listview_getselection; list->setselection = ui_listview_setselection; ui_listview_update(list, 0); + } else if(static_elements && static_nelm > 0) { + XmStringTable items = calloc(static_nelm, sizeof(XmString)); + for(int i=0;iwidget, + XmNitems, items, + XmNitemCount, + static_nelm, + NULL); + for (int i=0;igetvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; } XtAddCallback( @@ -114,19 +137,36 @@ XtAddCallback( widget, XmNextendedSelectionCallback, - (XtCallbackProc)ui_listview_selection, + (XtCallbackProc)ui_listview_selection_changed, listview); XtAddCallback( widget, XmNsingleSelectionCallback, - (XtCallbackProc)ui_listview_selection, + (XtCallbackProc)ui_listview_selection_changed, listview); return widget; } +void ui_listview_select(UIWIDGET listview, int index) { + XmListDeselectAllItems(listview); + XmListSelectPos(listview, index+1, False); +} + +int ui_listview_selection(UIWIDGET listview) { + int *selpositions = NULL; + int numpos = 0; + XtVaGetValues(listview, XmNselectedPositions, &selpositions, XmNselectedPositionCount, &numpos, NULL); + return numpos > 0 ? selpositions[0] : -1; +} + void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) { - // TODO + ui_listselection_free(listview->current_selection); + if(listview->model) { + ui_model_remove_observer(listview->model, listview); + ui_model_unref(listview->model); + } + free(listview); } static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) { @@ -144,8 +184,12 @@ UiListSelection sel = { cb->selected_item_count, NULL }; if(sel.count > 0) { sel.rows = calloc(sel.count, sizeof(int)); - for(int i=0;iselected_item_positions[i]-1; + if(sel.count == 1) { + sel.rows[0] = cb->item_position-1; + } else if(cb->selected_item_positions) { + for(int i=0;iselected_item_positions[i]-1; + } } } free(listview->current_selection.rows); @@ -159,7 +203,7 @@ } } -void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb) { +void ui_listview_selection_changed(Widget w, UiListView *listview, XmListCallbackStruct *cb) { listview_save_selection(listview, cb); if(listview->onselection) { list_callback(listview->obj, listview->current_selection, listview->onselection, listview->onselectiondata); @@ -209,10 +253,18 @@ UiListSelection ui_listview_getselection(UiList *list) { UiListView *listview = list->obj; - UiListSelection sel = { listview->current_selection.count, NULL }; - if(sel.count > 0) { - sel.rows = calloc(sel.count, sizeof(int)); - memcpy(sel.rows, listview->current_selection.rows, sel.count*sizeof(int)); + UiListSelection sel = { 0, NULL }; + int *selpositions = NULL; + int numpos = 0; + XtVaGetValues(listview->widget, XmNselectedPositions, &selpositions, XmNselectedPositionCount, &numpos, NULL); + if(numpos > 0) { + sel.rows = calloc(numpos, sizeof(int)); + sel.count = numpos; + memcpy(sel.rows, selpositions, numpos*sizeof(int)); + // motif selected positions start at index 1 -> translate positions + for(int i=0;ionselection = args->onselection; listview->onselectiondata = args->onselectiondata; + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; if(var) { UiList *list = var->value; list->obj = listview; list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->setselection = ui_listview_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; ui_listview_update(list, 0); + } else if(static_elements && static_nelm > 0) { + XmStringTable items = calloc(static_nelm, sizeof(XmString)); + for(int i=0;iwidget, + XmNitems, items, + XmNitemCount, + static_nelm, + NULL); + for (int i=0;igetvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; } XtAddCallback( @@ -312,8 +383,39 @@ XtAddCallback( widget, XmNselectionCallback, - (XtCallbackProc)ui_dropdown_selection, + (XtCallbackProc)ui_dropdown_selection_changed, listview); return widget; } + +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { + UiListView *listview = list->obj; + if(selection.count > 0) { + XtVaSetValues(listview->widget, XmNselectedPosition, selection.rows[0], NULL); + } else { + XtVaSetValues(listview->widget, XmNselectedPosition, 0, NULL); + } +} + +UiListSelection ui_dropdown_getselection(UiList *list) { + UiListView *listview = list->obj; + int pos = -1; + XtVaGetValues(listview->widget, XmNselectedPosition, &pos, NULL); + UiListSelection sel = { 0, NULL }; + if(pos >= 0) { + sel.rows = malloc(sizeof(int)); + sel.rows[0] = pos; + sel.count = 1; + } + return sel; +} + +void ui_dropdown_select(UIWIDGET dropdown, int index) { + XtVaSetValues(dropdown, XmNselectedPosition, index, NULL); +} + +int ui_dropdown_selection(UIWIDGET dropdown) { + int pos = -1; + XtVaGetValues(dropdown, XmNselectedPosition, &pos, NULL); +} diff -r 3da24640513a -r e57ca2747782 ui/motif/list.h --- a/ui/motif/list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -30,7 +30,7 @@ #define LIST_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../common/context.h" #ifdef __cplusplus @@ -63,12 +63,15 @@ void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d); void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb); -void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb); +void ui_listview_selection_changed(Widget w, UiListView *listview, XmListCallbackStruct *cb); void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); void ui_listview_setselection(UiList *list, UiListSelection selection); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_dropdown_getselection(UiList *list); + void* ui_strmodel_getvalue(void *elm, int column); #ifdef __cplusplus diff -r 3da24640513a -r e57ca2747782 ui/motif/menu.c --- a/ui/motif/menu.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/menu.c Sat Dec 13 15:58:58 2025 +0100 @@ -33,7 +33,6 @@ #include "menu.h" #include "button.h" #include "toolkit.h" -#include "stock.h" #include "container.h" #include "../common/context.h" #include "../common/menu.h" @@ -55,10 +54,10 @@ /* UI_MENU_SEPARATOR */ add_menuseparator_widget }; -void ui_create_menubar(UiObject *obj, Widget window) { +Widget ui_create_menubar(UiObject *obj, Widget window) { UiMenu *menus_begin = uic_get_menu_list(); if(!menus_begin) { - return; + return NULL; } Widget menubar = XmCreateMenuBar(window, "menubar", NULL, 0); @@ -70,6 +69,8 @@ add_menu_widget(menubar, 0, &menu->item, obj); ls = (UiMenu*)ls->item.next; } + + return menubar; } void ui_add_menu_items(Widget parent, int i, UiMenu *menu, UiObject *obj) { @@ -93,16 +94,15 @@ XtSetArg(args[n], XmNlabelString, s); n++; } - Widget submenu = XmVaCreateSimplePulldownMenu(parent, "menu_pulldown", i, NULL, NULL); + Widget submenu = XmCreatePulldownMenu(parent, "menu_pulldown", NULL, 0); XtSetArg(args[n], XmNsubMenuId, submenu); n++; - Widget menuItem = XtCreateManagedWidget( + (void)XtCreateManagedWidget( "menuitem", xmCascadeButtonWidgetClass, parent, args, n); - if(s) { XmStringFree(s); } @@ -145,11 +145,11 @@ XtAddCallback( mitem, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } - ui_set_widget_groups(obj->ctx, mitem, it->groups); + ui_set_widget_groups(obj->ctx, mitem, it->states); } void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { @@ -181,7 +181,7 @@ ui_bind_togglebutton(obj, checkbox, it->varname, NULL, it->callback, it->userdata, 0); - ui_set_widget_groups(obj->ctx, checkbox, it->groups); + ui_set_widget_groups(obj->ctx, checkbox, it->states); } void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj) { @@ -337,7 +337,7 @@ XtAddCallback( mitem, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } diff -r 3da24640513a -r e57ca2747782 ui/motif/menu.h --- a/ui/motif/menu.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/menu.h Sat Dec 13 15:58:58 2025 +0100 @@ -52,7 +52,7 @@ typedef void(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*); -void ui_create_menubar(UiObject *obj, Widget window); +Widget ui_create_menubar(UiObject *obj, Widget window); void ui_add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); diff -r 3da24640513a -r e57ca2747782 ui/motif/objs.mk --- a/ui/motif/objs.mk Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/objs.mk Sat Dec 13 15:58:58 2025 +0100 @@ -30,7 +30,6 @@ MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR) MOTIFOBJ = toolkit.o -MOTIFOBJ += stock.o MOTIFOBJ += window.o MOTIFOBJ += widget.o MOTIFOBJ += container.o @@ -39,12 +38,15 @@ MOTIFOBJ += button.o MOTIFOBJ += label.o MOTIFOBJ += text.o +MOTIFOBJ += pathbar.o MOTIFOBJ += list.o MOTIFOBJ += graphics.o MOTIFOBJ += range.o MOTIFOBJ += dnd.o MOTIFOBJ += image.o MOTIFOBJ += Grid.o +MOTIFOBJ += entry.o +MOTIFOBJ += Fsb.o TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%) TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c) diff -r 3da24640513a -r e57ca2747782 ui/motif/pathbar.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/pathbar.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,439 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pathbar.h" + +#include +#include + + + +void pathbar_resize(Widget w, PathBar *p, XtPointer d) +{ + Dimension width, height; + XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); + + Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); + + Dimension maxHeight = 0; + + /* get width/height from all widgets */ + Dimension pathWidth = 0; + for(int i=0;inumSegments;i++) { + Dimension segWidth; + Dimension segHeight; + XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); + segW[i] = segWidth; + pathWidth += segWidth; + if(segHeight > maxHeight) { + maxHeight = segHeight; + } + } + Dimension tfHeight; + XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); + if(tfHeight > maxHeight) { + maxHeight = tfHeight; + } + + Boolean arrows = False; + if(pathWidth + 10 > width) { + arrows = True; + pathWidth += p->lw + p->rw; + } + + /* calc max visible widgets */ + int start = 0; + if(arrows) { + Dimension vis = p->lw+p->rw; + for(int i=p->numSegments;i>0;i--) { + Dimension segWidth = segW[i-1]; + if(vis + segWidth + 10 > width) { + start = i; + arrows = True; + break; + } + vis += segWidth; + } + } else { + p->shift = 0; + } + + int leftShift = 0; + if(p->shift < 0) { + if(start + p->shift < 0) { + leftShift = start; + start = 0; + p->shift = -leftShift; + } else { + leftShift = -p->shift; /* negative shift */ + start += p->shift; + } + } + + int x = 0; + if(arrows) { + XtManageChild(p->left); + XtManageChild(p->right); + x = p->lw; + } else { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + } + + for(int i=0;inumSegments;i++) { + if(i >= start && i < p->numSegments - leftShift && !p->input) { + XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + x += segW[i]; + XtManageChild(p->pathSegments[i]); + } else { + XtUnmanageChild(p->pathSegments[i]); + } + } + + if(arrows) { + XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); + XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + } + + free(segW); + + Dimension rw, rh; + XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); + + XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); +} + +static void pathbarActivateTF(PathBar *p) +{ + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); + XtManageChild(p->textfield); + p->input = 1; + + XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); + + pathbar_resize(p->widget, p, NULL); +} + +void PathBarActivateTextfield(PathBar *p) +{ + p->focus = 1; + pathbarActivateTF(p); +} + +void pathbar_input(Widget w, PathBar *p, XtPointer c) +{ + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + p->focus = 0; + pathbarActivateTF(p); + } + } +} + +void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) +{ + if(--p->focus < 0) { + p->input = False; + XtUnmanageChild(p->textfield); + } +} + +static cxmutstr concat_path_s(cxstring base, cxstring path) { + if(!path.ptr) { + path = CX_STR(""); + } + + int add_separator = 0; + if(base.length != 0 && base.ptr[base.length-1] == '/') { + if(path.ptr[0] == '/') { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != '/') { + add_separator = 1; + } + } + + cxmutstr url; + if(add_separator) { + url = cx_strcat(3, base, CX_STR("/"), path); + } else { + url = cx_strcat(2, base, path); + } + + return url; +} + +char* pathbar_concat_path(const char *path1, const char *path2) { + return concat_path_s(cx_str(path1), cx_str(path2)).ptr; +} + +void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) +{ + char *newpath = XNETextGetString(p->textfield); + if(newpath) { + if(newpath[0] == '~') { + char *p = newpath+1; + char *home = getenv("HOME"); + char *cp = pathbar_concat_path(home, p); + XtFree(newpath); + newpath = cp; + } else if(newpath[0] != '/') { + char curdir[2048]; + curdir[0] = 0; + getcwd(curdir, 2048); + char *cp = pathbar_concat_path(curdir, newpath); + XtFree(newpath); + newpath = cp; + } + + /* update path */ + PathBarSetPath(p, newpath); + if(p->updateDir) { + p->updateDir(p->updateDirData, newpath, -1); + } + XtFree(newpath); + + /* hide textfield and show path as buttons */ + XtUnmanageChild(p->textfield); + pathbar_resize(p->widget, p, NULL); + + if(p->focus_widget) { + XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); + } + } +} + +void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) +{ + p->shift--; + pathbar_resize(p->widget, p, NULL); +} + +void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) +{ + if(p->shift < 0) { + p->shift++; + } + pathbar_resize(p->widget, p, NULL); +} + +static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *pb = data; + if(event->type == KeyReleaseMask) { + if(event->xkey.keycode == 9) { + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + *dispatch = False; + } else if(event->xkey.keycode == 36) { + pathbar_pathinput(pb->textfield, pb, NULL); + *dispatch = False; + } + } +} + +PathBar* CreatePathBar(Widget parent, ArgList args, int n) +{ + PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); + bar->path = NULL; + bar->updateDir = NULL; + bar->updateDirData = NULL; + + bar->focus_widget = NULL; + + bar->getpathelm = NULL; + bar->getpathelmdata = NULL; + bar->current_pathelms = NULL; + + bar->shift = 0; + + XtSetArg(args[n], XmNmarginWidth, 0); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); + XtAddCallback( + bar->widget, + XmNresizeCallback, + (XtCallbackProc)pathbar_resize, + bar); + XtAddCallback( + bar->widget, + XmNinputCallback, + (XtCallbackProc)pathbar_input, + bar); + + Arg a[4]; + XtSetArg(a[0], XmNshadowThickness, 0); + XtSetArg(a[1], XmNx, 0); + XtSetArg(a[2], XmNy, 0); + bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); + bar->input = 0; + XtAddCallback( + bar->textfield, + XmNlosingFocusCallback, + (XtCallbackProc)pathbar_losingfocus, + bar); + XtAddCallback(bar->textfield, XmNactivateCallback, + (XtCallbackProc)pathbar_pathinput, bar); + XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); + + XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); + bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); + bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtAddCallback( + bar->left, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_left, + bar); + XtAddCallback( + bar->right, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_right, + bar); + + Pixel bg; + XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); + XtVaSetValues(bar->widget, XmNbackground, bg, NULL); + + XtManageChild(bar->left); + XtManageChild(bar->right); + + XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); + XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); + + bar->segmentAlloc = 16; + bar->numSegments = 0; + bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); + + bar->selection = 0; + + return bar; +} + +void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) +{ + XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); + + int i; + for(i=0;inumSegments;i++) { + if(bar->pathSegments[i] == w) { + bar->selection = i; + XmToggleButtonSetState(w, True, False); + break; + } + } + + UiPathElm elm = bar->current_pathelms[i]; + cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); + if(bar->updateDir) { + XNETextSetString(bar->textfield, path.ptr); + bar->updateDir(bar->updateDirData, path.ptr, i); + } + free(path.ptr); +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;ipath) { + free(bar->path); + } + bar->path = strdup(path); + + for(int i=0;inumSegments;i++) { + XtDestroyWidget(bar->pathSegments[i]); + } + XtUnmanageChild(bar->textfield); + XtManageChild(bar->left); + XtManageChild(bar->right); + bar->input = False; + + Arg args[4]; + XmString str; + + bar->numSegments = 0; + + ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); + size_t nelm = 0; + UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); + if (!path_elm) { + return; + } + bar->current_pathelms = path_elm; + bar->numSegments = nelm; + bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); + + for(int i=0;iwidget, "pbbutton", args, 3); + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + + bar->pathSegments[i] = button; + } + + bar->selection = bar->numSegments-1; + XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); + + XNETextSetString(bar->textfield, (char*)path); + XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); + + pathbar_resize(bar->widget, bar, NULL); +} + +void PathBarDestroy(PathBar *pathbar) { + if(pathbar->path) { + XtFree(pathbar->path); + } + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +} diff -r 3da24640513a -r e57ca2747782 ui/motif/pathbar.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/pathbar.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,95 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PATHBAR_H +#define PATHBAR_H + +#include + +#include "../ui/text.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) +#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) +#define XNETextGetString(widget) XmTextFieldGetString(widget) +#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) +#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) +#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) + +typedef void(*updatedir_callback)(void*,char*,int); + +typedef struct PathBar { + Widget widget; + Widget textfield; + + Widget focus_widget; + + Widget left; + Widget right; + Dimension lw; + Dimension rw; + + int shift; + + UiPathElm *current_pathelms; + Widget *pathSegments; + size_t numSegments; + size_t segmentAlloc; + + char *path; + int selection; + Boolean input; + + int focus; + + Boolean disableResize; + + updatedir_callback updateDir; + void *updateDirData; + + ui_pathelm_func getpathelm; + void *getpathelmdata; +} PathBar; + +PathBar* CreatePathBar(Widget parent, ArgList args, int n); +void PathBarSetPath(PathBar *bar, const char *path); +void PathBarDestroy(PathBar *bar); + +void pathbar_resize(Widget w, PathBar *p, XtPointer d); + +char* pathbar_concat_path(const char *path1, const char *path2); + +#ifdef __cplusplus +} +#endif + +#endif /* PATHBAR_H */ + diff -r 3da24640513a -r e57ca2747782 ui/motif/text.c --- a/ui/motif/text.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/text.c Sat Dec 13 15:58:58 2025 +0100 @@ -32,6 +32,9 @@ #include "text.h" #include "container.h" +#include "pathbar.h" + +#include "../common/utils.h" #include @@ -210,9 +213,9 @@ int sel = left < right ? 1 : 0; if(sel != textarea->last_selection_state) { if(sel) { - ui_set_group(textarea->obj->ctx, UI_GROUP_SELECTION); + ui_set_state(textarea->obj->ctx, UI_GROUP_SELECTION); } else { - ui_unset_group(textarea->obj->ctx, UI_GROUP_SELECTION); + ui_unset_state(textarea->obj->ctx, UI_GROUP_SELECTION); } } textarea->last_selection_state = sel; @@ -405,10 +408,20 @@ XtManageChild(textfield); ui_container_add(ctn, textfield); - ui_set_widget_groups(obj->ctx, textfield, args->groups); + ui_set_widget_groups(obj->ctx, textfield, args->states); + + UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt)); + memset(eventdata, 0, sizeof(UiEventDataExt)); + eventdata->obj = obj; + eventdata->callback = args->onactivate; + eventdata->userdata = args->onactivatedata; + eventdata->callback2 = args->onchange; + eventdata->userdata2 = args->onchangedata; UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); if(var) { + eventdata->customdata0 = var; + UiString *value = (UiString*)var->value; value->obj = textfield; value->get = ui_textfield_get; @@ -419,9 +432,54 @@ } } + XtAddCallback( + textfield, + XmNactivateCallback, + (XtCallbackProc)ui_textfield_activate, + eventdata); + XtAddCallback( + textfield, + XmNvalueChangedCallback, + (XtCallbackProc)ui_textfield_value_changed, + eventdata); + XtAddCallback( + textfield, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_data, + eventdata); + return textfield; } +static void textfield_event(UiEventDataExt *eventdata, ui_callback callback, void *userdata) { + if(callback) { + UiVar *var = eventdata->customdata0; + UiString *value = var ? var->value : NULL; + + UiEvent e; + e.obj = eventdata->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = value; + e.eventdatatype = value ? UI_EVENT_DATA_TEXT_VALUE : 0; + e.intval = 0; + e.set = ui_get_setop(); + callback(&e, userdata); + } +} + +void ui_textfield_activate(Widget widget, XtPointer ud, XtPointer cb) { + UiEventDataExt *eventdata = ud; + textfield_event(ud, eventdata->callback, eventdata->userdata); +} + +void ui_textfield_value_changed(Widget widget, XtPointer ud, XtPointer cb) { + UiEventDataExt *eventdata = ud; + if(ui_onchange_events_is_enabled()) { + textfield_event(ud, eventdata->callback2, eventdata->userdata2); + } +} + UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) { return create_textfield(obj, args, FALSE, FALSE); } @@ -453,459 +511,6 @@ } - - - -/* -------------------- path bar -------------------- */ - -#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) -#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) -#define XNETextGetString(widget) XmTextFieldGetString(widget) -#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) -#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) -#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) - -typedef void(*updatedir_callback)(void*,char*,int); - -typedef struct PathBar { - Widget widget; - Widget textfield; - - Widget focus_widget; - - Widget left; - Widget right; - Dimension lw; - Dimension rw; - - int shift; - - UiPathElm *current_pathelms; - Widget *pathSegments; - size_t numSegments; - size_t segmentAlloc; - - char *path; - int selection; - Boolean input; - - int focus; - - updatedir_callback updateDir; - void *updateDirData; - - ui_pathelm_func getpathelm; - void *getpathelmdata; -} PathBar; - -void PathBarSetPath(PathBar *bar, const char *path); - -void pathbar_resize(Widget w, PathBar *p, XtPointer d) -{ - Dimension width, height; - XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); - - Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); - - Dimension maxHeight = 0; - - /* get width/height from all widgets */ - Dimension pathWidth = 0; - for(int i=0;inumSegments;i++) { - Dimension segWidth; - Dimension segHeight; - XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); - segW[i] = segWidth; - pathWidth += segWidth; - if(segHeight > maxHeight) { - maxHeight = segHeight; - } - } - Dimension tfHeight; - XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); - if(tfHeight > maxHeight) { - maxHeight = tfHeight; - } - - Boolean arrows = False; - if(pathWidth + 10 > width) { - arrows = True; - pathWidth += p->lw + p->rw; - } - - /* calc max visible widgets */ - int start = 0; - if(arrows) { - Dimension vis = p->lw+p->rw; - for(int i=p->numSegments;i>0;i--) { - Dimension segWidth = segW[i-1]; - if(vis + segWidth + 10 > width) { - start = i; - arrows = True; - break; - } - vis += segWidth; - } - } else { - p->shift = 0; - } - - int leftShift = 0; - if(p->shift < 0) { - if(start + p->shift < 0) { - leftShift = start; - start = 0; - p->shift = -leftShift; - } else { - leftShift = -p->shift; /* negative shift */ - start += p->shift; - } - } - - int x = 0; - if(arrows) { - XtManageChild(p->left); - XtManageChild(p->right); - x = p->lw; - } else { - XtUnmanageChild(p->left); - XtUnmanageChild(p->right); - } - - for(int i=0;inumSegments;i++) { - if(i >= start && i < p->numSegments - leftShift && !p->input) { - XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); - x += segW[i]; - XtManageChild(p->pathSegments[i]); - } else { - XtUnmanageChild(p->pathSegments[i]); - } - } - - if(arrows) { - XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); - XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); - } - - free(segW); - - Dimension rw, rh; - XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); - - XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); -} - -static void pathbarActivateTF(PathBar *p) -{ - XtUnmanageChild(p->left); - XtUnmanageChild(p->right); - XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); - XtManageChild(p->textfield); - p->input = 1; - - XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); - - pathbar_resize(p->widget, p, NULL); -} - -void PathBarActivateTextfield(PathBar *p) -{ - p->focus = 1; - pathbarActivateTF(p); -} - -void pathbar_input(Widget w, PathBar *p, XtPointer c) -{ - XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; - XEvent *xevent = cbs->event; - - if (cbs->reason == XmCR_INPUT) { - if (xevent->xany.type == ButtonPress) { - p->focus = 0; - pathbarActivateTF(p); - } - } -} - -void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) -{ - if(--p->focus < 0) { - p->input = False; - XtUnmanageChild(p->textfield); - } -} - -static cxmutstr concat_path_s(cxstring base, cxstring path) { - if(!path.ptr) { - path = CX_STR(""); - } - - int add_separator = 0; - if(base.length != 0 && base.ptr[base.length-1] == '/') { - if(path.ptr[0] == '/') { - base.length--; - } - } else { - if(path.length == 0 || path.ptr[0] != '/') { - add_separator = 1; - } - } - - cxmutstr url; - if(add_separator) { - url = cx_strcat(3, base, CX_STR("/"), path); - } else { - url = cx_strcat(2, base, path); - } - - return url; -} - -static char* ConcatPath(const char *path1, const char *path2) { - return concat_path_s(cx_str(path1), cx_str(path2)).ptr; -} - -void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) -{ - char *newpath = XNETextGetString(p->textfield); - if(newpath) { - if(newpath[0] == '~') { - char *p = newpath+1; - char *home = getenv("HOME"); - char *cp = ConcatPath(home, p); - XtFree(newpath); - newpath = cp; - } else if(newpath[0] != '/') { - char curdir[2048]; - curdir[0] = 0; - getcwd(curdir, 2048); - char *cp = ConcatPath(curdir, newpath); - XtFree(newpath); - newpath = cp; - } - - /* update path */ - PathBarSetPath(p, newpath); - if(p->updateDir) { - p->updateDir(p->updateDirData, newpath, -1); - } - XtFree(newpath); - - /* hide textfield and show path as buttons */ - XtUnmanageChild(p->textfield); - pathbar_resize(p->widget, p, NULL); - - if(p->focus_widget) { - XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); - } - } -} - -void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) -{ - p->shift--; - pathbar_resize(p->widget, p, NULL); -} - -void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) -{ - if(p->shift < 0) { - p->shift++; - } - pathbar_resize(p->widget, p, NULL); -} - -static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { - PathBar *pb = data; - if(event->type == KeyReleaseMask) { - if(event->xkey.keycode == 9) { - XtUnmanageChild(pb->textfield); - pathbar_resize(pb->widget, pb, NULL); - *dispatch = False; - } else if(event->xkey.keycode == 36) { - pathbar_pathinput(pb->textfield, pb, NULL); - *dispatch = False; - } - } -} - -PathBar* CreatePathBar(Widget parent, ArgList args, int n) -{ - PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); - bar->path = NULL; - bar->updateDir = NULL; - bar->updateDirData = NULL; - - bar->focus_widget = NULL; - - bar->getpathelm = NULL; - bar->getpathelmdata = NULL; - bar->current_pathelms = NULL; - - bar->shift = 0; - - XtSetArg(args[n], XmNmarginWidth, 0); n++; - XtSetArg(args[n], XmNmarginHeight, 0); n++; - bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); - XtAddCallback( - bar->widget, - XmNresizeCallback, - (XtCallbackProc)pathbar_resize, - bar); - XtAddCallback( - bar->widget, - XmNinputCallback, - (XtCallbackProc)pathbar_input, - bar); - - Arg a[4]; - XtSetArg(a[0], XmNshadowThickness, 0); - XtSetArg(a[1], XmNx, 0); - XtSetArg(a[2], XmNy, 0); - bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); - bar->input = 0; - XtAddCallback( - bar->textfield, - XmNlosingFocusCallback, - (XtCallbackProc)pathbar_losingfocus, - bar); - XtAddCallback(bar->textfield, XmNactivateCallback, - (XtCallbackProc)pathbar_pathinput, bar); - XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); - - XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); - bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); - XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); - bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); - XtAddCallback( - bar->left, - XmNactivateCallback, - (XtCallbackProc)pathbar_shift_left, - bar); - XtAddCallback( - bar->right, - XmNactivateCallback, - (XtCallbackProc)pathbar_shift_right, - bar); - - Pixel bg; - XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); - XtVaSetValues(bar->widget, XmNbackground, bg, NULL); - - XtManageChild(bar->left); - XtManageChild(bar->right); - - XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); - XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); - - bar->segmentAlloc = 16; - bar->numSegments = 0; - bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); - - bar->selection = 0; - - return bar; -} - -void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) -{ - XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); - - int i; - for(i=0;inumSegments;i++) { - if(bar->pathSegments[i] == w) { - bar->selection = i; - XmToggleButtonSetState(w, True, False); - break; - } - } - - UiPathElm elm = bar->current_pathelms[i]; - cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); - if(bar->updateDir) { - XNETextSetString(bar->textfield, path.ptr); - bar->updateDir(bar->updateDirData, path.ptr, i); - } - free(path.ptr); -} - -static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { - for(int i=0;ipath) { - free(bar->path); - } - bar->path = strdup(path); - - for(int i=0;inumSegments;i++) { - XtDestroyWidget(bar->pathSegments[i]); - } - XtUnmanageChild(bar->textfield); - XtManageChild(bar->left); - XtManageChild(bar->right); - bar->input = False; - - Arg args[4]; - XmString str; - - bar->numSegments = 0; - - ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); - size_t nelm = 0; - UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); - if (!path_elm) { - return; - } - bar->current_pathelms = path_elm; - bar->numSegments = nelm; - bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); - - for(int i=0;iwidget, "pbbutton", args, 3); - XtAddCallback( - button, - XmNvalueChangedCallback, - (XtCallbackProc)PathBarChangeDir, - bar); - XmStringFree(str); - - bar->pathSegments[i] = button; - } - - bar->selection = bar->numSegments-1; - XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); - - XNETextSetString(bar->textfield, (char*)path); - XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); - - pathbar_resize(bar->widget, bar, NULL); -} - -void PathBarDestroy(PathBar *pathbar) { - if(pathbar->path) { - XtFree(pathbar->path); - } - XtFree((void*)pathbar->pathSegments); - XtFree((void*)pathbar); -} - - /* ---------------------------- Path Text Field ---------------------------- */ static void destroy_pathbar(Widget w, XtPointer *data, XtPointer d) { @@ -915,47 +520,6 @@ XtFree((void*)pathbar); } -// 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 void pathbar_activate(void *data, char *path, int index) { UiEventData *event = data; UiEvent evt; @@ -981,7 +545,7 @@ PathBar *pathbar = CreatePathBar(parent, xargs, n); if(!args->getpathelm) { - pathbar->getpathelm= default_pathelm_func; + pathbar->getpathelm= ui_default_pathelm_func; } else { pathbar->getpathelm = args->getpathelm; pathbar->getpathelmdata = args->getpathelmdata; @@ -1018,7 +582,7 @@ XtAddCallback( pathbar->widget, XmNdestroyCallback, - (XtCallbackProc)ui_destroy_eventdata, + (XtCallbackProc)ui_destroy_data, eventdata); } diff -r 3da24640513a -r e57ca2747782 ui/motif/text.h --- a/ui/motif/text.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/text.h Sat Dec 13 15:58:58 2025 +0100 @@ -86,6 +86,9 @@ int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen); void ui_free_textbuf_op(UiTextBufOp *op); +void ui_textfield_activate(Widget widget, XtPointer ud, XtPointer cb); +void ui_textfield_value_changed(Widget widget, XtPointer ud, XtPointer cb); + char* ui_textfield_get(UiString *str); void ui_textfield_set(UiString *str, const char *value); diff -r 3da24640513a -r e57ca2747782 ui/motif/toolbar.c --- a/ui/motif/toolbar.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/toolbar.c Sat Dec 13 15:58:58 2025 +0100 @@ -33,7 +33,6 @@ #include "toolbar.h" #include "button.h" -#include "stock.h" #include "list.h" #include diff -r 3da24640513a -r e57ca2747782 ui/motif/toolkit.c --- a/ui/motif/toolkit.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/toolkit.c Sat Dec 13 15:58:58 2025 +0100 @@ -34,11 +34,11 @@ #include "toolkit.h" #include "toolbar.h" #include "container.h" -#include "stock.h" #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/document.h" #include "../common/properties.h" +#include "../common/app.h" #include #include @@ -49,13 +49,6 @@ static Widget active_window; static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static ui_callback appclose_fnc; static void *appclose_udata; @@ -84,11 +77,17 @@ NULL }; +static String *fallback_resources = fallback; + void input_proc(XtPointer data, int *source, XtInputId *iid) { void *ptr; read(event_pipe[0], &ptr, sizeof(void*)); } +void ui_motif_set_fallback_resources(String *fallbackres) { + fallback_resources = fallbackres; +} + void ui_init(const char *appname, int argc, char **argv) { application_name = appname; uic_init_global_context(); @@ -96,7 +95,7 @@ XtToolkitInitialize(); XtSetLanguageProc(NULL, NULL, NULL); app = XtCreateApplicationContext(); - XtAppSetFallbackResources(app, fallback); + XtAppSetFallbackResources(app, fallback_resources); display = XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv); @@ -128,40 +127,21 @@ return display; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); XtAppMainLoop(app); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); if(exit_on_shutdown) { exit(0); } } -void ui_exit_mainloop() { +void ui_app_quit() { XtAppSetExitFlag(app); } @@ -174,7 +154,7 @@ } void ui_show(UiObject *obj) { - uic_check_group_widgets(obj->ctx); + uic_check_state_widgets(obj->ctx); if(!XtIsRealized(obj->widget)) { XtRealizeWidget(obj->widget); obj->ref++; @@ -220,6 +200,15 @@ return TRUE; } +static Boolean ui_mainthread_job(void *data) { + UiJob *job = data; + if(job->job_func) { + job->job_func(job->job_data); + } + free(job); + return TRUE; +} + static void* ui_jobthread(void *data) { UiJob *job = data; int result = job->job_func(job->job_data); @@ -231,6 +220,15 @@ return NULL; } +void ui_call_mainthread(ui_threadfunc tf, void* td) { + UiJob *job = malloc(sizeof(UiJob)); + memset(job, 0, sizeof(UiJob)); + job->job_func = tf; + job->job_data = td; + write(event_pipe[1], &job, sizeof(void*)); // hack + XtAppAddWorkProc(app, ui_mainthread_job, job); +} + void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) { UiJob *job = malloc(sizeof(UiJob)); job->obj = obj; @@ -325,7 +323,7 @@ 4); } -void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) { +void ui_destroy_data(Widget w, XtPointer data, XtPointer d) { free(data); } @@ -333,13 +331,13 @@ if(!groups) { return; } - size_t ngroups = uic_group_array_size(groups); + size_t ngroups = uic_state_array_size(groups); ui_set_widget_ngroups(ctx, widget, groups, ngroups); } void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups) { if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); ui_set_enabled(widget, FALSE); } } diff -r 3da24640513a -r e57ca2747782 ui/motif/toolkit.h --- a/ui/motif/toolkit.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/toolkit.h Sat Dec 13 15:58:58 2025 +0100 @@ -82,10 +82,9 @@ typedef enum UiOrientation UiOrientation; enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; -void ui_exit_mainloop(); - XtAppContext ui_motif_get_app(void); Display* ui_motif_get_display(void); +void ui_motif_set_fallback_resources(String *fallback); void ui_set_active_window(Widget w); Widget ui_get_active_window(); @@ -93,7 +92,7 @@ void ui_secondary_event_loop(int *loop); void ui_window_dark_theme(Display *dp, Window window); -void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d); +void ui_destroy_data(Widget w, XtPointer data, XtPointer d); void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) ; void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups); diff -r 3da24640513a -r e57ca2747782 ui/motif/window.c --- a/ui/motif/window.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/window.c Sat Dec 13 15:58:58 2025 +0100 @@ -28,15 +28,22 @@ #include #include +#include +#include +#include + +#include "window.h" #include "toolkit.h" #include "menu.h" #include "toolbar.h" #include "container.h" -#include "../ui/window.h" +#include "pathbar.h" #include "../common/context.h" +#include "../common/utils.h" #include "Grid.h" +#include "Fsb.h" #include @@ -50,7 +57,7 @@ uic_object_destroy(obj); nwindows--; if(nwindows == 0) { - ui_exit_mainloop(); + ui_app_quit(); } } @@ -67,20 +74,28 @@ } -static UiObject* create_window(const char *title, void *window_data, Boolean simple) { +static UiObject* create_window(const char *title, Boolean simple) { CxMempool *mp = cxMempoolCreateSimple(256); const CxAllocator *a = mp->allocator; UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; obj->destroy = ui_window_widget_destroy; + int window_width = window_default_width; + int window_height = window_default_height; + if(!simple) { + ui_get_window_default_width(&window_width, &window_height); + } + + UiMotifAppWindow *appwindow = cxZalloc(a, sizeof(UiMotifAppWindow)); + ui_object_set(obj, "ui_motif_app_window", appwindow); + Arg args[16]; int n = 0; XtSetArg(args[n], XmNtitle, title); n++; XtSetArg(args[n], XmNminWidth, 100); n++; XtSetArg(args[n], XmNminHeight, 50); n++; - XtSetArg(args[n], XmNwidth, window_default_width); n++; - XtSetArg(args[n], XmNheight, window_default_height); n++; + XtSetArg(args[n], XmNwidth, window_width); n++; + XtSetArg(args[n], XmNheight, window_height); n++; Widget toplevel = XtAppCreateShell( ui_appname(), @@ -110,7 +125,7 @@ // menu if(!simple) { - ui_create_menubar(obj, window); + appwindow->menubar = ui_create_menubar(obj, window); } // content frame @@ -135,10 +150,161 @@ return obj; } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE); +UiObject* ui_window(const char *title) { + return create_window(title, FALSE); +} + +UiObject* ui_simple_window(const char *title) { + return create_window(title, TRUE); +} + +void ui_window_size(UiObject *obj, int width, int height) { + XtVaSetValues(obj->widget, XmNwidth, width, XmNheight, height, NULL); +} + +void ui_window_default_size(int width, int height) { + window_default_width = width; + window_default_height = height; +} + +void ui_window_menubar_set_visible(UiObject *obj, UiBool visible) { + UiMotifAppWindow *window = ui_object_get(obj, "ui_motif_app_window"); + if(window) { + if(window->menubar) { + ui_set_visible(window->menubar, visible); + } + } else { + fprintf(stderr, "Error: obj is not an application window\n"); + } +} + +static Atom net_wm_state; +static Atom net_wm_state_fullscreen; +static int net_wm_atoms_initialized = 0; + +void ui_window_fullscreen(UiObject *obj, UiBool fullscreen) { + Display *dpy = XtDisplay(obj->widget); + + // init net_wm_state atoms + if(!net_wm_atoms_initialized) { + net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False); + net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + net_wm_atoms_initialized = 1; + } + + XEvent ev; + memset(&ev, 0, sizeof(XEvent)); + ev.type = ClientMessage; + ev.xclient.window = XtWindow(obj->widget); + ev.xclient.message_type = net_wm_state; + ev.xclient.format = 32; + ev.xclient.data.l[0] = fullscreen ? 1 : 0; + ev.xclient.data.l[1] = net_wm_state_fullscreen; + ev.xclient.data.l[2] = 0; + XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); +} + +static void filedialog_event(UiEventData *event, int result, UiFileList flist) { + UiEvent evt; + evt.obj = event->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = result; + + evt.eventdata = &flist; + evt.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + if(event->callback) { + event->callback(&evt, event->userdata); + } } -UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE); +static void filedialog_select( + Widget widget, + UiEventData *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + UiFileList flist; + + char *value = NULL; + XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value); + flist.files = &value; + flist.nfiles = 1; + + filedialog_event(data, 1, flist); + + XtFree(value); + + XtUnmanageChild(widget); + XtDestroyWidget(widget); +} + +static void filedialog_cancel( + Widget widget, + UiEventData *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + filedialog_event(data, 0, flist); + + XtUnmanageChild(widget); + XtDestroyWidget(widget); } + +void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + Widget dialog = XnCreateFileSelectionDialog(obj->widget, "dialog", NULL, 0); + + UiEventData *data = malloc(sizeof(UiEventData)); + memset(data, 0, sizeof(UiEventData)); + data->obj = obj; + data->callback = file_selected_callback; + data->userdata = cbdata; + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, data); + //XtAddCallback(dialog, XmNhelpCallback, (XtCallbackProc)filedialog_help, wd); + + XtManageChild(dialog); +} + +void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + Arg args[16]; + int n = 0; + + // Save File Dialog needs this parameter + XtSetArg(args[n], XnNfsbType, FILEDIALOG_SAVE); n++; + char *selectedpath = (char*)name; + if(name) { + if(name[0] != '/') { + char cwd[PATH_MAX]; + if(getcwd(cwd, PATH_MAX)) { + pathbar_concat_path(cwd, name); + } else { + fprintf(stderr, "Error: getcwd failed: %s\n", strerror(errno)); + selectedpath = NULL; + } + } + if(selectedpath) { + XtSetArg(args[n], XnNselectedPath, selectedpath); n++; + } + } + Widget dialog = XnCreateFileSelectionDialog(obj->widget, "dialog", args, n); + + UiEventData *data = malloc(sizeof(UiEventData)); + memset(data, 0, sizeof(UiEventData)); + data->obj = obj; + data->callback = file_selected_callback; + data->userdata = cbdata; + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, data); + //XtAddCallback(dialog, XmNhelpCallback, (XtCallbackProc)filedialog_help, wd); + + XtManageChild(dialog); + + if(selectedpath != name) { + free(selectedpath); + } +} diff -r 3da24640513a -r e57ca2747782 ui/motif/window.h --- a/ui/motif/window.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/motif/window.h Sat Dec 13 15:58:58 2025 +0100 @@ -29,11 +29,18 @@ #ifndef WINDOW_H #define WINDOW_H +#include "../ui/window.h" +#include "../ui/widget.h" + #ifdef __cplusplus extern "C" { #endif + +#define UI_MOTIF_APP_WINDOW 0xabcd - +typedef struct UiMotifAppWindow { + Widget menubar; +} UiMotifAppWindow; #ifdef __cplusplus diff -r 3da24640513a -r e57ca2747782 ui/qt/container.cpp --- a/ui/qt/container.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/container.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -82,7 +82,14 @@ bool fill = layout.fill; if(hasStretchedWidget && fill) { fill = false; - fprintf(stderr, "UiError: container has 2 filled widgets"); + fprintf(stderr, "UiError: container has 2 filled widgets\n"); + } + + if(singleChild) { + fill = true; + if(hasChild) { + fprintf(stderr, "UiError: single child container already has a child\n"); + } } uic_layout_setup_margin(&layout); @@ -103,6 +110,7 @@ hasStretchedWidget = true; } current = widget; + hasChild = true; } UIWIDGET ui_box(UiObject *obj, UiContainerArgs *args, QBoxLayout::Direction dir) { @@ -239,6 +247,64 @@ } +/* ------------------------------ Frame ------------------------------ */ + +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { + UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + bool createBox = true; + bool singleChild = false; + QBoxLayout::Direction dir = QBoxLayout::TopToBottom; + QGroupBox *widget = new QGroupBox(); + if(args->label) { + widget->setTitle(args->label); + } + + switch(args->subcontainer) { + default: break; + case UI_CONTAINER_HBOX: { + dir = QBoxLayout::LeftToRight; + break; + } + case UI_CONTAINER_GRID: { + createBox = false; + break; + } + case UI_CONTAINER_NO_SUB: { + singleChild = true; + } + } + + if(createBox) { + QBoxLayout *box = new QBoxLayout(dir); + widget->setLayout(box); + + UiBoxContainer *container = new UiBoxContainer(box); + ui_obj_add_container(obj, container); + + container->singleChild = singleChild; + } else { + QGridLayout *grid = new QGridLayout(); + widget->setLayout(grid); + + ui_obj_add_container(obj, new UiGridContainer( + grid, + args->padding, + args->columnspacing, + args->rowspacing, + false, + false, + false, + false)); + } + + + ctn->add(widget, layout); + + return widget; +} + /* ---------------------------- UiSidebar ---------------------------- */ UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { diff -r 3da24640513a -r e57ca2747782 ui/qt/container.h --- a/ui/qt/container.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/container.h Sat Dec 13 15:58:58 2025 +0100 @@ -39,6 +39,7 @@ #include #include #include +#include #define ui_obj_container(obj) (UiContainerPrivate*)((UiContainerX*)obj->container_end)->container @@ -54,6 +55,8 @@ public: QBoxLayout *box; bool hasStretchedWidget = false; + bool singleChild = false; + bool hasChild = false; QSpacerItem *space; QBoxLayout::Direction direction; diff -r 3da24640513a -r e57ca2747782 ui/qt/list.cpp --- a/ui/qt/list.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/list.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -32,6 +32,7 @@ #include #include #include +#include extern "C" void* ui_strmodel_getvalue(void *elm, int column) { return column == 0 ? elm : NULL; @@ -46,7 +47,7 @@ return NULL; } -UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); QListView *view = new QListView(); @@ -92,7 +93,7 @@ return view; } -UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); QTreeView *view = new QTreeView(); @@ -141,3 +142,42 @@ return view; } + +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QComboBox *view = new QComboBox(); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); + + ui_getvaluefunc2 getvalue = nullptr; + void *getvaluedata = nullptr; + if(args->getvalue2) { + getvalue = args->getvalue2; + getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = getvalue_wrapper; + getvaluedata = (void*)ui_strmodel_getvalue; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + ListModel *model = new ListModel(obj, view, var, getvalue, getvaluedata); + view->setModel(model); + + if(var) { + UiList *list = (UiList*)var->value; + list->update = ui_listmodel_update; + list->getselection = ui_listmodel_getselection; + list->setselection = ui_listmodel_setselection; + list->obj = model; + } + + model->setActivationCallback(args->onactivate, args->onactivatedata); + model->setSelectionCallback(args->onselection, args->onselectiondata); + + return view; +} diff -r 3da24640513a -r e57ca2747782 ui/qt/list.h --- a/ui/qt/list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -29,7 +29,7 @@ #ifndef TREE_H #define TREE_H -#include "../ui/tree.h" +#include "../ui/list.h" #include "model.h" #include diff -r 3da24640513a -r e57ca2747782 ui/qt/menu.cpp --- a/ui/qt/menu.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/menu.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -38,7 +38,6 @@ #include "../common/menu.h" #include "../ui/properties.h" #include "../ui/window.h" -#include "stock.h" #include "container.h" @@ -88,8 +87,8 @@ } if(states) { - size_t nstates = uic_group_array_size(states); - uic_add_group_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); + size_t nstates = uic_state_array_size(states); + uic_add_state_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); action->setEnabled(false); } @@ -98,7 +97,7 @@ void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenuItem *it = (UiMenuItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger())); } @@ -110,7 +109,7 @@ void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenuCheckItem *it = (UiMenuCheckItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); action->setCheckable(true); action->prepare_event = ui_checkableaction_prepare_event; @@ -130,7 +129,7 @@ void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { UiMenuRadioItem *it = (UiMenuRadioItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); action->setCheckable(true); action->prepare_event = ui_actiongroup_prepare_event; diff -r 3da24640513a -r e57ca2747782 ui/qt/model.cpp --- a/ui/qt/model.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/model.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -29,9 +29,23 @@ #include "model.h" -ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata){ +ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata) { this->obj = obj; - this->view = view; + this->listview = view; + this->combobox = nullptr; + this->var = var; + this->getvalue = getvalue; + this->getvaluedata = getvaluedata; + this->onactivate = nullptr; + this->onactivatedata = nullptr; + this->onselection = nullptr; + this->onselectiondata = nullptr; +} + +ListModel::ListModel(UiObject *obj, QComboBox *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata) { + this->obj = obj; + this->combobox = view; + this->listview = nullptr; this->var = var; this->getvalue = getvalue; this->getvaluedata = getvaluedata; @@ -85,7 +99,12 @@ } void ListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { - UiListSelection sel = ui_selection_model_to_selection(view->selectionModel()); + UiListSelection sel; + if(listview) { + sel = ui_selection_model_to_selection(listview->selectionModel()); + } else { + // TODO + } UiEvent event; event.obj = obj; @@ -257,10 +276,19 @@ QModelIndex index = model->index(sel.rows[i]); selection.select(index, index); } - model->view->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + if(model->listview) { + model->listview->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + } else if(model->combobox) { + // TODO + } } UiListSelection ui_listmodel_getselection(UiList *list) { ListModel *model = (ListModel*)list->obj; - return ui_selection_model_to_selection(model->view->selectionModel()); + if(model->listview) { + return ui_selection_model_to_selection(model->listview->selectionModel()); + } else { + UiListSelection sel = { 0, NULL }; + return sel; + } } diff -r 3da24640513a -r e57ca2747782 ui/qt/model.h --- a/ui/qt/model.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/model.h Sat Dec 13 15:58:58 2025 +0100 @@ -30,10 +30,11 @@ #define MODEL_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../common/context.h" #include #include +#include #include #include #include @@ -54,9 +55,11 @@ public: UiObject *obj; UiVar *var; - QListView *view; + QListView *listview; + QComboBox *combobox; ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); + ListModel(UiObject *obj, QComboBox *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); void setActivationCallback(ui_callback f, void *userdata); void setSelectionCallback(ui_callback f, void *userdata); diff -r 3da24640513a -r e57ca2747782 ui/qt/qt5.pro --- a/ui/qt/qt5.pro Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/qt5.pro Sat Dec 13 15:58:58 2025 +0100 @@ -43,7 +43,6 @@ SOURCES += window.cpp SOURCES += menu.cpp SOURCES += toolbar.cpp -SOURCES += stock.cpp SOURCES += container.cpp SOURCES += text.cpp SOURCES += model.cpp @@ -59,7 +58,6 @@ HEADERS += window.h HEADERS += menu.h HEADERS += toolbar.h -HEADERS += stock.h HEADERS += container.h HEADERS += text.h HEADERS += model.h diff -r 3da24640513a -r e57ca2747782 ui/qt/toolbar.cpp --- a/ui/qt/toolbar.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/toolbar.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -30,7 +30,6 @@ #include "toolbar.h" #include "menu.h" -#include "stock.h" static void add_items(UiObject *obj, QToolBar *toolbar, CxList *defaults, CxMap *items); static void create_item(UiObject *obj, QToolBar *toolbar, UiToolbarItemI *i); diff -r 3da24640513a -r e57ca2747782 ui/qt/toolkit.cpp --- a/ui/qt/toolkit.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/toolkit.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -31,22 +31,15 @@ #include "toolkit.h" #include "window.h" -#include "stock.h" #include "../common/document.h" #include "../common/properties.h" #include "../common/menu.h" #include "../common/toolbar.h" +#include "../common/app.h" static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static int is_toplevel_realized = 0; static int app_argc; @@ -73,33 +66,14 @@ return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); application->exec(); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); delete application; diff -r 3da24640513a -r e57ca2747782 ui/qt/window.cpp --- a/ui/qt/window.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/qt/window.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -41,9 +41,8 @@ #include #include -static UiObject* create_window(const char *title, void *window_data, bool simple, bool sidebar = false) { +static UiObject* create_window(const char *title, bool simple, bool sidebar = false) { UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; obj->next = NULL; QMainWindow *window = new QMainWindow(); @@ -73,16 +72,16 @@ return obj; } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, false); +UiObject* ui_window(const char *title) { + return create_window(title, false); } -UiObject* ui_simplewindow(char *title, void *window_data) { - return create_window(title, window_data, true); +UiObject* ui_simple_window(const char *title) { + return create_window(title, true); } -UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, false, true); +UiObject* ui_sidebar_window(const char *title) { + return create_window(title, false, true); } void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { diff -r 3da24640513a -r e57ca2747782 ui/ui/button.h --- a/ui/ui/button.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/button.h Sat Dec 13 15:58:58 2025 +0100 @@ -65,7 +65,7 @@ ui_callback onclick; void *onclickdata; - const int *groups; + const int *states; } UiButtonArgs; typedef struct UiToggleArgs { @@ -93,9 +93,9 @@ const char *varname; ui_callback onchange; void *onchangedata; - int enable_group; + int enable_state; - const int *groups; + const int *states; } UiToggleArgs; typedef struct UiLinkButtonArgs { @@ -124,7 +124,7 @@ UiBool nofollow; UiLinkType type; - const int *groups; + const int *states; } UiLinkButtonArgs; #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } ) diff -r 3da24640513a -r e57ca2747782 ui/ui/container.h --- a/ui/ui/container.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/container.h Sat Dec 13 15:58:58 2025 +0100 @@ -98,6 +98,8 @@ int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; @@ -310,13 +312,14 @@ #define ui_left_panel0(obj) for(ui_left_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_right_panel0(obj) for(ui_right_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) - #define ui_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_grid_w(obj, w, ...) for(w = ui_grid_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_tabview_w(obj, w, ...) for(w = ui_tabview_create(obj, &(UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_scrolledwindow_w(obj, w, ...) for(w = ui_scrolledwindow_create(obj, &(UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_custom_container(ob, widget, addfunc, data) for(ui_custom_container_create(obj, widget, addfunc, data);ui_container_finish(obj);ui_container_begin_close(obj)) + #define ui_hsplitpane(obj, ...) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_vsplitpane(obj, ...) for(ui_vsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hsplitpane0(obj) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) @@ -367,6 +370,9 @@ UIEXPORT void ui_newline(UiObject *obj); +typedef void(*ui_addwidget_func)(UiObject *obj, UIWIDGET parent, UIWIDGET child, void *userdata); +UIEXPORT void ui_custom_container_create(UiObject *obj, UIWIDGET widget, ui_addwidget_func add_child, void *userdata); + // TODO UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj); UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view); diff -r 3da24640513a -r e57ca2747782 ui/ui/entry.h --- a/ui/ui/entry.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/entry.h Sat Dec 13 15:58:58 2025 +0100 @@ -65,7 +65,7 @@ ui_callback onchange; void* onchangedata; - const int *groups; + const int *states; } UiSpinBoxArgs; diff -r 3da24640513a -r e57ca2747782 ui/ui/image.h --- a/ui/ui/image.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/image.h Sat Dec 13 15:58:58 2025 +0100 @@ -58,6 +58,8 @@ int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; diff -r 3da24640513a -r e57ca2747782 ui/ui/list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/ui/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,367 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TREE_H +#define UI_TREE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiModel UiModel; +typedef struct UiListCallbacks UiListCallbacks; +typedef struct UiListDnd UiListDnd; + +typedef struct UiListArgs UiListArgs; +typedef struct UiSourceListArgs UiSourceListArgs; + +typedef struct UiSubList UiSubList; +typedef struct UiSubListItem UiSubListItem; + +typedef enum UiModelType { + UI_STRING = 0, + UI_STRING_FREE, + UI_INTEGER, + UI_ICON, + UI_ICON_TEXT, + UI_ICON_TEXT_FREE, + UI_STRING_EDITABLE, + UI_BOOL_EDITABLE +} UiModelType; + +typedef struct UiCellValue { + union { + const char *string; + int64_t i; + UiBool b; + }; + UiModelType type; +} UiCellValue; + +typedef UiBool (*ui_list_savefunc)(UiList *list, int row, int col, UiCellValue *value, void *userdata); + +typedef void (*ui_model_update_func)(UiModel *model, void *userdata, int insert_index, int delete_index); + +typedef struct UiModelChangeObserver UiModelChangeObserver; +struct UiModelChangeObserver { + ui_model_update_func update; + void *userdata; + UiModelChangeObserver *next; +}; + +struct UiModel { + UiContext *ctx; + + /* + * number of columns + */ + int columns; + + /* + * current allocation size (internal) + */ + int alloc; + + /* + * array of column types + * array length is the number of columns + */ + UiModelType *types; + + /* + * array of column titles + * array length is the number of columns + */ + char **titles; + + /* + * array of column size hints + */ + int *columnsize; + + /* + * Model change observers, that will be called when + * columns are added or removed + */ + UiModelChangeObserver *observer; + + /* + * reference counter + */ + int ref; +}; + +struct UiListCallbacks { + /* + * selection callback + */ + ui_callback activate; + + /* + * cursor callback + */ + ui_callback selection; + + /* + * userdata for all callbacks + */ + void *userdata; +}; + +struct UiListArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + + const char *name; + const char *style_class; + UiList *list; + const char* varname; + UiModel *model; + char **static_elements; + size_t static_nelm; + ui_getvaluefunc getvalue; + ui_getvaluefunc2 getvalue2; + void *getvalue2data; + ui_getstylefunc getstyle; + void *getstyledata; + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + ui_callback ondragstart; + void *ondragstartdata; + ui_callback ondragcomplete; + void *ondragcompletedata; + ui_callback ondrop; + void *ondropdata; + UiBool multiselection; + UiMenuBuilder *contextmenu; + ui_list_savefunc onsave; + void *onsavedata; + + const int *states; +}; + +typedef void (*ui_sublist_getvalue_func)(UiList *list, void *sublist_userdata, void *rowdata, int index, UiSubListItem *item, void *userdata); + +struct UiSubList { + UiList *value; + const char *varname; + const char *header; + UiBool separator; + void *userdata; +}; + +typedef struct UiSubListEventData { + UiList *list; + int sublist_index; + int row_index; + void *row_data; + void *sublist_userdata; + void *event_data; +} UiSubListEventData; + +/* + * list item members must be filled by the sublist getvalue func + * all members must be allocated (by malloc, strdup, ...) the pointer + * will be passed to free + */ +struct UiSubListItem { + char *icon; + char *label; + char *button_icon; + char *button_label; + UiMenuBuilder *button_menu; + char *badge; + void *eventdata; +}; + +struct UiSourceListArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + const char *name; + const char *style_class; + + const int *states; + + /* + * static list of sublists + * a sublist must have a varname or a value + * + * the last entry in the list must contain all NULL values or numsublists + * must contain the number of sublists + * + * sublists can be NULL, in which case sublists are dynamically loaded + * from dynamic_sublist/varname + */ + UiSubList *sublists; + /* + * optional number of sublists + * if the value is 0, it is assumed, that sublists is null-terminated + * (last item contains only NULL values) + */ + size_t numsublists; + + /* + * list value, that contains UiSubList* elements + */ + UiList *dynamic_sublist; + + /* + * load sublists dynamically from a variable with the specified name + */ + const char *varname; + + + /* + * callback for each list item, that should fill all necessary + * UiSubListItem fields + */ + ui_sublist_getvalue_func getvalue; + + /* + * getvalue_func userdata + */ + void *getvaluedata; + + /* + * is a sublist header a selectable item + */ + UiBool header_is_item; + + /* + * activated when a list item is selected + */ + ui_callback onactivate; + void *onactivatedata; + + /* + * activated, when the additional list item button is clicked + */ + ui_callback onbuttonclick; + void *onbuttonclickdata; + + UiMenuBuilder *contextmenu; +}; + +#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ } +#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} } + + +/* + * Creates an UiModel, that specifies columns for a table widget. + * + * For each column a column type (UiModelType) and a title string + * (char*) must be specified. The column list must be terminated + * with -1. + * + * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1); + */ +UIEXPORT UiModel* ui_model(UiContext *ctx, ...); +UIEXPORT UiModel* ui_model_new(UiContext *ctx); +UIEXPORT void ui_model_add_column(UiModel *model, UiModelType type, const char *title, int width); +UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model); +UIEXPORT void ui_model_ref(UiModel *model); +UIEXPORT void ui_model_unref(UiModel *model); +UIEXPORT void ui_model_add_observer(UiModel *model, ui_model_update_func update, void *data); +UIEXPORT void ui_model_remove_observer(UiModel *model, void *data); +UIEXPORT void ui_model_free(UiModel *mi); + +#define ui_listview(obj, ...) ui_listview_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_table(obj, ...) ui_table_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_dropdown(obj, ...) ui_dropdown_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, &(UiSourceListArgs) { __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args); +UIEXPORT UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args); +UIEXPORT UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args); +UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject *obj, UiListArgs *args); + +UIEXPORT void ui_listview_select(UIWIDGET listview, int index); +UIEXPORT int ui_listview_selection(UIWIDGET listview); +UIEXPORT void ui_dropdown_select(UIWIDGET dropdown, int index); +UIEXPORT int ui_dropdown_selection(UIWIDGET dropdown); + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args); + +UIEXPORT void ui_sublist_item_set_icon(UiSubListItem *item, const char *icon); +UIEXPORT void ui_sublist_item_set_label(UiSubListItem *item, const char *label); +UIEXPORT void ui_sublist_item_set_button_icon(UiSubListItem *item, const char *button_icon); +UIEXPORT void ui_sublist_item_set_button_label(UiSubListItem *item, const char *button_label); +UIEXPORT void ui_sublist_item_set_button_menu(UiSubListItem *item, UiMenuBuilder *menu); +UIEXPORT void ui_sublist_item_set_badge(UiSubListItem *item, const char *badge); +UIEXPORT void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata); + + + +/* + * Only relevant for some language bindings + */ +typedef void(*ui_sourcelist_update_func)(void); + +/* + * The sourcelist update callback is called after any source list + * sublist update is completed + */ +UIEXPORT void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_TREE_H */ + diff -r 3da24640513a -r e57ca2747782 ui/ui/menu.h --- a/ui/ui/menu.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/menu.h Sat Dec 13 15:58:58 2025 +0100 @@ -43,7 +43,7 @@ ui_callback onclick; void* onclickdata; - const int* groups; + const int* states; } UiMenuItemArgs; typedef struct UiMenuToggleItemArgs { @@ -54,7 +54,7 @@ ui_callback onchange; void* onchangedata; - const int* groups; + const int* nstates; } UiMenuToggleItemArgs; typedef struct UiMenuItemListArgs { diff -r 3da24640513a -r e57ca2747782 ui/ui/properties.h --- a/ui/ui/properties.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/properties.h Sat Dec 13 15:58:58 2025 +0100 @@ -35,6 +35,9 @@ extern "C" { #endif +void ui_load_properties_file_on_startup(UiBool enable); +void ui_set_properties_data(const char *str, size_t len); + const char* ui_get_property(const char *name); void ui_set_property(const char *name, const char *value); const char* ui_set_default_property(const char *name, const char *value); diff -r 3da24640513a -r e57ca2747782 ui/ui/text.h --- a/ui/ui/text.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/text.h Sat Dec 13 15:58:58 2025 +0100 @@ -59,7 +59,7 @@ ui_callback onchange; void *onchangedata; - const int *groups; + const int *states; } UiTextAreaArgs; typedef struct UiTextFieldArgs { @@ -87,7 +87,7 @@ ui_callback onactivate; void *onactivatedata; - const int *groups; + const int *states; } UiTextFieldArgs; typedef struct UiPathElmRet { @@ -115,6 +115,7 @@ int margin_bottom; int colspan; int rowspan; + int width; const char *name; const char *style_class; diff -r 3da24640513a -r e57ca2747782 ui/ui/toolbar.h --- a/ui/ui/toolbar.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/toolbar.h Sat Dec 13 15:58:58 2025 +0100 @@ -44,7 +44,7 @@ ui_callback onclick; void* onclickdata; - const int *groups; + const int *states; const int *visibility_states; } UiToolbarItemArgs; @@ -57,7 +57,7 @@ ui_callback onchange; void *onchangedata; - const int *groups; + const int *states; const int *visibility_states; } UiToolbarToggleItemArgs; diff -r 3da24640513a -r e57ca2747782 ui/ui/toolkit.h --- a/ui/ui/toolkit.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/toolkit.h Sat Dec 13 15:58:58 2025 +0100 @@ -146,6 +146,14 @@ #endif +#elif UI_SERVER + +typedef struct UiWidget UiWidget; + +#define UIWIDGET UiWidget* +#define UIWINDOW UiWidget* +#define UIMENU void* + #endif #ifndef TRUE @@ -417,6 +425,7 @@ }; typedef void (*ui_list_init_func)(UiContext *ctx, UiList *list, void *userdata); +typedef void (*ui_list_destroy_func)(UiContext *ctx, UiList *list, void *userdata); /* * abstract list @@ -541,6 +550,7 @@ UIEXPORT void ui_app_exit_on_shutdown(UiBool exitapp); UIEXPORT void ui_main(void); +UIEXPORT void ui_app_quit(void); UIEXPORT void ui_show(UiObject *obj); UIEXPORT void ui_close(UiObject *obj); @@ -557,16 +567,18 @@ UIEXPORT UiContext* ui_document_context(void *doc); +UIEXPORT void* ui_context_get_document(UiContext *ctx); +UIEXPORT void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable); UIEXPORT void ui_attach_document(UiContext *ctx, void *document); UIEXPORT void ui_detach_document(UiContext *ctx, void *document); -UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); -UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups); +UIEXPORT void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); +UIEXPORT void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates); UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, const int *states, int nstates); -UIEXPORT void ui_set_group(UiContext *ctx, int group); -UIEXPORT void ui_unset_group(UiContext *ctx, int group); -UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups); +UIEXPORT void ui_set_state(UiContext *ctx, int state); +UIEXPORT void ui_unset_state(UiContext *ctx, int state); +UIEXPORT int* ui_active_states(UiContext *ctx, int *nstates); UIEXPORT void* ui_allocator(UiContext *ctx); UIEXPORT void* ui_cx_mempool(UiContext *ctx); @@ -625,6 +637,13 @@ UIEXPORT void ui_var_set_string(UiContext *ctx, const char *name, char *value); UIEXPORT char* ui_var_get_string(UiContext *ctx, const char *name); +UIEXPORT UiInteger* ui_get_int_var(UiContext *ctx, const char *name); +UIEXPORT UiDouble* ui_get_double_var(UiContext *ctx, const char *name); +UIEXPORT UiString* ui_get_string_var(UiContext *ctx, const char *name); +UIEXPORT UiText* ui_get_text_var(UiContext *ctx, const char *name); +UIEXPORT UiRange* ui_get_range_var(UiContext *ctx, const char *name); +UIEXPORT UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name); + UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data); UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer); UIEXPORT UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data); @@ -635,7 +654,7 @@ UIEXPORT UiList* ui_list_new(UiContext *ctx, const char *name); UIEXPORT UiList* ui_list_new2(UiContext *ctx, const char *name, ui_list_init_func init, void *userdata); -UIEXPORT void ui_list_free(UiList *list); +UIEXPORT void ui_list_free(UiContext *ctx, UiList *list); UIEXPORT void* ui_list_first(UiList *list); UIEXPORT void* ui_list_next(UiList *list); UIEXPORT void* ui_list_get(UiList *list, int i); @@ -680,9 +699,12 @@ UIEXPORT void ui_setop_enable(int set); UIEXPORT int ui_get_setop(void); +UIEXPORT void ui_onchange_events_enable(UiBool enable); +UIEXPORT UiBool ui_onchange_events_is_enabled(void); UIEXPORT void ui_global_list_initializer(ui_list_init_func func, void *userdata); +UIEXPORT void ui_global_list_destructor(ui_list_destroy_func func, void *userdata); UIEXPORT void ui_list_class_set_first(UiList *list, void*(*first)(UiList *list)); UIEXPORT void ui_list_class_set_next(UiList *list, void*(*next)(UiList *list)); UIEXPORT void ui_list_class_set_get(UiList *list, void*(*get)(UiList *list, int i)); @@ -690,6 +712,9 @@ UIEXPORT void ui_list_class_set_data(UiList *list, void *data); UIEXPORT void ui_list_class_set_iter(UiList *list, void *iter); +UIEXPORT void ui_object_set(UiObject *obj, const char *key, void *data); +UIEXPORT void* ui_object_get(UiObject *obj, const char *key); + #ifdef __cplusplus } #endif diff -r 3da24640513a -r e57ca2747782 ui/ui/ui.h --- a/ui/ui/ui.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/ui.h Sat Dec 13 15:58:58 2025 +0100 @@ -35,11 +35,10 @@ #include "menu.h" #include "toolbar.h" #include "window.h" -#include "stock.h" #include "button.h" #include "text.h" #include "properties.h" -#include "tree.h" +#include "list.h" #include "graphics.h" #include "entry.h" #include "range.h" diff -r 3da24640513a -r e57ca2747782 ui/ui/webview.h --- a/ui/ui/webview.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/webview.h Sat Dec 13 15:58:58 2025 +0100 @@ -60,13 +60,15 @@ int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; UiGeneric *value; const char *varname; - const int* groups; + const int* states; } UiWebviewArgs; #define ui_webview(obj, ...) ui_webview_create(obj, &(UiWebviewArgs){ __VA_ARGS__ } ) diff -r 3da24640513a -r e57ca2747782 ui/ui/widget.h --- a/ui/ui/widget.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/widget.h Sat Dec 13 15:58:58 2025 +0100 @@ -64,6 +64,8 @@ typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); #elif defined(UI_WIN32) typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#elif defined(UI_SERVER) +typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); #endif UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args); diff -r 3da24640513a -r e57ca2747782 ui/ui/win32.h --- a/ui/ui/win32.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/win32.h Sat Dec 13 15:58:58 2025 +0100 @@ -49,7 +49,7 @@ }; struct W32WidgetClass { - void (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + int (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void (*show)(W32Widget *widget, BOOLEAN show); void (*enable)(W32Widget *widget, BOOLEAN enable); W32Size (*get_preferred_size)(W32Widget *widget); diff -r 3da24640513a -r e57ca2747782 ui/ui/window.h --- a/ui/ui/window.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/ui/window.h Sat Dec 13 15:58:58 2025 +0100 @@ -61,10 +61,10 @@ const char *lbutton2; const char *rbutton3; const char *rbutton4; - const int *lbutton1_groups; - const int *lbutton2_groups; - const int *rbutton3_groups; - const int *rbutton4_groups; + const int *lbutton1_states; + const int *lbutton2_states; + const int *rbutton3_states; + const int *rbutton4_states; int default_button; int width; int height; @@ -72,10 +72,10 @@ void *onclickdata; } UiDialogWindowArgs; -UIEXPORT UiObject *ui_window(const char *title, void *window_data); -UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_window(const char *title); +UIEXPORT UiObject *ui_sidebar_window(const char *title); UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar); -UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_simple_window(const char *title); UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args); #define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, &(UiDialogWindowArgs){ __VA_ARGS__ }); @@ -83,6 +83,9 @@ UIEXPORT void ui_window_size(UiObject *obj, int width, int height); UIEXPORT void ui_window_default_size(int width, int height); +UIEXPORT void ui_window_menubar_set_visible(UiObject *obj, UiBool visible); + +UIEXPORT void ui_window_fullscreen(UiObject *obj, UiBool fullscreen); UIEXPORT void ui_splitview_window_set_pos(UiObject *obj, int pos); UIEXPORT int ui_splitview_window_get_pos(UiObject *obj); diff -r 3da24640513a -r e57ca2747782 ui/win32/button.c --- a/ui/win32/button.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/button.c Sat Dec 13 15:58:58 2025 +0100 @@ -32,6 +32,8 @@ #include #include +#include + #include static W32WidgetClass button_widget_class = { @@ -42,6 +44,22 @@ .destroy = w32_widget_default_destroy }; +static W32WidgetClass togglebutton_widget_class = { + .eventproc = ui_togglebutton_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_button_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +static W32WidgetClass radiobutton_widget_class = { + .eventproc = ui_radiobutton_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_button_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) { HINSTANCE hInstance = GetModuleHandle(NULL); UiContainerPrivate *container = ui_obj_container(obj); @@ -82,11 +100,12 @@ return size; } -void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { +int ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { UiWidget *w = (UiWidget*)widget; - printf("button eventproc\n"); - fflush(stdout); + if (uMsg != WM_COMMAND) { + return 0; + } UiEvent e; e.obj = w->obj; @@ -100,4 +119,200 @@ if (w->callback) { w->callback(&e, w->callbackdata); } + + return 0; } + +static UIWIDGET create_togglebutton(UiObject *obj, UiToggleArgs *args, unsigned long type) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindow( + "BUTTON", + args->label, + WS_VISIBLE | WS_CHILD | type, + 0, 0, 100, 30, + parent, + (HMENU)1, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + W32Widget *widget = w32_widget_create(&togglebutton_widget_class, hwnd, sizeof(UiWidget)); + ui_container_add(container, widget, &layout); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if (var) { + UiInteger *i = var->value; + i->obj = widget; + i->get = ui_togglebutton_get; + i->set = ui_togglebutton_set; + if (i->value != 0) { + SendMessage(hwnd, BM_SETCHECK, BST_CHECKED, 0); + } + } + + UiWidget *btn = (UiWidget*)widget; + btn->obj = obj; + btn->var = var; + btn->callback = args->onchange; + btn->callbackdata = args->onchangedata; + + return widget; +} + +UIWIDGET ui_togglebutton_create(UiObject *obj, UiToggleArgs *args) { + return create_togglebutton(obj, args, BS_AUTOCHECKBOX | BS_PUSHLIKE); +} + +UIWIDGET ui_checkbox_create(UiObject *obj, UiToggleArgs *args) { + return create_togglebutton(obj, args, BS_AUTOCHECKBOX); +} + +UIWIDGET ui_switch_create(UiObject *obj, UiToggleArgs *args) { + return create_togglebutton(obj, args, BS_AUTOCHECKBOX); +} + +int64_t ui_togglebutton_get(UiInteger *i) { + UiWidget *btn = (UiWidget*)i->obj; + LRESULT state = SendMessage(btn->widget.hwnd, BM_GETCHECK, 0, 0); + i->value = state; + return state; +} + +void ui_togglebutton_set(UiInteger *i, int64_t v) { + UiWidget *btn = (UiWidget*)i->obj; + WPARAM state = v ? BST_CHECKED : BST_UNCHECKED; + SendMessage(btn->widget.hwnd, BM_SETCHECK, state, 0); + i->value = v; +} + +int ui_togglebutton_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg != WM_COMMAND) { + return 0; + } + UiWidget *w = (UiWidget*)widget; + + UiEvent e; + e.obj = w->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = SendMessage(w->widget.hwnd, BM_GETCHECK, 0, 0); + e.set = ui_get_setop(); + + if (w->callback) { + w->callback(&e, w->callbackdata); + } + + return 0; +} + + +UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindow( + "BUTTON", + args->label, + WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON | WS_GROUP , + 0, 0, 100, 30, + parent, + (HMENU)1, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + W32Widget *widget = w32_widget_create(&radiobutton_widget_class, hwnd, sizeof(UiWidget)); + ui_container_add(container, widget, &layout); + UiWidget *btn = (UiWidget*)widget; + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if (var) { + UiInteger *i = var->value; + // Use a CxList as binding object (i->obj) and add all radiobuttons to it + // The first radiobutton, which binds to this var, creates the CxList + CxList *group = NULL; + if (i->obj) { + group = i->obj; + } else { + group = cxArrayListCreate(obj->ctx->allocator, NULL, CX_STORE_POINTERS, 8); + i->obj = group; + } + + cxListAdd(group, btn); + if (i->value == cxListSize(group)) { + // TODO: select + } + } + + btn->obj = obj; + btn->var = var; + btn->callback = args->onchange; + btn->callbackdata = args->onchangedata; + + return widget; +} + +int ui_radiobutton_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg != WM_COMMAND) { + return 0; + } + UiWidget *w = (UiWidget*)widget; + + int checked = SendMessage(w->widget.hwnd, BM_GETCHECK, 0, 0); + if (!checked) { + return 0; // ignore uncheck events + } + + int b = 0; + if (w->var) { + UiInteger *i = w->var->value; + CxList *group = i->obj; + // Find selected index and uncheck all radiobuttons in this group + // that are not this event widget + CxIterator iter = cxListIterator(group); + cx_foreach(UiWidget *, radiobutton, iter) { + if (radiobutton == w) { + i->value = iter.index+1; + b = i->value; + } else { + SendMessage(radiobutton->widget.hwnd, BM_SETCHECK, BST_UNCHECKED, 0); + } + } + } + + UiEvent e; + e.obj = w->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = b; + e.set = ui_get_setop(); + + if (w->callback) { + w->callback(&e, w->callbackdata); + } + + return 0; +} + +int64_t ui_radiobutton_get(UiInteger *i) { + return i->value; +} + +void ui_radiobutton_set(UiInteger *i, int64_t v) { + CxList *group = i->obj; + CxIterator iter = cxListIterator(group); + cx_foreach(UiWidget *, radiobutton, iter) { + SendMessage(radiobutton->widget.hwnd, BM_SETCHECK, iter.index+1 == v ? BST_CHECKED : BST_UNCHECKED, 0); + } + i->value = v; +} diff -r 3da24640513a -r e57ca2747782 ui/win32/button.h --- a/ui/win32/button.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/button.h Sat Dec 13 15:58:58 2025 +0100 @@ -34,6 +34,14 @@ W32Size ui_button_get_preferred_size(W32Widget *widget); -void ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +int ui_button_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +int64_t ui_togglebutton_get(UiInteger *i); +void ui_togglebutton_set(UiInteger *i, int64_t v); +int ui_togglebutton_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +int64_t ui_radiobutton_get(UiInteger *i); +void ui_radiobutton_set(UiInteger *i, int64_t v); +int ui_radiobutton_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #endif //BUTTON_H diff -r 3da24640513a -r e57ca2747782 ui/win32/container.c --- a/ui/win32/container.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/container.c Sat Dec 13 15:58:58 2025 +0100 @@ -35,7 +35,7 @@ static W32WidgetClass grid_layout_widget_class = { - .eventproc = NULL, + .eventproc = ui_grid_widget_event, .enable = NULL, .show = w32_widget_default_show, .get_preferred_size = ui_grid_layout_get_preferred_size, @@ -62,6 +62,18 @@ ctn->container.newline = FALSE; } +int ui_grid_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg == WM_ERASEBKGND) { + HDC hdc = (HDC)wParam; + UiGridWidget *grid = (UiGridWidget*)widget; + RECT rc; + GetClientRect(hwnd, &rc); + FillRect(hdc, &rc, grid->brush); + return 1; + } + return 0; +} + W32Size ui_grid_layout_get_preferred_size(W32Widget *widget) { UiGridLayout *grid = widget->layoutmanager; W32Size size; @@ -134,26 +146,27 @@ TEXT("STATIC"), NULL, WS_CHILD | WS_VISIBLE, - 0, 0, 100, 100, + 0, 0, 300, 300, parent, NULL, hInstance, NULL); SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)ui_default_eventproc); - W32Widget *widget = w32_widget_new(&grid_layout_widget_class, hwnd); - ui_container_add(container, widget, &layout); + UiGridWidget *widget = w32_widget_create(&grid_layout_widget_class, hwnd, sizeof(UiGridWidget)); + ui_container_add(container, (W32Widget*)widget, &layout); UiContainerX *gridContainer = ui_grid_container_create(obj, hwnd, args->columnspacing, args->rowspacing, INSETS_ZERO); uic_object_push_container(obj, gridContainer); UiGridLayoutContainer *grid = (UiGridLayoutContainer*)gridContainer; - widget->layout = (W32LayoutFunc)ui_grid_layout; - widget->layoutmanager = grid->layout; + widget->widget.layout = (W32LayoutFunc)ui_grid_layout; + widget->widget.layoutmanager = grid->layout; + widget->brush = GetSysColorBrush(COLOR_BTNFACE); grid->layout->preferred_width = 200; grid->layout->preferred_height = 200; - return widget; + return (W32Widget*)widget; } UiContainerX* ui_grid_container_create(UiObject *obj, HWND hwnd, short columnspacing, short rowspacing, GridEdgeInsets padding) { diff -r 3da24640513a -r e57ca2747782 ui/win32/container.h --- a/ui/win32/container.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/container.h Sat Dec 13 15:58:58 2025 +0100 @@ -40,6 +40,7 @@ typedef struct UiContainerPrivate UiContainerPrivate; typedef struct UiGridLayoutContainer UiGridLayoutContainer; typedef struct UiBoxContainer UiBoxContainer; +typedef struct UiGridWidget UiGridWidget; enum UiBoxOrientation { UI_BOX_VERTICAL = 0, @@ -88,10 +89,16 @@ UiBool def_vfill; }; +struct UiGridWidget { + W32Widget widget; + HBRUSH brush; +}; + UiContainerPrivate* ui_obj_container(UiObject *obj); HWND ui_container_get_parent(UiContainerPrivate *ctn); void ui_container_add(UiContainerPrivate *ctn, W32Widget *widget, UiLayout *layout); +int ui_grid_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); W32Size ui_grid_layout_get_preferred_size(W32Widget *widget); UiContainerX* ui_box_container_create(UiObject *obj, HWND hwnd, UiBoxOrientation orientation, short spacing, GridEdgeInsets padding); diff -r 3da24640513a -r e57ca2747782 ui/win32/grid.c --- a/ui/win32/grid.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/grid.c Sat Dec 13 15:58:58 2025 +0100 @@ -293,7 +293,9 @@ child_x = col->pos + elm->layout.margin.left; child_y = row->pos + elm->layout.margin.top; - SetWindowPos(elm->widget->hwnd, NULL, child_x, child_y, child_width, child_height, SWP_NOZORDER); + SetWindowPos(elm->widget->hwnd, NULL, child_x, child_y, child_width, child_height, SWP_NOZORDER | SWP_NOACTIVATE); + InvalidateRect(elm->widget->hwnd, NULL, TRUE); + //UpdateWindow(elm->widget->hwnd); if (elm->widget->layout) { elm->widget->layout(elm->widget->layoutmanager, child_width, child_height); } diff -r 3da24640513a -r e57ca2747782 ui/win32/list.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/list.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,501 @@ +/* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +#include "list.h" +#include "container.h" + + + +static W32WidgetClass listview_widget_class = { + .eventproc = ui_listview_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_listview_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +static void* strmodel_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return col == 0 ? elm : NULL; +} + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return NULL; +} + +/* + * Creates an UiListView widget object and initializes it from the UiListArgs + */ +static UiListView* create_listview_widget(UiObject *obj, W32WidgetClass *widget_class, HWND hwnd, UiListArgs *args, UiBool table) { + UiListView *listview = w32_widget_create(widget_class, hwnd, sizeof(UiListView)); + listview->widget.hwnd = hwnd; + listview->obj = obj; + listview->preferred_width = args->width ? args->width : 300; // 300: default width/height + listview->preferred_height = args->height ? args->height : 300; + listview->onactivate = args->onactivate; + listview->onactivatedata = args->onactivatedata; + listview->onselection = args->onselection; + listview->onselectiondata = args->onselectiondata; + listview->ondragstart = args->ondragstart; + listview->ondragstartdata = args->ondragstartdata; + listview->ondragcomplete = args->ondragcomplete; + listview->ondragcompletedata = args->ondragcompletedata; + listview->ondrop = args->ondrop; + listview->ondropdata = args->ondropdata; + listview->istable = table; + + // convert ui_getvaluefunc into ui_getvaluefunc2 if necessary + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = table ? null_getvalue : strmodel_getvalue; + } + } + listview->getvalue = getvalue; + listview->getvaluedata = getvaluedata; + + listview->var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + return listview; +} + +static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_LISTVIEW, + "", + WS_CHILD | WS_VISIBLE | LVS_REPORT, + 0, 0, 100, 100, + parent, + (HMENU)1337, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + ListView_SetExtendedListViewStyle( + hwnd, + LVS_EX_FULLROWSELECT //| LVS_EX_GRIDLINES + ); + + UiListView *listview = create_listview_widget(obj, &listview_widget_class, hwnd, args, table); + ui_container_add(container, (W32Widget*)listview, &layout); + + // init list model + // always initialize listview->model + int numcolumns = 0; + if (table) { + if (args->model) { + listview->model = ui_model_copy(obj->ctx, args->model); + numcolumns = listview->model->columns; + } else { + listview->model = ui_model_new(obj->ctx); + } + } else { + UiModel *model = ui_model_new(obj->ctx); + ui_model_add_column(model, UI_STRING, "Test", -1); + listview->model = model; + numcolumns = 1; + } + + // create columns + UiModel *model = listview->model; + for (int i=0;itypes[i]; + char *title = model->titles[i]; + size_t titlelen = title ? strlen(title) : 0; + int size = model->columnsize[i]; + switch (type) { + default: { + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = title; + col.cx = size > 0 ? size : titlelen*10+5; + break; + } + case UI_ICON: { + break; // TODO + } + } + ListView_InsertColumn(hwnd, i, &col); + } + + // bind the listview to the provided UiList + if (listview->var) { + UiList *list = listview->var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection_impl; + list->setselection = ui_listview_setselection_impl; + + ui_listview_update(list, -1); + } else if (!table && args->static_elements && args->static_nelm > 0) { + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; + LVITEM item; + item.mask = LVIF_TEXT; + item.iSubItem = 0; + for (int i=0;igetvalue = strmodel_getvalue; + listview->getvaluedata = NULL; + } + + return (W32Widget*)listview; +} + +static UiListSelection listview_get_selection2(HWND hwnd) { + UiListSelection sel = { 0, NULL }; + + CX_ARRAY_DECLARE(int, indices); + cx_array_initialize(indices, 8); + + int index = -1; + while ((index = ListView_GetNextItem(hwnd, index, LVNI_SELECTED)) != -1) { + cx_array_simple_add(indices, index); + } + + if (indices_size > 0) { + sel.rows = indices; + sel.count = indices_size; + } + + return sel; +} + +static UiListSelection listview_get_selection(UiListView *listview) { + HWND hwnd = listview->widget.hwnd; + return listview_get_selection2(hwnd); +} + +// listview class event proc +int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + UiListView *listview = (UiListView*)widget; + switch (uMsg) { + case WM_NOTIFY: { + LPNMHDR hdr = (LPNMHDR)lParam; + switch (hdr->code) { + case LVN_ITEMCHANGED: { + LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; + int row = lv->iItem; + if ((lv->uChanged & LVIF_STATE) && (lv->uNewState & LVIS_SELECTED) && listview->onselection) { + UiListSelection sel = listview_get_selection(listview); + + UiEvent event; + event.obj = listview->obj; + event.window = listview->obj->window; + event.document = listview->obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = row; + event.set = ui_get_setop(); + listview->onselection(&event, listview->onselectiondata); + + ui_listselection_free(sel); + } + break; + } + case LVN_ITEMACTIVATE: { + LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; + int row = lv->iItem; + if (listview->onactivate) { + UiEvent event; + event.obj = listview->obj; + event.window = listview->obj->window; + event.document = listview->obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + event.intval = row; + event.set = ui_get_setop(); + + if (listview->var) { + UiList *list = listview->var->value; + event.eventdata = list->get(list, row); + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + } + + listview->onactivate(&event, listview->onactivatedata); + } + break; + } + } + break; + } + } + + return 0; +} + +W32Size ui_listview_get_preferred_size(W32Widget *widget) { + UiListView *listview = (UiListView*)widget; + W32Size size; + size.width = listview->preferred_width; + size.height = listview->preferred_height; + return size; +} + +/* + * Creates and inserts an LVITEM + * + * list: An UiList bound to an UiListView + * row: row index + * elm: list element (same as list->get(list, row)) + */ +static void insert_item(UiList *list, int row, void *elm) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + UiModel *model = listview->model; + + LVITEM item; + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + int idx = -1; + for (int col=0;colcolumns;col++) { + UiBool freeResult = FALSE; + // convert the list element to a value, that can be displayed in the list view + // TODO: handle all model types + char *str = listview->getvalue(list, elm, row, col, listview->getvaluedata, &freeResult); + if (col == 0) { + item.pszText = str; + idx = ListView_InsertItem(hwnd, &item); + } else { + ListView_SetItemText(hwnd, idx, col, str); + } + + if (freeResult) { + free(str); + } + } +} + +/* + * UiList->update function + * + * Updates one or all rows + * row: list index or -1 for updating all rows + */ +void ui_listview_update(UiList *list, int row) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + UiModel *model = listview->model; + if (row < 0) { + ListView_DeleteAllItems(hwnd); + void *elm = list->first(list); + int row = 0; + while (elm) { + insert_item(list, row, elm); + elm = list->next(list); + row++; + } + } else { + ListView_DeleteItem(hwnd, row); + void *elm = list->get(list, row); + insert_item(list, row, elm); + } + + // re-adjust all columns + for (int i=0;icolumns;i++) { + ListView_SetColumnWidth(hwnd, i, LVSCW_AUTOSIZE); + } +} + +UiListSelection ui_listview_getselection_impl(UiList *list) { + UiListView *listview = (UiListView*)list->obj; + return listview_get_selection(listview); +} + +void ui_listview_setselection_impl(UiList *list, UiListSelection selection) { + +} + +// public API +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { + return listview_create(obj, args, FALSE); +} + +// public API +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { + return listview_create(obj, args, TRUE); +} + +void ui_listview_select(UIWIDGET listview, int index) { + +} + +int ui_listview_selection(UIWIDGET listview) { + W32Widget *w = (W32Widget*)listview; + UiListSelection sel = listview_get_selection2(w->hwnd); + int index = -1; + if (sel.count > 0) { + index = sel.rows[0]; + } + free(sel.rows); + return index; +} + +/* ------------------------------------ DropDown ------------------------------------*/ + +static W32WidgetClass dropdown_widget_class = { + .eventproc = ui_dropdown_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_dropdown_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_COMBOBOX, + "", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, + 0, 0, 100, 100, + parent, + (HMENU)1337, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + UiListView *dropdown = create_listview_widget(obj, &dropdown_widget_class, hwnd, args, FALSE); + ui_container_add(container, (W32Widget*)dropdown, &layout); + + // bind the dropdown to the provided UiList + if (dropdown->var) { + UiList *list = dropdown->var->value; + list->obj = dropdown; + list->update = ui_dropdown_update; + list->getselection = ui_dropdown_getselection_impl; + list->setselection = ui_dropdown_setselection_impl; + + ui_dropdown_update(list, -1); + } else if (args->static_elements && args->static_nelm > 0) { + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; + for (int i=0;igetvalue = strmodel_getvalue; + dropdown->getvaluedata = NULL; + } + + return (W32Widget*)dropdown; +} + +int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; +} + +W32Size ui_dropdown_get_preferred_size(W32Widget *widget) { + W32Size size; + size.width = 200; + size.height = 30; + return size; +} + +static void dropdown_insert_item(UiList *list, int row, void *elm) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + + UiBool freeResult = FALSE; + char *str = listview->getvalue(list, elm, row, 0, listview->getvaluedata, &freeResult); + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)str); + + if (freeResult) { + free(str); + } +} + +void ui_dropdown_update(UiList *list, int row) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + if (row < 0) { + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); + + void *elm = list->first(list); + int row = 0; + while (elm) { + dropdown_insert_item(list, row, elm); + elm = list->next(list); + row++; + } + } else { + SendMessage(hwnd, CB_DELETESTRING, row, 0); + void *elm = list->get(list, row); + dropdown_insert_item(list, row, elm); + } +} + +UiListSelection ui_dropdown_getselection_impl(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListView *listview = (UiListView*)list->obj; + int index = (int)SendMessage(listview->widget.hwnd, CB_GETCURSEL, 0, 0); + if (index >= 0) { + sel.rows = malloc(sizeof(int)); + sel.rows[0] = index; + sel.count = 1; + } + return sel; +} + +void ui_dropdown_setselection_impl(UiList *list, UiListSelection selection) { + UiListView *listview = (UiListView*)list->obj; + SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); +} + +void ui_dropdown_select(UIWIDGET dropdown, int index) { + SendMessage(dropdown->hwnd, CB_SETCURSEL, 0, 0); +} + +int ui_dropdown_selection(UIWIDGET dropdown) { + return SendMessage(dropdown->hwnd, CB_GETCURSEL, 0, 0); +} diff -r 3da24640513a -r e57ca2747782 ui/win32/list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,83 @@ +/* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIST_H +#define LIST_H + +#include "toolkit.h" +#include "../ui/list.h" +#include "win32.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + W32Widget widget; + UiObject *obj; + UiVar *var; + ui_getvaluefunc2 getvalue; + void *getvaluedata; + ui_getstylefunc getstyle; + void *getstyledata; + UiModel *model; + UiBool istable; + int preferred_width; + int preferred_height; + ui_callback onactivate; + void* onactivatedata; + ui_callback onselection; + void* onselectiondata; + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropdata; +} UiListView; + +int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +W32Size ui_listview_get_preferred_size(W32Widget *widget); + +void ui_listview_update(UiList *list, int row); +UiListSelection ui_listview_getselection_impl(UiList *list); +void ui_listview_setselection_impl(UiList *list, UiListSelection selection); + +int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +W32Size ui_dropdown_get_preferred_size(W32Widget *widget); + +void ui_dropdown_update(UiList *list, int row); +UiListSelection ui_dropdown_getselection_impl(UiList *list); +void ui_dropdown_setselection_impl(UiList *list, UiListSelection selection); + +#ifdef __cplusplus +} +#endif + +#endif //_LIST_H \ No newline at end of file diff -r 3da24640513a -r e57ca2747782 ui/win32/menu.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/menu.c Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,97 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "menu.h" + +static ui_menu_add_f createMenuItem[] = { + /* UI_MENU */ ui_add_menu, + /* UI_MENU_ITEM */ ui_add_menu_item, + /* UI_MENU_CHECK_ITEM */ ui_add_menu_checkitem, + /* UI_MENU_RADIO_ITEM */ ui_add_menu_radioitem, + /* UI_MENU_ITEM_LIST */ ui_add_menu_list, + /* UI_MENU_CHECKITEM_LIST */ ui_add_menu_checklist, + /* UI_MENU_RADIOITEM_LIST */ ui_add_menu_radiolist, + /* UI_MENU_SEPARATOR */ ui_add_menu_separator +}; + + +HMENU ui_create_main_menu(UiObject *obj) { + UiMenu *menu = uic_get_menu_list(); + if (!menu) { + return NULL; + } + + HMENU hMenu = CreateMenu(); + ui_add_menu(hMenu, 0, &menu->item, obj); + + + + return hMenu; +} + +void ui_add_menu(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + UiMenu *menu = (UiMenu*)item; + HMENU hMenu = CreatePopupMenu(); + AppendMenu(parent, MF_POPUP, (UINT_PTR)hMenu, menu->label); + + int i = 0; + UiMenuItemI *child = menu->items_begin; + while (child) { + createMenuItem[child->type](hMenu, i++, child, obj); + child = child->next; + } +} + +void ui_add_menu_item(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *i = (UiMenuItem*)item; + AppendMenu(parent, MF_STRING, 0, i->label); +} + +void ui_add_menu_checkitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_radioitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_checklist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_radiolist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_separator(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} diff -r 3da24640513a -r e57ca2747782 ui/win32/menu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/win32/menu.h Sat Dec 13 15:58:58 2025 +0100 @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MENU_H +#define MENU_H + +#include "toolkit.h" +#include "../ui/menu.h" +#include "../common/menu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void(*ui_menu_add_f)(HMENU, int, UiMenuItemI*, UiObject*); + +HMENU ui_create_main_menu(UiObject *obj); + +void ui_add_menu(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_item(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_checkitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_radioitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_checklist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_radiolist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_separator(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); + +#ifdef __cplusplus +} +#endif + +#endif //MENU_H \ No newline at end of file diff -r 3da24640513a -r e57ca2747782 ui/win32/objs.mk --- a/ui/win32/objs.mk Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/objs.mk Sat Dec 13 15:58:58 2025 +0100 @@ -38,6 +38,8 @@ WIN32OBJ += button.obj WIN32OBJ += grid.obj WIN32OBJ += text.obj +WIN32OBJ += list.obj +WIN32OBJ += menu.obj TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%) TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c) diff -r 3da24640513a -r e57ca2747782 ui/win32/text.c --- a/ui/win32/text.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/text.c Sat Dec 13 15:58:58 2025 +0100 @@ -28,6 +28,135 @@ #include "text.h" +static W32WidgetClass textarea_widget_class = { + .eventproc = ui_textarea_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_textarea_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + int width = args->width >= 0 ? args->width : 100; + int height = args->height >= 0 ? args->height : 100; + + HWND hwnd = CreateWindowEx( + 0, + "EDIT", + "", + WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL, + 0, 0, width, height, + parent, + (HMENU)0, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + W32Widget *widget = w32_widget_create(&textarea_widget_class, hwnd, sizeof(UiTextArea)); + ui_container_add(container, widget, &layout); + + UiTextArea *textarea = (UiTextArea*)widget; + textarea->width = width; + textarea->widget.var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); + textarea->widget.callback = args->onchange; + textarea->widget.callbackdata = args->onchangedata; + + if (textarea->widget.var) { + UiText *t = textarea->widget.var->value; + + if (t->value.ptr) { + // TODO: set textarea string + } + t->obj = widget; + t->save = ui_textarea_save; + t->destroy = ui_textarea_destroy; + t->restore = ui_textarea_restore; + t->get = ui_textarea_get; + t->set = ui_textarea_set; + t->getsubstr = ui_textarea_getsubstr; + t->insert = ui_textarea_insert; + t->setposition = ui_textarea_setposition; + t->position = ui_textarea_position; + t->setselection = ui_textarea_setselection; + t->selection = ui_textarea_selection; + t->length = ui_textarea_length; + t->remove = ui_textarea_remove; + } + + return widget; +} + +W32Size ui_textarea_get_preferred_size(W32Widget *widget) { + W32Size size; + UiTextArea *textarea = (UiTextArea*)widget; + size.width = textarea->width; + size.height = textarea->height; + return size; +} + +int ui_textarea_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; +} + +void ui_textarea_save(UiText *text) { + +} + +void ui_textarea_destroy(UiText *text) { + +} + +void ui_textarea_restore(UiText *text) { + +} + +void ui_textarea_set(UiText *text, const char *str) { + +} + +char* ui_textarea_get(UiText *text) { + return NULL; +} + +char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + return NULL; +} + +void ui_textarea_insert(UiText *text, int pos, char *str) { + +} + +void ui_textarea_setposition(UiText *text, int pos) { + +} + +int ui_textarea_position(UiText *text) { + return 0; +} + +void ui_textarea_setselection(UiText *text, int begin, int end) { + +} + +void ui_textarea_selection(UiText *text, int *begin, int *end) { + +} + +int ui_textarea_length(UiText *text) { + return 0; +} + +void ui_textarea_remove(UiText *text, int begin, int end) { + +} + +/* ----------------------------- TextField ----------------------------- */ + static W32WidgetClass textfield_widget_class = { .eventproc = ui_textfield_eventproc, .enable = w32_widget_default_enable, @@ -84,8 +213,8 @@ return (W32Size){ .width = textfield->width, .height = 32}; } -void ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - +int ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; } char* ui_textfield_get(UiString *s) { diff -r 3da24640513a -r e57ca2747782 ui/win32/text.h --- a/ui/win32/text.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/text.h Sat Dec 13 15:58:58 2025 +0100 @@ -33,13 +33,36 @@ #include "container.h" #include "toolkit.h" +typedef struct UiTextArea { + UiWidget widget; + int width; + int height; +} UiTextArea; + typedef struct UiTextField { UiWidget widget; int width; } UiTextField; +W32Size ui_textarea_get_preferred_size(W32Widget *widget); +int ui_textarea_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +void ui_textarea_save(UiText *text); +void ui_textarea_destroy(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_set(UiText *text, const char *str); +char* ui_textarea_get(UiText *text); +char* ui_textarea_getsubstr(UiText *text, int begin, int end); +void ui_textarea_insert(UiText *text, int pos, char *str); +void ui_textarea_setposition(UiText *text, int pos); +int ui_textarea_position(UiText *text); +void ui_textarea_setselection(UiText *text, int begin, int end); +void ui_textarea_selection(UiText *text, int *begin, int *end); +int ui_textarea_length(UiText *text); +void ui_textarea_remove(UiText *text, int begin, int end); + W32Size ui_textfield_get_preferred_size(W32Widget *widget); -void ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +int ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); char* ui_textfield_get(UiString *s); void ui_textfield_set(UiString *s, const char *value); diff -r 3da24640513a -r e57ca2747782 ui/win32/toolkit.c --- a/ui/win32/toolkit.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/toolkit.c Sat Dec 13 15:58:58 2025 +0100 @@ -35,6 +35,7 @@ #include "../common/toolbar.h" #include "../common/document.h" #include "../common/properties.h" +#include "../common/app.h" #include "../ui/widget.h" @@ -45,13 +46,6 @@ static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -void *open_data; -static ui_callback exit_func; -void *exit_data; - static HFONT ui_font = NULL; void ui_init(const char *appname, int argc, char **argv) { @@ -62,9 +56,10 @@ uic_toolbar_init(); uic_load_app_properties(); - ui_window_init(); - - INITCOMMONCONTROLSEX icex = { sizeof(icex), ICC_WIN95_CLASSES }; + INITCOMMONCONTROLSEX icex = { + sizeof(icex), + ICC_WIN95_CLASSES | ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_BAR_CLASSES | ICC_TAB_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES + }; InitCommonControlsEx(&icex); SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); @@ -72,6 +67,8 @@ NONCLIENTMETRICS ncm = { sizeof(ncm) }; SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE); ui_font = CreateFontIndirect(&ncm.lfMessageFont); + + ui_window_init(); } HFONT ui_win32_get_font(void) { @@ -88,25 +85,8 @@ return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); // event loop MSG msg; @@ -115,9 +95,7 @@ DispatchMessage(&msg); } - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); } @@ -128,8 +106,11 @@ LRESULT CALLBACK ui_default_eventproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { W32Widget *widget = (W32Widget*)GetWindowLongPtr(hwnd, GWLP_USERDATA); if (widget && widget->wclass->eventproc) { - widget->wclass->eventproc(widget, hwnd, uMsg, wParam, lParam); + if (widget->wclass->eventproc(widget, hwnd, uMsg, wParam, lParam)) { + return 1; + } } + switch(uMsg) { case WM_DESTROY: { PostQuitMessage(0); @@ -141,16 +122,30 @@ if (cmdWidget && cmdWidget->wclass->eventproc) { cmdWidget->wclass->eventproc(cmdWidget, hwnd, uMsg, wParam, lParam); } + break; + } + case WM_NOTIFY: { + LPNMHDR hdr = (LPNMHDR)lParam; + HWND hwndCtrl = hdr->hwndFrom; + W32Widget *cmdWidget = (W32Widget*)GetWindowLongPtr(hwndCtrl, GWLP_USERDATA); + if (cmdWidget && cmdWidget->wclass->eventproc) { + cmdWidget->wclass->eventproc(cmdWidget, hwnd, uMsg, wParam, lParam); + } + break; } case WM_SIZE: { int width = LOWORD(lParam); int height = HIWORD(lParam); - if (widget->layout) { + if (widget && widget->layout) { widget->layout(widget->layoutmanager, width, height); } break; } - default: return DefWindowProc(hwnd, uMsg, wParam, lParam); + default: break;//return DefWindowProc(hwnd, uMsg, wParam, lParam); } - return 0; -} \ No newline at end of file + return DefWindowProc(hwnd, uMsg, wParam, lParam);; +} + +void ui_call_mainthread(ui_threadfunc tf, void* td) { + // TODO +} diff -r 3da24640513a -r e57ca2747782 ui/win32/window.c --- a/ui/win32/window.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/window.c Sat Dec 13 15:58:58 2025 +0100 @@ -39,6 +39,7 @@ #include #include "win32.h" +#include "menu.h" static W32WidgetClass w32_toplevel_widget_class = { .eventproc = ui_window_widget_event, @@ -54,9 +55,9 @@ void ui_window_init(void) { - hInstance = GetModuleHandle(NULL); - - WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + hInstance = GetModuleHandle(NULL); + + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; wc.lpfnWndProc = ui_default_eventproc; wc.hInstance = hInstance; wc.lpszClassName = mainWindowClass; @@ -69,49 +70,59 @@ } } -static UiObject* create_window(const char *title, void *window_data, bool simple) { +static UiObject* create_window(const char *title, bool simple) { UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; - HWND hwnd = CreateWindowExA( - 0, - "UiMainWindow", - title, - WS_OVERLAPPEDWINDOW, + HWND hwnd = CreateWindowExA( + 0, + "UiMainWindow", + title, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, - 800, - 600, + 800, + 600, + NULL, NULL, - NULL, - hInstance, - NULL); + hInstance, + NULL); + + if (!simple) { + HMENU menubar = ui_create_main_menu(obj); + if (menubar) { + SetMenu(hwnd, menubar); + } + } UpdateWindow(hwnd); - UiContainerX *container = ui_box_container_create(obj, hwnd, UI_BOX_VERTICAL, 0, INSETS_ZERO); - uic_object_push_container(obj, container); - UiBoxContainer *box = (UiBoxContainer*)container; + UiContainerX *container = ui_box_container_create(obj, hwnd, UI_BOX_VERTICAL, 0, INSETS_ZERO); + uic_object_push_container(obj, container); + UiBoxContainer *box = (UiBoxContainer*)container; - UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow)); - widget->obj = obj; - widget->widget.layout = (W32LayoutFunc)ui_grid_layout; - widget->widget.layoutmanager = box->layout; - obj->widget = (W32Widget*)widget; - obj->ref = 1; + UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow)); + widget->obj = obj; + widget->widget.layout = (W32LayoutFunc)ui_grid_layout; + widget->widget.layoutmanager = box->layout; + obj->widget = (W32Widget*)widget; + obj->ref = 1; - return obj; + return obj; } -UiObject *ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE); +UiObject *ui_window(const char *title) { + return create_window(title, FALSE); } +UiObject *ui_simple_window(const char *title) { + return create_window(title, TRUE); +} -void ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - //UiWindow *window = (UiWindow*)widget; +int ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + //UiWindow *window = (UiWindow*)widget; + return 0; } void ui_window_widget_show(W32Widget *w, BOOLEAN show) { - ShowWindow(w->hwnd, show ? SW_SHOWNORMAL : SW_HIDE); + ShowWindow(w->hwnd, show ? SW_SHOWNORMAL : SW_HIDE); } diff -r 3da24640513a -r e57ca2747782 ui/win32/window.h --- a/ui/win32/window.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/win32/window.h Sat Dec 13 15:58:58 2025 +0100 @@ -48,7 +48,7 @@ void ui_window_init(void); -void ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +int ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void ui_window_widget_show(W32Widget *w, BOOLEAN show); #ifdef __cplusplus diff -r 3da24640513a -r e57ca2747782 ui/winui/list.h --- a/ui/winui/list.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/winui/list.h Sat Dec 13 15:58:58 2025 +0100 @@ -28,7 +28,7 @@ #pragma once -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include "../ui/container.h" diff -r 3da24640513a -r e57ca2747782 ui/winui/table.h --- a/ui/winui/table.h Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/winui/table.h Sat Dec 13 15:58:58 2025 +0100 @@ -28,7 +28,7 @@ #pragma once -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include "dnd.h" diff -r 3da24640513a -r e57ca2747782 ui/winui/toolkit.cpp --- a/ui/winui/toolkit.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/winui/toolkit.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -37,6 +37,7 @@ #include "../common/document.h" #include "../common/toolbar.h" #include "../common/properties.h" +#include "../common/app.h" #include "icons.h" @@ -55,15 +56,6 @@ static const char* application_name; -static ui_callback startup_func; -static void* startup_data; - -static ui_callback open_func; -void* open_data; - -static ui_callback exit_func; -void* exit_data; - static ui_callback appclose_fnc; static void* appclose_udata; @@ -75,19 +67,14 @@ void ui_app_run_startup() { uiDispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); - - if (startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); } class App : public ApplicationT { public: void OnLaunched(LaunchActivatedEventArgs const&) { Resources().MergedDictionaries().Append(XamlControlsResources()); - if (startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); //auto window = make(); //window.Activate(); @@ -175,21 +162,6 @@ return application_name; } -void ui_onstartup(ui_callback f, void* userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void* userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void* userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_main() { /* init_apartment(); diff -r 3da24640513a -r e57ca2747782 ui/winui/window.cpp --- a/ui/winui/window.cpp Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/winui/window.cpp Sat Dec 13 15:58:58 2025 +0100 @@ -68,89 +68,88 @@ obj->wobj->window.Close(); } -UiObject* ui_window(const char* title, void* window_data) { - UiObject* obj = ui_simple_window(title, window_data); +UiObject* ui_window(const char* title) { + UiObject* obj = ui_simple_window(title); - /* - if (uic_get_menu_list()) { - // create/add menubar - MenuBar mb = ui_create_menubar(obj); - mb.VerticalAlignment(VerticalAlignment::Top); - obj->container->Add(mb, false); - } - */ + /* + if (uic_get_menu_list()) { + // create/add menubar + MenuBar mb = ui_create_menubar(obj); + mb.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(mb, false); + } + */ - if (uic_toolbar_isenabled()) { - // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto" - Grid toolbar_grid = Grid(); - GridLength gl; - gl.Value = 0; - gl.GridUnitType = GridUnitType::Auto; + if (uic_toolbar_isenabled()) { + // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto" + Grid toolbar_grid = Grid(); + GridLength gl; + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; - ColumnDefinition coldef0 = ColumnDefinition(); - coldef0.Width(gl); - toolbar_grid.ColumnDefinitions().Append(coldef0); + ColumnDefinition coldef0 = ColumnDefinition(); + coldef0.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef0); - gl.Value = 1; - gl.GridUnitType = GridUnitType::Star; - ColumnDefinition coldef1 = ColumnDefinition(); - coldef1.Width(gl); - toolbar_grid.ColumnDefinitions().Append(coldef1); + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + ColumnDefinition coldef1 = ColumnDefinition(); + coldef1.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef1); - gl.Value = 0; - gl.GridUnitType = GridUnitType::Auto; - ColumnDefinition coldef2 = ColumnDefinition(); - coldef2.Width(gl); - toolbar_grid.ColumnDefinitions().Append(coldef2); + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + ColumnDefinition coldef2 = ColumnDefinition(); + coldef2.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef2); - // rowdef - gl.Value = 0; - gl.GridUnitType = GridUnitType::Auto; - RowDefinition rowdef = RowDefinition(); - rowdef.Height(gl); - toolbar_grid.RowDefinitions().Append(rowdef); + // rowdef + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + RowDefinition rowdef = RowDefinition(); + rowdef.Height(gl); + toolbar_grid.RowDefinitions().Append(rowdef); - // create commandbar - CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); - CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); - CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + // create commandbar + CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); - bool addappmenu = true; - if (cxListSize(def_r) > 0) { - CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu); - toolbar_grid.SetColumn(toolbar_r, 2); - toolbar_grid.SetRow(toolbar_r, 0); - toolbar_grid.Children().Append(toolbar_r); - addappmenu = false; - } - if (cxListSize(def_c) > 0) { - CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu); - toolbar_c.HorizontalAlignment(HorizontalAlignment::Center); - toolbar_grid.SetColumn(toolbar_c, 1); - toolbar_grid.SetRow(toolbar_c, 0); - toolbar_grid.Children().Append(toolbar_c); - addappmenu = false; - } - if (cxListSize(def_l) > 0) { - CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu); - toolbar_grid.SetColumn(toolbar_l, 0); - toolbar_grid.SetRow(toolbar_l, 0); - toolbar_grid.Children().Append(toolbar_l); - } + bool addappmenu = true; + if (cxListSize(def_r) > 0) { + CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu); + toolbar_grid.SetColumn(toolbar_r, 2); + toolbar_grid.SetRow(toolbar_r, 0); + toolbar_grid.Children().Append(toolbar_r); + addappmenu = false; + } + if (cxListSize(def_c) > 0) { + CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu); + toolbar_c.HorizontalAlignment(HorizontalAlignment::Center); + toolbar_grid.SetColumn(toolbar_c, 1); + toolbar_grid.SetRow(toolbar_c, 0); + toolbar_grid.Children().Append(toolbar_c); + addappmenu = false; + } + if (cxListSize(def_l) > 0) { + CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu); + toolbar_grid.SetColumn(toolbar_l, 0); + toolbar_grid.SetRow(toolbar_l, 0); + toolbar_grid.Children().Append(toolbar_l); + } - toolbar_grid.VerticalAlignment(VerticalAlignment::Top); - obj->container->Add(toolbar_grid, false); - } + toolbar_grid.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(toolbar_grid, false); + } - return obj; + return obj; } -UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) { +UIEXPORT UiObject* ui_simple_window(const char *title) { UiObject* obj = uic_object_new_toplevel(); obj->ctx = uic_context(obj, mp); - obj->window = window_data; Window window = Window(); //Window window = make(); diff -r 3da24640513a -r e57ca2747782 ui/winui/winui.vcxproj --- a/ui/winui/winui.vcxproj Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/winui/winui.vcxproj Sat Dec 13 15:58:58 2025 +0100 @@ -142,7 +142,7 @@ - + diff -r 3da24640513a -r e57ca2747782 ui/winui/winui.vcxproj.filters --- a/ui/winui/winui.vcxproj.filters Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/winui/winui.vcxproj.filters Sat Dec 13 15:58:58 2025 +0100 @@ -108,7 +108,7 @@ public - + public